Reference-library parity (Radix Themes, Chakra v3): - size: 'xs' | 'sm' | 'md' | 'lg' (Chakra t-shirt scale) → thickness via --scroll-area-scrollbar-size. - radius: 'none' | 'sm' | 'md' | 'lg' | 'full' (Radix Themes) → thumb curvature via --scroll-area-thumb-radius. - scrollbars: 'vertical' | 'horizontal' | 'both' (Radix Themes) → recipe-level sugar that auto-mounts Scrollbar / Thumb / Corner from the root. Explicit composition still supported (omit the prop). Bug fix — type='scroll' + horizontal sometimes stuck hidden: - `mounted` on ScrollAreaScrollbarProvider is now $state. Previously a plain field, so the `shouldShow` derived didn't invalidate when the requestFrame mount-flag flipped during the first frame. - The reveal-on-scroll $effect now skips its first run via a `firstRun` flag. That first run was the subscription pass with scrollTop/scrollLeft still 0 — it was eagerly calling show() and setting `visible=true` before `mounted` flipped. Subsequent real scrolls then assigned visible=true again (no-op), so shouldShow didn't re-run and the bar stayed hidden until the hide timer expired. Now the bar only reveals on genuine scroll deltas. Demo: chip controls for size + radius, switched to scrollbars prop on the root (drops the explicit Scrollbar/Thumb children), eidos snippet shows the recipe-sugar form, soma snippet still shows the explicit Radix-style composition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>active-uix
parent
560ce8f66c
commit
e11d3539ef
@ -1,23 +1,81 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Eidos `<ScrollArea>` — root component. Wraps Soma's
|
||||
* `<ScrollArea.Provider>` to set up the headless context; children
|
||||
* compose via the named members attached in `index.ts`:
|
||||
* `<ScrollArea.Provider>` to set up the headless context.
|
||||
*
|
||||
* <ScrollArea type="hover">
|
||||
* <ScrollArea type="hover" size="md" radius="md" scrollbars="vertical">
|
||||
* <ScrollArea.Viewport>{content}</ScrollArea.Viewport>
|
||||
* <ScrollArea.Scrollbar orientation="vertical">
|
||||
* <ScrollArea.Thumb />
|
||||
* </ScrollArea.Scrollbar>
|
||||
* <ScrollArea.Corner />
|
||||
* </ScrollArea>
|
||||
*
|
||||
* Two composition modes:
|
||||
*
|
||||
* - Explicit (default, Radix-style) — `scrollbars` omitted; user
|
||||
* composes Viewport + Scrollbar + Thumb + Corner manually.
|
||||
* - Sugar (Radix-Themes style) — `scrollbars="vertical|horizontal|both"`
|
||||
* auto-mounts the right parts; user only composes Viewport.
|
||||
*
|
||||
* `size` / `radius` are pure CSS-variable bindings; they affect
|
||||
* recipe paint only, not Soma's scroll math.
|
||||
*/
|
||||
import * as ScrollArea from '$soma/components/scroll-area';
|
||||
import type { ScrollAreaProps } from './types';
|
||||
import Viewport from './scroll-area-viewport.svelte';
|
||||
import Scrollbar from './scroll-area-scrollbar.svelte';
|
||||
import Thumb from './scroll-area-thumb.svelte';
|
||||
import Corner from './scroll-area-corner.svelte';
|
||||
import type { ScrollAreaProps, ScrollAreaSize, ScrollAreaRadius } from './types';
|
||||
|
||||
let {
|
||||
children,
|
||||
size,
|
||||
radius,
|
||||
scrollbars,
|
||||
style,
|
||||
...rest
|
||||
}: ScrollAreaProps = $props();
|
||||
|
||||
// Map t-shirt size → `--space-*` token. Matches Chakra's xs/sm/md/lg
|
||||
// scale. The CSS recipe consumes `--scroll-area-scrollbar-size` for
|
||||
// both track thickness and corner dimensions.
|
||||
const SIZE_TOKEN: Record<ScrollAreaSize, string> = {
|
||||
xs: 'var(--space-1-5)',
|
||||
sm: 'var(--space-2)',
|
||||
md: 'var(--space-2-5)',
|
||||
lg: 'var(--space-3)'
|
||||
};
|
||||
|
||||
const RADIUS_TOKEN: Record<ScrollAreaRadius, string> = {
|
||||
none: '0',
|
||||
sm: 'var(--radius-sm)',
|
||||
md: 'var(--radius-md)',
|
||||
lg: 'var(--radius-lg)',
|
||||
full: 'var(--radius-full)'
|
||||
};
|
||||
|
||||
const recipeStyle = $derived(() => {
|
||||
const parts: string[] = [];
|
||||
if (size) parts.push(`--scroll-area-scrollbar-size: ${SIZE_TOKEN[size]};`);
|
||||
if (radius) parts.push(`--scroll-area-thumb-radius: ${RADIUS_TOKEN[radius]};`);
|
||||
return parts.join(' ');
|
||||
});
|
||||
|
||||
let { children, ...rest }: ScrollAreaProps = $props();
|
||||
const mergedStyle = $derived(
|
||||
[recipeStyle(), typeof style === 'string' ? style : ''].filter(Boolean).join(' ')
|
||||
);
|
||||
</script>
|
||||
|
||||
<ScrollArea.Provider {...rest}>
|
||||
<ScrollArea.Provider {...rest} style={mergedStyle || undefined}>
|
||||
{@render children?.()}
|
||||
{#if scrollbars === 'vertical' || scrollbars === 'both'}
|
||||
<Scrollbar orientation="vertical">
|
||||
<Thumb />
|
||||
</Scrollbar>
|
||||
{/if}
|
||||
{#if scrollbars === 'horizontal' || scrollbars === 'both'}
|
||||
<Scrollbar orientation="horizontal">
|
||||
<Thumb />
|
||||
</Scrollbar>
|
||||
{/if}
|
||||
{#if scrollbars === 'both'}
|
||||
<Corner />
|
||||
{/if}
|
||||
</ScrollArea.Provider>
|
||||
|
||||
Loading…
Reference in new issue