feat(ui): Add a Mosaic scroll area with scroll-driven edge fades - #9312
feat(ui): Add a Mosaic scroll area with scroll-driven edge fades#9312maxyinger wants to merge 22 commits into
Conversation
🦋 Changeset detectedLatest commit: cef0fc7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A vertically scrolling region that fades its content at whichever edge still has something to reveal. Composed as `ScrollArea.Root` + `ScrollArea.Viewport`. The indicators are pure CSS with no runtime cost: two scroll-driven animations write a progress var per edge, and a single four-stop mask gradient computes its stops from them. Driving a number rather than the mask's own geometry means one gradient covers both edges — no `mask-composite` — and leaves the progress vars readable so a consumer can swap the treatment from a stylesheet alone. The vars are `stylex.types.number`, so StyleX emits an `@property` registration for each. That does two jobs: an unregistered custom property animates discretely and would snap at 50% instead of tracking the scroll, and `initial-value: 0` is what hides both indicators when the viewport isn't scrollable — an inactive timeline leaves the vars at their initial value, so the resting state is already the hidden one. Only `animation-name` sits behind `@supports (animation-timeline: scroll())`. A browser that ignores `animation-timeline` would otherwise run the animations on the document timeline at the default `0s` duration, land on the end frame immediately, and paint both fades permanently; with no name the rest is inert and the mask resolves to fully opaque. Because the fade is a mask rather than a sticky overlay element, it is paint-only and cannot shift the content the way sticky pseudo-elements do. `gutter` defaults to `auto`, matching CSS. `stable` is the opt-in for content that can change height in place — a filterable list, a paginated table — where crossing the overflow threshold would shift the rows sideways. Scrollbar size is a theme token (`--cl-scrollbar-width`, default `thin`) rather than a prop, since Mosaic has no reason to size scrollbars differently between components. It is keyword-only by spec: `scrollbar-width` accepts `auto | thin | none` and not a length. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the story module and MDX page for `ScrollArea`, following the archetype A compound layout: Example, Usage, Parts, Styling. Five examples. `Default` and `NotScrollable` show that absent indicators are the resting state rather than something switched off. `Gutter` toggles the content across the overflow threshold, because `stable` and `auto` are identical while the content permanently overflows — a side-by-side pair alone demonstrates nothing, and the docs say so alongside the platform caveat, since neither value does anything where scrollbars overlay. `CustomIndicators` is the CSS-only override path: `mask-image: none` plus the progress vars driving a pair of gradient overlays. Its scrim mixes from `--cl-color-card-foreground` rather than hardcoding black — a black scrim darkens a dark surface, which is indistinguishable from the mask it replaced, so the example would have taught a bug in dark mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--cl-scroll-area-fade-size`, `-fade-range` and `-scrollbar-inset` become `--cl-scroll-fade-size`, `-range` and `-inset` in `tokens.stylex.ts`. Every other token in Mosaic is global and category-named — `--cl-color-*`, `--cl-radius-*`, `--cl-duration-*`, `--cl-scrollbar-width`. These three were about to be the first `--cl-<component>-<thing>` family, and if each component followed, theming Clerk would mean enumerating N components x M knobs rather than learning one vocabulary. How soft the edge of a scrolling region is belongs to the design language, not to one component; a component that needs different values still sets the token on itself. The progress vars stay component-named on purpose. They are per-element runtime output that the animations overwrite on every scrolling element, so a `:root` value would be meaningless — promoting them would imply they are settable when setting them does nothing. Set versus read is the line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Chrome and Firefox make an overflowing scroll container keyboard-focusable on their own; Safari does not, so a keyboard-only user there cannot scroll the region at all (WCAG 2.1.1). Leaving that to the caller meant every caller had to know about a browser gap to get a baseline accessibility guarantee, which is the component's responsibility rather than theirs. The viewport now takes a tab stop exactly when the browsers themselves would: when it overflows AND its content contains nothing focusable. The second half carries as much weight as the first — a list whose rows are buttons or links is already reachable, since tabbing into the content scrolls it, so a stop on the container would be redundant. Reproducing the browsers' rule rather than sniffing for Safari means this is a no-op wherever the browser already handles it. Both halves are observed rather than sampled once. A ResizeObserver watches the viewport and each element child, because content growing past the threshold leaves the viewport's own box unchanged and a MutationObserver wouldn't catch a purely visual change like an image loading; a MutationObserver covers rows gaining or losing interactivity. An explicit `tabIndex` always wins, and `-1` opts out. This is the component's only JavaScript. The JSDoc claim that nothing ran at runtime is corrected accordingly — the indicators still cost nothing, with no scroll listener and no measurement behind them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings `ScrollArea` in line with the rest of Mosaic, where every part is polymorphic through `render`. A scrolling region often wants semantics of its own — a list of members is a `<ul>`, a labelled region is a `<section>` — and without this the caller had to choose between the component's styling and the right element. Both parts take it rather than just the viewport: a compound component where only one half is polymorphic is a trap. On the viewport the rendered element has to be able to establish a scroll box, since the overflow, mask and scroll timelines all apply to whatever lands there. `useRender` merges an array of refs, which also replaces the hand-rolled ref composition the viewport needed to observe its own element while still honouring the caller's ref. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nent Everything the scroll area does is CSS, so wrapping it in a component only added a DOM node to every scrolling surface and an API to version. `scrollAreaViewport` returns the atoms for the element that scrolls and `scrollAreaRoot` styles a positioned ancestor, so the styles ride on an element that already exists — an `Item.Group`, a list, a panel body — rather than introducing one. That also drops the `render` prop, since applying styles to your own element is polymorphism by construction, and it removes the `.cl-scroll-area-*` classes: the host element keeps its own slot class, and that stays the hook a theme targets. The swingset examples put the atoms on an `Item.Group`, so the override story is written against `.cl-item-group`. The managed tab stop goes with it. It was the only JavaScript here, and `tabindex` cannot be expressed as a style, so the Safari keyboard gap is now the caller's to close — documented, with the rule the browsers themselves use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n page A dedicated ScrollArea page made sense when there was a ScrollArea component. Now that it ships as atoms, a component page for a thing that isn't a component reads as a mistake, and the atoms are only meaningful applied to something. `Item` gains a `Scrolling` example: a capped-height group of organizations with the scroll surface spread onto the `Item.Group` itself, which is the shape a real call site takes. The page absorbs what the ScrollArea docs carried that has no other home — the theme tokens, the two progress vars, the override recipe, and the two caveats worth knowing (the mask covers the scrollbar until a custom scrollbar exists, and Safari's keyboard gap is the caller's to close). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A touch platform draws an overlay scrollbar there is no width or colour to apply to, and thinning a target that is already hard to hit would be actively worse. Gating appearance on `@media (pointer: fine)` leaves those platforms with the bar they already draw. Shopify's `s-scroll-box` gates the same way. `scrollbar-gutter` stays ungated: reserving space is a layout decision about the surrounding content, not an appearance one, and the value that is right for a list that can change height doesn't change with the pointer. Forced-colors still wins inside the gate — StyleX nests the two at-rules and triples the class, so the system scrollbar comes back where it has to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces `scrollbar-color` / `scrollbar-width` with the webkit pseudo-elements, which is the only path that can express a per-interaction-state thumb colour and a real pixel width. The browser derived the hover state from `scrollbar-color` and got it backwards, lightening the thumb on hover; there is no syntax to correct that on the standard path. The two are mutually exclusive — a non-`auto` value for either standard property makes a UA ignore the pseudo-elements — so this is one code path, not two. Firefox implements neither and keeps its platform scrollbar. `::-webkit-scrollbar-thumb` cannot transition its own properties, so the colour is routed through an `@property`-registered var on the scroller and inherited into the pseudo-element, which only reads it. `--cl-scrollbar-width` becomes a length, and `--cl-scroll-fade-inset` is deleted: now that we specify the lane's width, the mask derives its inset from it rather than exposing a second name for the same number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f the lane Three corrections to the webkit scrollbar, all found in the browser. The mask still covered the scrollbar: StyleX 0.19 rewrites the argument of `@supports selector(…)` with the same `:not(#\#)` specificity bump it applies to real selectors, turning the query into `selector(:not(#\#):not(#\#):not(#\#)::-webkit-scrollbar)` — false everywhere. Verified in Chrome, where the honest form is true and the rewritten one is false. Swapped for a property-based condition, which StyleX leaves alone. Hover was reading the SCROLLER's `:hover`, so the thumb lit up whenever the pointer was anywhere over the region. Both states now live on the thumb itself, and the transition moves to the pseudo-element with them. The lane goes to 8px carrying a 2px inset — a 4px pill — and the thumb starts mixed most of the way toward the surface colour, so a bar this thin reads as a hairline rather than a hard rule. In pixels, not rem: a scrollbar is chrome, and should not scale with the text around it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Half of the Item page — 129 of 242 lines — documented the scroll surface, which isn't Item and isn't a component. `Hooks` was already the precedent for a non-component section (`use-data-table.stories.tsx` is `meta` alone, no story exports), so `Styles` follows it: the scroll area gets a real page with room for examples that don't fit on a component page. Item keeps the demo and a pointer. This does revisit "Document the scroll surface on Item instead of its own page", but that move was made because a COMPONENT page was the wrong home for something that isn't a component. A Styles section answers that rather than reopening it. New examples the old page had no room for: a surface with nothing to scroll (the resting state costs nothing and needs no branch), `stable` vs `auto` gutter side by side, and a retuned scrollbar. Two rendering bugs surfaced by the first non-component, multi-word entry: the sidebar wrapped every non-hook title in JSX, so this read `<Scroll Area />`, and the breadcrumb only capitalised the first character, so it read "Scroll area". The sidebar now picks its form from the layer, and the breadcrumb resolves the registry's own `meta.title` — which also fixes `useDataTable`, previously shown as "Use data table". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blink runs no transition declared on `::-webkit-scrollbar-thumb` — the reason Polaris declares its own on the scroller. Scoping hover to the thumb moved the transition onto the pseudo-element with it, which silently killed the fade. Putting it back on the scroller draws the line clearly: a change made ON THE SCROLLER animates and the thumb inherits it, a change made on the thumb itself can only snap. So `-hover` and `-active` are instant by construction, while a consumer retargeting `--cl-scrollbar-thumb` from the region's `:hover` gets a real fade. Confirmed by sampling painted pixels mid-transition. Also declares `::-webkit-scrollbar-track` transparent. Opting into a custom scrollbar makes the track ours, and leaving it undeclared falls back to the UA's painting for that part — which shows through as a dark rail the moment the thumb is less than opaque. Adds the hover-reveal example this all came out of, and documents the one trap in it: `transparent` is `rgba(0, 0, 0, 0)`, so fading out of the keyword drags the thumb through dark half-transparent greys. The rest value is the reveal colour at zero alpha instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dius A 12px corner against an 8px scrollbar lane reads as a mismatch — the bar runs straight past the curve. `--cl-radius-inner` keeps the box out of the way of the thing being demonstrated. Real surfaces would decorate around this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Its three colours were all thumb states, which snap by construction — no choice of colour makes them move. Added a scroller-driven step (rest amber → teal on region hover) so the example shows both halves of the rule: a change made on the scroller fades, a change made on the thumb does not. Verified by sampling mid-transition pixels on the first and confirming the second jumps. Notes in the description why the values are `oklch()` literals — one space keeps the animated step well defined — and points at the `transparent` trap rather than repeating it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reveal jumped straight to full `--cl-color-neutral-faded`, which spent the contrast up front and left the thumb's own hover and active with nowhere to go. Scoping the override to `:not(:hover)` means reaching the region simply stops hiding the bar, so the revealed state is Mosaic's ordinary rest colour and the thumb's states read as a step up from it, exactly as on an unstyled scroll area. Measured: invisible, then 78 → 101 → 126. The theming example's one animated step runs at `--cl-duration-base`, which is 0.15s — over before you finish moving the pointer in, and on a scrollbar off to one side that reads as nothing happening. Slowed to 0.6s in that example only. Neither Item nor Avatar reads a duration token, so nothing else stretches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The snippet still showed the two-rule form that revealed straight to full `--cl-color-neutral-faded` — the loud version the example moved away from. It now matches: one `:not(:hover)` rule hiding the default rest colour at zero alpha, plus why hiding beats revealing (it leaves the thumb's own states their headroom). The sample names the colour in an intermediate custom property for readability, which the story inlines; checked that the two resolve identically before shipping it as copy-paste. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds `--cl-scrollbar-thumb-idle`, a fourth colour sitting a notch below the base, used while the pointer is elsewhere. The four now run quietest to loudest: idle, base on region hover, then the thumb's own hover and active. Idle → base is set on the scroller, so it is the one step that fades. That also collapses the hover-reveal recipe to a single declaration — `--cl-scrollbar-thumb-idle: oklch(from var(--cl-scrollbar-thumb) l c h / 0)` — where the example previously needed a `:not(:hover)` rule of its own. Adds `--cl-scrollbar-thumb-offset` (default 1px) to nudge the thumb toward the content. A scrollbar's lane is placed by the browser at the inline end of the padding box and takes no margin, offset, or transform, so the only way to move anything is to move the thumb inside its lane: the offset comes off the inset on one side and goes onto the other, shifting the pill without resizing it. Logical properties, so the nudge stays on the content side in RTL, and the near side is floored at 0 — a negative border width is invalid and would drop the declaration back to `medium`, which looks like a broken thumb rather than a slightly large token. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Shifting the thumb within its lane means unequal insets, and unequal insets break the shape. `background-clip: content-box` clips the paint to the content box using the INNER radius, which CSS derives per corner as the outer radius minus that side's own border width. Different borders, different curvature on each half of every cap. Measured on the 4px pill: a symmetric `35 76 76 35` becomes `24 60 78 54`, and the outer edge squares off. The geometry was right — the pill stayed 4px at every offset — which is why the first pass at this looked fine. The cost was in the corners. Reverted to a single uniform `border-width`, with the finding written down in both the styles and the token docs so the next person doesn't retry it. Caps are mirrored again on every row of the cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ample Turns the prose-only "Replacing the fade" note into a running example. The mask comes off and two pseudo-element scrims take over, each driving both opacity and a translate from its edge's progress var, so a scrim slides out from behind its edge rather than only fading in. The scrim mixes from `--cl-color-card-foreground` so it reads as a shadow in light mode and a glow in dark, where the hardcoded black the old snippet used would have been invisible. `mask-image` is dropped inline rather than from the demo's stylesheet: in dev, swingset injects StyleX atoms with a `:not(#\#)` specificity bump that no selector can outrank. The extracted production sheet puts the same atoms in a cascade layer, so the documented CSS wins there on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cuts the page down without dropping anything it was actually saying, and strips the inline comments from the story bodies — those render in each example's "View code" footer, unlike the JSDoc above them, which `extractStorySource` leaves out. Three things had gone stale: - The recipe for keeping the scrollbar's lane without its bar set only `--cl-scrollbar-thumb: transparent`. `-idle` is a `color-mix` from that base, so it resolved to roughly 45% of the card colour rather than to nothing. It now sets both resting colours and says the thumb still paints faintly on hover. - "Those two states switch instantly" had drifted two paragraphs away from the hover/active states it refers to, after the thumb-offset note landed between them. - `:focus-within` lifts the thumb to its base colour just as the pointer does, which the token docs never mentioned. Also renames `themedRows`, now that three examples share it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing in the scroll area is reachable from a published entry point: the `exports` map exposes `./styles.css` but not `./mosaic`, so `scrollAreaViewport` and `scrollAreaRoot` cannot be imported, and no shipped Mosaic component applies the atoms. All a consumer's `styles.css` gains is a handful of inert `:root` declarations and unused atoms. Both changesets also described token churn that only ever happened inside this branch — `--cl-scroll-fade-inset` being removed, `--cl-scrollbar-width` changing from a keyword to a length — which would have read as breaking changes against a state nobody was ever given. The tokens become a real API the moment a shipped component scrolls with them or `./mosaic` is exported; that is the change worth a changeset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
API Changes Report
Summary
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
029359d to
cef0fc7
Compare
📝 WalkthroughWalkthroughAdds a Mosaic Scroll Area styling API with scrollbar tokens, scroll-fade variables, gutters, edge indicators, focus states, exports, and tests. Adds Scroll Area and Item Scrolling stories with reference documentation. Registers the new Styles documentation group and updates Swingset navigation labels and breadcrumbs. Documents Styles and Hooks as non-component layers. Adds a patch-release changeset. Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts (2)
273-303: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an explicit return type and an
@returnstag toscrollAreaViewport.
scrollAreaViewportis a public API export. The coding guidelines require explicit return types for public APIs and JSDoc with@returns. The inferred tuple type is also part of the published surface, so naming it prevents accidental widening.♻️ Proposed change
+ * `@returns` The StyleX atoms to spread onto the scrolling element. */ -export function scrollAreaViewport(gutter: ScrollAreaGutter = 'auto') { +export function scrollAreaViewport(gutter: ScrollAreaGutter = 'auto'): readonly stylex.StyleXStyles[] { return [Alternatively, declare a named exported type for the returned tuple so consumers can reference it.
As per coding guidelines: "Always define explicit return types for functions, especially public APIs" and "Document functions with JSDoc comments including
@param,@returns,@throws, and@exampletags".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts` around lines 273 - 303, Update the public scrollAreaViewport function with an explicit, stable return type for its StyleX atom tuple, preferably using a named exported type consumers can reference, and add an `@returns` tag to its existing JSDoc describing the returned styles. Preserve the current tuple contents and ordering.Sources: Coding guidelines, Learnings
201-205: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueConsider the paint cost of a mask on every scroll surface.
maskImageon the scroller forces a compositing layer and repaints the masked region on each scroll frame. For longItem.Grouplists this is measurable, and a mask on a scroll container also clipsposition: stickydescendants inside it. Confirm the behavior on a long list before this becomes the default for every Mosaic scroll surface.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts` around lines 201 - 205, Reassess the mask-based styling in the scroll-area style definition, especially the mask property using maskImage, and avoid applying it by default to every Mosaic scroll surface due to scroll repaint and sticky-descendant clipping costs. Validate the behavior with a long Item.Group list and preserve the existing scroll-surface appearance through a lower-cost or opt-in approach.packages/ui/src/mosaic/components/scroll-area/index.ts (1)
1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBarrel file, flagged for awareness only.
The coding guidelines advise against
index.tsre-export barrels because of circular-dependency risk. This file matches the existing Mosaic pattern, and no cycle exists today:scroll-area.styles.tsimports../../tokens.stylexdirectly rather than through a barrel. The split between value exports and theexport typeline is correct for tree-shaking. No change is required unless the team wants to drop the barrel pattern across Mosaic.As per coding guidelines: "Avoid barrel files (index.ts re-exports) as they can cause circular dependencies".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/mosaic/components/scroll-area/index.ts` around lines 1 - 3, No change is required in the scroll-area barrel exports; retain the existing value exports and separate export type for consistency with the Mosaic pattern. Only replace this index.ts re-export pattern if the team is undertaking a broader removal of Mosaic barrel files.Source: Coding guidelines
packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts (1)
8-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert atom identity rather than the atom count.
toHaveLength(7)passes if an atom is replaced by a different one, and it fails on any deliberate addition without saying what changed. Ifstyles.maskorstyles.scrollbaris dropped from the tuple, the count test is the only guard and it reports a number instead of the missing behavior. Assert membership of each atom, and assert that thestablevariant carries the gutter rather than only differing fromauto.💚 Proposed test change
it('composes the viewport atoms into one spreadable set', () => { - expect(scrollAreaViewport()).toHaveLength(7); - expect(scrollAreaRoot).toBeDefined(); + const atoms = scrollAreaViewport(); + expect(atoms).toHaveLength(7); + // Every atom is distinct, so a dropped or duplicated slot fails here. + expect(new Set(atoms).size).toBe(atoms.length); + expect(atoms.every(Boolean)).toBe(true); + expect(scrollAreaRoot).toBeDefined(); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts` around lines 8 - 21, Update the scrollAreaViewport tests to assert that the returned atom set contains each expected viewport atom, including styles.mask and styles.scrollbar, instead of relying on toHaveLength(7). In the gutter test, verify that the stable variant contains the stable gutter atom directly, while preserving the existing default-auto assertion.packages/ui/src/mosaic/tokens.stylex.ts (1)
90-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDesign rationale belongs in the Swingset reference page, not in inline comments. Across the three implementation files this cohort adds roughly 200 lines of prose rationale to about 130 lines of code. The content is valuable — the pixel-versus-rem argument, the four thumb states, the Blink transition limitation, the Gecko
@supportsworkaround, the forced-colors decision — but the coding guidelines require one terse line per warranted comment. This PR already addspackages/swingset/src/stories/scroll-area.mdx, which is the correct home for it, and prose there stays discoverable to consumers instead of only to readers of the source.
packages/ui/src/mosaic/tokens.stylex.ts#L90-L135: move the scrollbar-token rationale (pixel scale, four-state derivation, no-nudge decision,pointer: finegating) to the MDX page and keep one line per token group. Apply the same to the scroll-fade block on lines 152-166.packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144: move the 29-line scrollbar block comment to the MDX page and keep a single line noting why every declaration repeats{ default: null, '@media(pointer: fine)': … }. Apply the same treatment to the blocks at lines 16-26, 41-50, 77-91, 206-218, and 227-232. Keep theeslint-disablejustifications, which are load-bearing and already terse.packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18: move the@propertyregistration explanation to the MDX page and keep one line stating that the typed vars exist so StyleX emits@propertywithinitial-value: 0.As per coding guidelines: "Keep code comments minimal. Add comments only when critical to explain why a non-obvious change was made; never restate code behavior, and keep warranted comments to one terse line rather than a verbose multi-line block."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/mosaic/tokens.stylex.ts` around lines 90 - 135, Move verbose design rationale from packages/ui/src/mosaic/tokens.stylex.ts#L90-L135 and `#L152-L166`, packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144, `#L16-L26`, `#L41-L50`, `#L77-L91`, `#L206-L218`, and `#L227-L232`, and packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18 to packages/swingset/src/stories/scroll-area.mdx. Retain only one terse, warranted comment per token or style group: explain the pointer-fine gating, repeated media-query declarations, and typed variables emitting `@property` with initial-value 0; preserve the existing eslint-disable justifications.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/lazy-pans-tickle.md:
- Line 17: Align both release notes with the shipped scrollbar token contract:
in .changeset/lazy-pans-tickle.md at line 17, identify the transparent
--cl-scrollbar-thumb-idle value as an override or document its color-mix
default; in .changeset/mosaic-scroll-area.md at lines 19-20, document
--cl-scrollbar-width as an 8px length and remove --cl-scroll-fade-inset from the
new-token list.
In `@packages/swingset/src/lib/registry.ts`:
- Around line 98-105: Update the scroll-area story registration by importing the
exported ShadowIndicators symbol alongside the existing ScrollArea story imports
and adding it to scrollAreaModule with the other registered stories. Keep
registry.ts as the single source of truth for all six scroll-area stories.
In `@packages/swingset/src/stories/item.stories.tsx`:
- Around line 387-390: Update the Avatar.Image in the Item story to use an empty
alt value because Item.Title already provides the organization name; follow the
existing pattern in the corresponding Item MDX example and preserve the current
image source.
In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts`:
- Around line 51-58: Split the `--cl-scroll-fade-inset` absence assertion out of
the `keeps the thumb colour carrier out of the public token namespace` test into
a separate test focused on `scrollFadeVars`. Keep the existing
`scrollbarThumbVars` assertion in the thumb-colour test and preserve both
expectations unchanged.
---
Nitpick comments:
In `@packages/ui/src/mosaic/components/scroll-area/index.ts`:
- Around line 1-3: No change is required in the scroll-area barrel exports;
retain the existing value exports and separate export type for consistency with
the Mosaic pattern. Only replace this index.ts re-export pattern if the team is
undertaking a broader removal of Mosaic barrel files.
In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts`:
- Around line 273-303: Update the public scrollAreaViewport function with an
explicit, stable return type for its StyleX atom tuple, preferably using a named
exported type consumers can reference, and add an `@returns` tag to its existing
JSDoc describing the returned styles. Preserve the current tuple contents and
ordering.
- Around line 201-205: Reassess the mask-based styling in the scroll-area style
definition, especially the mask property using maskImage, and avoid applying it
by default to every Mosaic scroll surface due to scroll repaint and
sticky-descendant clipping costs. Validate the behavior with a long Item.Group
list and preserve the existing scroll-surface appearance through a lower-cost or
opt-in approach.
In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts`:
- Around line 8-21: Update the scrollAreaViewport tests to assert that the
returned atom set contains each expected viewport atom, including styles.mask
and styles.scrollbar, instead of relying on toHaveLength(7). In the gutter test,
verify that the stable variant contains the stable gutter atom directly, while
preserving the existing default-auto assertion.
In `@packages/ui/src/mosaic/tokens.stylex.ts`:
- Around line 90-135: Move verbose design rationale from
packages/ui/src/mosaic/tokens.stylex.ts#L90-L135 and `#L152-L166`,
packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144,
`#L16-L26`, `#L41-L50`, `#L77-L91`, `#L206-L218`, and `#L227-L232`, and
packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18
to packages/swingset/src/stories/scroll-area.mdx. Retain only one terse,
warranted comment per token or style group: explain the pointer-fine gating,
repeated media-query declarations, and typed variables emitting `@property` with
initial-value 0; preserve the existing eslint-disable justifications.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 27edef85-c1e1-453e-b133-8b3c69b9147b
📒 Files selected for processing (18)
.changeset/gentle-hoops-relax.md.changeset/lazy-pans-tickle.md.changeset/mosaic-scroll-area.mdpackages/swingset/CLAUDE.mdpackages/swingset/src/components/ClientRoot.tsxpackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/components/app-sidebar.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/stories/item.mdxpackages/swingset/src/stories/item.stories.tsxpackages/swingset/src/stories/scroll-area.mdxpackages/swingset/src/stories/scroll-area.stories.tsxpackages/ui/src/mosaic/components/scroll-area/index.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.styles.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.test.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.tspackages/ui/src/mosaic/styles/index.tspackages/ui/src/mosaic/tokens.stylex.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
| import { | ||
| Default as ScrollAreaDefault, | ||
| Gutter as ScrollAreaGutter, | ||
| HoverReveal as ScrollAreaHoverReveal, | ||
| meta as scrollAreaMeta, | ||
| NotScrollable as ScrollAreaNotScrollable, | ||
| ThemedScrollbar as ScrollAreaThemedScrollbar, | ||
| } from '../stories/scroll-area.stories'; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Find consumers that enumerate story-module keys generically, beyond `.meta`.
rg -n 'Object\.(keys|entries|values)\(' packages/swingset/src --glob '*.{ts,tsx}' -C3
rg -n 'storyModule\b' packages/swingset/src --glob '*.{ts,tsx}' -C3Repository: clerk/javascript
Length of output: 10068
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== registry imports and scrollAreaModule =="
sed -n '90,110p;220,236p' packages/swingset/src/lib/registry.ts
echo
echo "== scroll-area stories exports and shadow indicators =="
rg -n "^export (const|function|interface|type|default) (ShadowIndicators|meta|Default|Gutter|HoverReveal|NotScrollable|ThemedScrollbar)" packages/swingset/src/stories/scroll-area.stories.tsx -C 2 || true
echo
echo "== registry.ts StoryModule type =="
rg -n "interface StoryModule|type StoryModule|meta:" packages/swingset/src/lib/registry.ts -C 3
echo
echo "== docsViewer registry usage =="
rg -n "getModule|docsModules|registry" packages/swingset/src/ -g '*.ts' -g '*.tsx' -C 2Repository: clerk/javascript
Length of output: 12457
Register the missing ShadowIndicators story.
scroll-area.stories.tsx exports ShadowIndicators, but registry.ts imports and registers only the other five scroll area stories. Add ShadowIndicators to both the import and scrollAreaModule so lib/registry.ts remains the single source of truth for the story components.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/lib/registry.ts` around lines 98 - 105, Update the
scroll-area story registration by importing the exported ShadowIndicators symbol
alongside the existing ScrollArea story imports and adding it to
scrollAreaModule with the other registered stories. Keep registry.ts as the
single source of truth for all six scroll-area stories.
Source: Path instructions
| <Avatar.Image | ||
| src='https://github.com/clerk.png' | ||
| alt={name} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Avoid duplicating the organization name in the accessible label.
Item.Title already renders name inside the button. alt={name} can cause assistive technology to announce the organization name twice. Use alt='', as shown in packages/swingset/src/stories/item.mdx Line 74, unless the image conveys information different from the title.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/item.stories.tsx` around lines 387 - 390,
Update the Avatar.Image in the Item story to use an empty alt value because
Item.Title already provides the organization name; follow the existing pattern
in the corresponding Item MDX example and preserve the current image source.
| // The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not | ||
| // drift into the themable `--cl-` namespace the way a rename easily could. | ||
| it('keeps the thumb colour carrier out of the public token namespace', () => { | ||
| expect(scrollbarThumbVars).toMatchObject({ | ||
| '--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)', | ||
| }); | ||
| expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset'); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Move the --cl-scroll-fade-inset assertion into its own test.
The test name states that it checks the thumb-colour carrier namespace. Line 57 asserts that the removed --cl-scroll-fade-inset token is absent from scrollFadeVars, which is a separate contract. If line 57 fails, the reported test name points a reader at the wrong token family.
💚 Proposed split
it('keeps the thumb colour carrier out of the public token namespace', () => {
expect(scrollbarThumbVars).toMatchObject({
'--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)',
});
+ });
+
+ // The mask derives its inset from `--cl-scrollbar-width`, so the old knob must stay gone.
+ it('no longer exposes the removed fade inset token', () => {
expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset');
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not | |
| // drift into the themable `--cl-` namespace the way a rename easily could. | |
| it('keeps the thumb colour carrier out of the public token namespace', () => { | |
| expect(scrollbarThumbVars).toMatchObject({ | |
| '--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)', | |
| }); | |
| expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset'); | |
| }); | |
| // The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not | |
| // drift into the themable `--cl-` namespace the way a rename easily could. | |
| it('keeps the thumb colour carrier out of the public token namespace', () => { | |
| expect(scrollbarThumbVars).toMatchObject({ | |
| '--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)', | |
| }); | |
| }); | |
| // The mask derives its inset from `--cl-scrollbar-width`, so the old knob must stay gone. | |
| it('no longer exposes the removed fade inset token', () => { | |
| expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset'); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts` around
lines 51 - 58, Split the `--cl-scroll-fade-inset` absence assertion out of the
`keeps the thumb colour carrier out of the public token namespace` test into a
separate test focused on `scrollFadeVars`. Keep the existing
`scrollbarThumbVars` assertion in the thumb-colour test and preserve both
expectations unchanged.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/swingset/src/stories/scroll-area.stories.tsx (1)
28-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd explicit return types to the story components.
packages/swingset/src/stories/scroll-area.stories.tsx#L28-L58: Add: React.JSX.ElementtoOrganizationRow.packages/swingset/src/stories/scroll-area.stories.tsx#L64-L93: Add: React.JSX.ElementtoDefault.packages/swingset/src/stories/scroll-area.stories.tsx#L96-L115: Add: React.JSX.ElementtoNotScrollable.packages/swingset/src/stories/scroll-area.stories.tsx#L123-L182: Add: React.JSX.ElementtoGutter.packages/swingset/src/stories/scroll-area.stories.tsx#L208-L228: Add: React.JSX.ElementtoHoverReveal.packages/swingset/src/stories/scroll-area.stories.tsx#L235-L263: Add: React.JSX.ElementtoThemedScrollbar.packages/swingset/src/stories/scroll-area.stories.tsx#L279-L325: Add: React.JSX.ElementtoShadowIndicators.As per coding guidelines, “Always define explicit return types for functions, especially public APIs.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/swingset/src/stories/scroll-area.stories.tsx` around lines 28 - 58, Add explicit React.JSX.Element return types to the OrganizationRow, Default, NotScrollable, Gutter, HoverReveal, ThemedScrollbar, and ShadowIndicators components in packages/swingset/src/stories/scroll-area.stories.tsx at ranges 28-58, 64-93, 96-115, 123-182, 208-228, 235-263, and 279-325 respectively.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/swingset/src/stories/scroll-area.mdx`:
- Around line 22-30: Resolve the unavailable scroll-area import used in the
example by either marking this API as internal/unreleased in the documentation
or exposing ./mosaic/components/scroll-area through the `@clerk/ui` package
exports map. Update the relevant documentation or package export configuration
while preserving the existing scrollAreaRoot and scrollAreaViewport usage.
In `@packages/swingset/src/stories/scroll-area.stories.tsx`:
- Around line 16-20: Update the exported StoryMeta configuration for the Scroll
Area story to set styleEngine to 'stylex', ensuring the documented
scrollAreaRoot and scrollAreaViewport APIs are rendered as StyleX APIs.
---
Nitpick comments:
In `@packages/swingset/src/stories/scroll-area.stories.tsx`:
- Around line 28-58: Add explicit React.JSX.Element return types to the
OrganizationRow, Default, NotScrollable, Gutter, HoverReveal, ThemedScrollbar,
and ShadowIndicators components in
packages/swingset/src/stories/scroll-area.stories.tsx at ranges 28-58, 64-93,
96-115, 123-182, 208-228, 235-263, and 279-325 respectively.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 8a14de27-07ab-43af-8ff4-8fa4f9b5b4b0
📒 Files selected for processing (16)
.changeset/gentle-hoops-relax.mdpackages/swingset/CLAUDE.mdpackages/swingset/src/components/ClientRoot.tsxpackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/components/app-sidebar.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/stories/item.mdxpackages/swingset/src/stories/item.stories.tsxpackages/swingset/src/stories/scroll-area.mdxpackages/swingset/src/stories/scroll-area.stories.tsxpackages/ui/src/mosaic/components/scroll-area/index.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.styles.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.test.tspackages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.tspackages/ui/src/mosaic/styles/index.tspackages/ui/src/mosaic/tokens.stylex.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/swingset/src/components/app-sidebar.tsx
- packages/swingset/src/components/DocsViewer.tsx
- packages/swingset/src/components/ClientRoot.tsx
- packages/swingset/src/lib/registry.ts
- packages/ui/src/mosaic/styles/index.ts
- packages/swingset/src/stories/item.mdx
- packages/swingset/CLAUDE.md
- packages/swingset/src/stories/item.stories.tsx
- packages/ui/src/mosaic/tokens.stylex.ts
- packages/ui/src/mosaic/components/scroll-area/index.ts
- packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts
- .changeset/gentle-hoops-relax.md
- packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts
- packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts
| ```tsx | ||
| import { scrollAreaRoot, scrollAreaViewport } from '@clerk/ui/mosaic/components/scroll-area'; | ||
| import * as stylex from '@stylexjs/stylex'; | ||
|
|
||
| <div | ||
| {...stylex.props(scrollAreaRoot)} | ||
| style={{ height: 260 }} | ||
| > | ||
| <Item.Group {...stylex.props(...scrollAreaViewport())}>{organizations}</Item.Group> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -HI '^package\.json$' packages/ui --max-depth 1 --exec cat {}
echo "---"
rg -n '"exports"' -A 40 packages/ui/package.jsonRepository: clerk/javascript
Length of output: 7002
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- story context ---'
sed -n '1,75p' packages/swingset/src/stories/scroll-area.mdx
printf '%s\n' '--- mosaic package and build references ---'
rg -n --hidden -g '!node_modules' -g '!dist' \
'mosaic/components/scroll-area|scrollAreaRoot|scrollAreaViewport|build:mosaic|dist-mosaic' \
packages/ui packages/swingset pnpm-workspace.yaml package.json
printf '%s\n' '--- exact exports keys ---'
python3 - <<'PY'
import json
from pathlib import Path
p = Path("packages/ui/package.json")
data = json.loads(p.read_text())
exports = data.get("exports", {})
print("mosaic/components/scroll-area" in exports)
print([k for k in exports if "mosaic" in k])
PYRepository: clerk/javascript
Length of output: 8752
Document the import as internal or expose the subpath.
@clerk/ui/package.json does not export ./mosaic/components/scroll-area. Consumers cannot resolve the import shown in this usage example. Mark the API as internal/unreleased, or add the subpath to the exports map.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/scroll-area.mdx` around lines 22 - 30, Resolve
the unavailable scroll-area import used in the example by either marking this
API as internal/unreleased in the documentation or exposing
./mosaic/components/scroll-area through the `@clerk/ui` package exports map.
Update the relevant documentation or package export configuration while
preserving the existing scrollAreaRoot and scrollAreaViewport usage.
| export const meta: StoryMeta = { | ||
| group: 'Styles', | ||
| title: 'Scroll Area', | ||
| source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts', | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Set styleEngine to 'stylex'.
StoryMeta defaults to 'emotion'. This story documents scrollAreaRoot and scrollAreaViewport, which are StyleX APIs. The viewer can otherwise render Emotion-specific API rows.
Proposed fix
export const meta: StoryMeta = {
group: 'Styles',
title: 'Scroll Area',
source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts',
+ styleEngine: 'stylex',
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const meta: StoryMeta = { | |
| group: 'Styles', | |
| title: 'Scroll Area', | |
| source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts', | |
| }; | |
| export const meta: StoryMeta = { | |
| group: 'Styles', | |
| title: 'Scroll Area', | |
| source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts', | |
| styleEngine: 'stylex', | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/swingset/src/stories/scroll-area.stories.tsx` around lines 16 - 20,
Update the exported StoryMeta configuration for the Scroll Area story to set
styleEngine to 'stylex', ensuring the documented scrollAreaRoot and
scrollAreaViewport APIs are rendered as StyleX APIs.
Description
https://swingset-git-max-scroll-area-scrollbar-enhancements.clerkstage.dev/styles/scroll-area
Adds the Mosaic scroll area: a scrolling region that fades its content at whichever
edge still has something to reveal, and paints a matching scrollbar. It ships as
StyleX atoms —
scrollAreaViewport(gutter?)andscrollAreaRoot— rather than acomponent, since everything it does is CSS.
The fade is a mask driven by scroll-driven animations: no scroll listener, no
measurement, and paint-only, so it cannot shift content. The scrollbar is painted
through
::-webkit-scrollbar, which buys a real pixel width and a thumb colour perinteraction state. Both are tuned by global tokens (
--cl-scroll-fade-*,--cl-scrollbar-*) that retune every scrolling surface at once, and the fade can beretired for a shadow or any other indicator by reading the two progress vars.
Internal for now, hence the empty changeset:
./mosaicis not in theexportsmapand no shipped component applies the atoms, so nothing here is reachable from a
published entry point.
Swingset gains a
Stylessection whose Scroll Area page exercises every case.Note
with this all being atomic stylex classes, it will be tougher for a user to target all scroll areas outside the theming variables. so something like change all scroll indicators to be shadows instead of a mask would be tough. worth considering adding some sort of stable class name to scrollers for ease of user css declarations and customization
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change