feat(ui): Introduce mosaic recipes and conditions#8830
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces CVA-based styling with a slot-recipe system: slot registry and appearance parsing, condition expansion, slot resolution, defineSlotRecipe/useRecipe and useSlot, MosaicProvider now accepts appearance+scope, migrates Button/Input/Dialog to recipes, removes headless data-cl-slot emission, updates swingset/docs/storybook, and adds tests and docs. ChangesMosaic Slot Recipes Refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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 |
API Changes Report
Summary
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/expo
@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: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/ui/src/mosaic/slot-recipe.ts (1)
269-311: ⚡ Quick winConsider extracting the
kebabutility to a shared module.The
kebabhelper at line 310 is duplicated inuseSlot.tsline 52. Extracting it to a shared utility module would reduce duplication and ensure consistency.🤖 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/slot-recipe.ts` around lines 269 - 311, Extract the local kebab function into a shared utility module (e.g., export function kebab(value: string): string) and replace the duplicated implementations by importing that exported kebab in both slot-recipe.ts (used by variantsToAttrs and stateToAttrs) and useSlot.ts; update the files to remove the local kebab definitions, add the import, and ensure the shared util is exported for reuse and covered by existing tests/typings.
🤖 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/mosaic-slot-recipes.md:
- Around line 1-2: The empty changeset file .changeset/mosaic-slot-recipes.md
must be populated with a proper changeset: add a YAML frontmatter listing the
package "`@clerk/ui`" and set the bump type to "minor", and include a concise
summary explaining the mosaic slot recipes feature and the new public APIs
(e.g., defineSlotRecipe, useRecipe, MosaicProvider enhancements) so release
tooling and consumers understand the change.
In `@packages/ui/src/mosaic/appearance.ts`:
- Around line 63-88: Add a JSDoc block above the exported function
parseMosaicAppearance describing its purpose (parses a MosaicAppearance.elements
object into global+scoped layers), document the parameters (appearance?:
MosaicAppearance, scope?: string) with types and meanings, document the return
type (ParsedMosaicElements) and behavior (global keys collected first, then
scoped keys if scope matches SCOPE_KEY_SET), and include a short example showing
input appearance and the resulting array (as in the suggested snippet); ensure
the JSDoc is on the exported function so Clerk Docs will pick it up and mention
usage by MosaicProvider.
---
Nitpick comments:
In `@packages/ui/src/mosaic/slot-recipe.ts`:
- Around line 269-311: Extract the local kebab function into a shared utility
module (e.g., export function kebab(value: string): string) and replace the
duplicated implementations by importing that exported kebab in both
slot-recipe.ts (used by variantsToAttrs and stateToAttrs) and useSlot.ts; update
the files to remove the local kebab definitions, add the import, and ensure the
shared util is exported for reuse and covered by existing tests/typings.
🪄 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), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 2013d262-0780-4240-b47e-2a8106383146
📒 Files selected for processing (23)
.changeset/mosaic-slot-recipes.mdpackages/swingset/src/components/StoryPreview.tsxpackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/input.stories.tsxpackages/ui/src/mosaic/MosaicProvider.tsxpackages/ui/src/mosaic/__tests__/MosaicProvider.test.tsxpackages/ui/src/mosaic/__tests__/conditions.test.tspackages/ui/src/mosaic/__tests__/cva.test.tspackages/ui/src/mosaic/__tests__/resolveSlot.test.tspackages/ui/src/mosaic/__tests__/slot-recipe.test.tspackages/ui/src/mosaic/__tests__/utils.test.tspackages/ui/src/mosaic/appearance.tspackages/ui/src/mosaic/components/__tests__/button.test.tsxpackages/ui/src/mosaic/components/button.tsxpackages/ui/src/mosaic/components/input.tsxpackages/ui/src/mosaic/conditions.tspackages/ui/src/mosaic/cva.tspackages/ui/src/mosaic/registry.tspackages/ui/src/mosaic/resolveSlot.tspackages/ui/src/mosaic/slot-recipe.tspackages/ui/src/mosaic/useSlot.tspackages/ui/src/mosaic/utils.tsreferences/mosaic-architecture.md
💤 Files with no reviewable changes (3)
- packages/ui/src/mosaic/tests/cva.test.ts
- packages/ui/src/mosaic/utils.ts
- packages/ui/src/mosaic/cva.ts
🦋 Changeset detectedLatest commit: d46e35c 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/ui/src/mosaic/MosaicProvider.tsx`:
- Around line 47-56: The useInsertionEffect in MosaicProvider currently runs on
every render because it has no dependency array; update it to only re-run when
its inputs change (e.g., include cssLayerName and cache.key in the dependency
array) so you don't repeatedly re-scan and wrap all
style[data-emotion^="${cache.key}"] tags each render, while retaining the
idempotent `@layer` check; if you intentionally need per-render execution, add a
concise comment in MosaicProvider explaining why it must run every render
despite the cost and referencing useInsertionEffect, cssLayerName, and
cache.key.
🪄 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), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 5b0b5aad-c7ff-4050-bfd8-1e8f526decfb
📒 Files selected for processing (3)
packages/swingset/src/components/StoryPreview.tsxpackages/ui/src/mosaic/MosaicProvider.tsxpackages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ui/src/mosaic/tests/MosaicProvider.test.tsx
Replaces the emotionCache.insert monkey-patch with a useInsertionEffect that rewrites <style> textContent after emotion injects but before paint, avoiding the need to intercept the internal insert API.
Slot identity now lives in the mosaic styled layer. The dialog primitives keep their runtime state attrs (data-cl-open, data-cl-starting-style, …) but no longer emit data-cl-slot; tests locate parts via role/text/testid.
Replace the cva/styled Dialog with a multi-slot dialogRecipe that owns slot identity and base styles. Each part reads its slot via useRecipe and spreads it onto the bridged headless primitive through the new withMosaicSlot bridge. Removes the obsolete styled/types/withMosaicTheme cva helpers.
The insertion effect rewrote emotion's <style> textContent to wrap rules in @layer, which desynced from emotion's rule bookkeeping and dropped component styles on client navigation. Reverting to normal emotion insertion; the @layer/cssLayerName ordering will be revisited separately.
Adds a styled mosaic Dialog story (Components) that uses a Button as the trigger via the render prop. Routes are now group-aware (/components/<c> and /primitives/<c>) so the styled Dialog and the headless Dialog primitive no longer collide on a title-only slug.
400c9c2 to
157fec5
Compare
| if (cssLayerName) { | ||
| const prevInsert = emotionCache.insert.bind(emotionCache); | ||
| emotionCache.insert = (selector: string, serialized: SerializedStyles, sheet: any, shouldCache: boolean) => { | ||
| if (serialized && typeof serialized.styles === 'string') { | ||
| const newSerialized = { ...serialized }; | ||
| newSerialized.styles = `@layer ${cssLayerName} {${serialized.styles}}`; | ||
| return prevInsert(selector, newSerialized, sheet, shouldCache); | ||
| } | ||
| return prevInsert(selector, serialized, sheet, shouldCache); | ||
| }; | ||
| } |
There was a problem hiding this comment.
removed for now, need to figure out a more reliable solution for this feature that supports SSR.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/dialog.component.stories.tsx`:
- Around line 14-19: Change the story function Default to accept a single
parameter of type Record<string, unknown> (e.g., Default(args: Record<string,
unknown>)) and cast that generic args object to the concrete prop types used in
the story before passing them through; locate the Default function and update
how props are forwarded into Dialog.Trigger's render and the Button (cast args
to the proper HTMLAttributes/prop types) so the story follows the shared
knobs-to-props contract.
In `@packages/ui/src/mosaic/MosaicProvider.tsx`:
- Around line 25-33: The provider removed the standalone variables prop and now
only reads appearance?.variables, which breaks backward compatibility; update
MosaicProviderProps to reintroduce variables (e.g., variables?:
Partial<MosaicVariables>) and change the memo that builds theme to merge/precede
props.variables with appearance?.variables (e.g.,
resolveVariables(defaultMosaicVariables, variables ?? appearance?.variables) or
otherwise merge so existing consumers using the variables prop keep their
overrides), leaving parseMosaicAppearance(scope, appearance) unchanged;
reference MosaicProvider, MosaicProviderProps, appearance, variables,
resolveVariables, parseMosaicAppearance, and defaultMosaicVariables when making
the change.
In `@references/mosaic-architecture.md`:
- Line 263: Update the sentence at line 263 to distinguish between two cases:
(1) when a variant map entry is null — explain that pickSlot converts falsy
entries (null → undefined) and mergeInto only merges truthy sources so those
variant-map entries result in no styles; and (2) when a variant prop is
explicitly passed as null — explain that it becomes the string key "null" for
lookup so styles only apply if the recipe defines a "null" variant. Reference
the existing terms pickSlot, mergeInto, variant map, variant prop, and recipe in
the revised wording to make the distinction clear.
🪄 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), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: a7499a18-dc14-484f-9dfc-07d4ac22bb15
📒 Files selected for processing (44)
.changeset/mosaic-slot-recipes.mdpackages/headless/src/primitives/dialog/README.mdpackages/headless/src/primitives/dialog/dialog-backdrop.tsxpackages/headless/src/primitives/dialog/dialog-close.tsxpackages/headless/src/primitives/dialog/dialog-description.tsxpackages/headless/src/primitives/dialog/dialog-popup.tsxpackages/headless/src/primitives/dialog/dialog-title.tsxpackages/headless/src/primitives/dialog/dialog-trigger.tsxpackages/headless/src/primitives/dialog/dialog-viewport.tsxpackages/headless/src/primitives/dialog/dialog.test.tsxpackages/swingset/src/app/[group]/[component]/page.tsxpackages/swingset/src/app/components/[component]/page.tsxpackages/swingset/src/components/ClientRoot.tsxpackages/swingset/src/components/DocsViewer.tsxpackages/swingset/src/components/StoryPreview.tsxpackages/swingset/src/components/app-sidebar.tsxpackages/swingset/src/components/ui/sidebar.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/dialog.component.mdxpackages/swingset/src/stories/dialog.component.stories.tsxpackages/swingset/src/stories/input.stories.tsxpackages/ui/src/mosaic/MosaicProvider.tsxpackages/ui/src/mosaic/__tests__/MosaicProvider.test.tsxpackages/ui/src/mosaic/__tests__/conditions.test.tspackages/ui/src/mosaic/__tests__/cva.test.tspackages/ui/src/mosaic/__tests__/resolveSlot.test.tspackages/ui/src/mosaic/__tests__/slot-recipe.test.tspackages/ui/src/mosaic/__tests__/utils.test.tspackages/ui/src/mosaic/appearance.tspackages/ui/src/mosaic/components/__tests__/button.test.tsxpackages/ui/src/mosaic/components/button.tsxpackages/ui/src/mosaic/components/dialog.tsxpackages/ui/src/mosaic/components/input.tsxpackages/ui/src/mosaic/conditions.tspackages/ui/src/mosaic/cva.tspackages/ui/src/mosaic/primitives/dialog.tsxpackages/ui/src/mosaic/primitives/withMosaicSlot.tsxpackages/ui/src/mosaic/registry.tspackages/ui/src/mosaic/resolveSlot.tspackages/ui/src/mosaic/slot-recipe.tspackages/ui/src/mosaic/useSlot.tspackages/ui/src/mosaic/utils.tsreferences/mosaic-architecture.md
💤 Files with no reviewable changes (4)
- packages/swingset/src/app/components/[component]/page.tsx
- packages/ui/src/mosaic/cva.ts
- packages/ui/src/mosaic/tests/cva.test.ts
- packages/ui/src/mosaic/utils.ts
✅ Files skipped from review due to trivial changes (2)
- packages/swingset/src/stories/dialog.component.mdx
- .changeset/mosaic-slot-recipes.md
🚧 Files skipped from review as they are similar to previous changes (15)
- packages/ui/src/mosaic/tests/conditions.test.ts
- packages/ui/src/mosaic/resolveSlot.ts
- packages/swingset/src/stories/button.stories.tsx
- packages/ui/src/mosaic/tests/utils.test.ts
- packages/ui/src/mosaic/appearance.ts
- packages/swingset/src/components/StoryPreview.tsx
- packages/ui/src/mosaic/components/tests/button.test.tsx
- packages/ui/src/mosaic/registry.ts
- packages/ui/src/mosaic/tests/MosaicProvider.test.tsx
- packages/ui/src/mosaic/components/input.tsx
- packages/ui/src/mosaic/useSlot.ts
- packages/ui/src/mosaic/tests/slot-recipe.test.ts
- packages/ui/src/mosaic/components/button.tsx
- packages/ui/src/mosaic/conditions.ts
- packages/ui/src/mosaic/slot-recipe.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx (2)
44-46: 💤 Low valueConsider using JSX for the wrapper instead of React.createElement.
The
React.createElementcalls work correctly but are verbose. JSX would be more idiomatic and readable in test wrappers:wrapper: ({ children }) => <MosaicProvider appearance={appearance} scope="signIn">{children}</MosaicProvider>This pattern applies to lines 60 and 67 as well.
🤖 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/__tests__/MosaicProvider.test.tsx` around lines 44 - 46, Replace the verbose React.createElement usage in the test hook wrappers with JSX to improve readability: update the wrapper function passed to renderHook that currently uses React.createElement(MosaicProvider, { appearance, scope: 'signIn' }, children) (and the other two similar instances) to return JSX for MosaicProvider with props appearance and scope and the children inside; target the useMosaicAppearance test wrappers and any other wrapper lambdas referencing MosaicProvider to make this change.
56-71: ⚡ Quick winAdd a test for the
useMosaicThemeerror case.According to the implementation in
MosaicProvider.tsx,useMosaicThemethrows an error when used outside aMosaicProvider. This error handling is part of the public contract and should be verified:🧪 Suggested test case
it('throws when useMosaicTheme is used outside MosaicProvider', () => { expect(() => { renderHook(() => useMosaicTheme()); }).toThrow('useMosaicTheme must be used within a MosaicProvider'); });🤖 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/__tests__/MosaicProvider.test.tsx` around lines 56 - 71, Add a test to MosaicProvider.test.tsx that verifies useMosaicTheme throws when called outside a MosaicProvider: create an it() case that calls renderHook(() => useMosaicTheme()) with no wrapper and assert it throws with the exact message 'useMosaicTheme must be used within a MosaicProvider' (use expect(() => renderHook(...)).toThrow(...)); reference the useMosaicTheme hook and MosaicProvider to locate where to add the test.
🤖 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.
Nitpick comments:
In `@packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx`:
- Around line 44-46: Replace the verbose React.createElement usage in the test
hook wrappers with JSX to improve readability: update the wrapper function
passed to renderHook that currently uses React.createElement(MosaicProvider, {
appearance, scope: 'signIn' }, children) (and the other two similar instances)
to return JSX for MosaicProvider with props appearance and scope and the
children inside; target the useMosaicAppearance test wrappers and any other
wrapper lambdas referencing MosaicProvider to make this change.
- Around line 56-71: Add a test to MosaicProvider.test.tsx that verifies
useMosaicTheme throws when called outside a MosaicProvider: create an it() case
that calls renderHook(() => useMosaicTheme()) with no wrapper and assert it
throws with the exact message 'useMosaicTheme must be used within a
MosaicProvider' (use expect(() => renderHook(...)).toThrow(...)); reference the
useMosaicTheme hook and MosaicProvider to locate where to add the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: b903ac2f-a067-4bfd-b12b-aba9e19123b1
📒 Files selected for processing (1)
packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx
| return ( | ||
| <Dialog.Root> | ||
| <Dialog.Trigger | ||
| render={(props: Omit<HTMLAttributes<HTMLElement>, 'color'>) => <Button {...props}>Open dialog</Button>} |
There was a problem hiding this comment.
I think we need to be strict about prop variants names not being dom attributes to avoid this dance. noted to tackle in future work.
Description
Mosaic styling system: slot recipes + data-attribute contract +
appearancecascade. Replacescva.Public contract — data attributes
data-cl-slot— slot iddata-cl-<state>— presence-only, omitted when falsedata-cl-<variant>— resolved variant (defaults emitted too, sosmvsmdtargetable)Style 3 ways, same attrs
Authoring —
defineSlotReciperoot={ 'data-cl-slot', 'data-cl-size', 'data-cl-disabled'?, css }.cssmerged base → variants → compound → sx → appearance.Multi-slot + sugar
appearance— variables global, elements scopableConditions
_hover→@media (hover: hover) { &:hover },_disabled→&[data-cl-disabled],_focusVisible,_invalid, … Work in recipes /sx/appearance.elements. Merge by key (consumer wins), expand once at the end.Misc
StyleRule= csstypeCSSObject→ CSS-prop autocomplete inelements/sx/recipes.cva.ts/cva.test.ts(engine absorbed intoslot-recipe.ts).references/mosaic-architecture.md.Test
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation
Chores