diff --git a/.changeset/tall-pans-tickle.md b/.changeset/tall-pans-tickle.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/tall-pans-tickle.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/mosaic/SKILL.md b/.claude/skills/mosaic/SKILL.md index 80165faf255..523a341bc07 100644 --- a/.claude/skills/mosaic/SKILL.md +++ b/.claude/skills/mosaic/SKILL.md @@ -1,8 +1,8 @@ --- name: mosaic description: >- - Work on Mosaic UI: styling a component with slot recipes (`defineSlotRecipe` / - `useRecipe` / slots / variants), or building a flow — authoring a state machine + Work on Mosaic UI: styling a component with StyleX (`stylex.create`, `--cl-*` + tokens, `themeProps`), or building a flow — authoring a state machine (`setup`, states/guards/`invoke`, wiring to React with `useMachine`/`useActor`/ `useSelector`), writing the controller (Clerk adapter) or view (rendering) layer, testing any of those layers, or migrating a legacy / pre-Mosaic component into the @@ -15,9 +15,10 @@ description: >- Two things live under Mosaic, and this skill covers the how-to for both: -- **Styled components** are authored with **slot recipes** — one recipe owns a - part's slot identity (`data-cl-slot`), variants, state, and appearance - cascade; `useRecipe` resolves it and hands back per-slot props to spread. +- **Styled components** are authored with **StyleX** — `stylex.create` declares + the styles, `themeProps` emits the part's public identity (the `.cl-` + class plus `data-` attrs), and `mergeStyleProps` fuses the two with the + consumer's `className`/`style`. - **Flows** follow a **machine → controller → view** split that keeps Clerk resource logic out of visual components and makes behavior testable without a running Clerk app: @@ -35,25 +36,24 @@ view Rendering only: receives a snapshot plus explicit props, renders UI, ``` `references/mosaic-architecture.md` (repo root, read by all agents) is the -canonical contract for the whole design system — tokens, theme delivery, the -`data-cl-*` styling API, slot recipes, appearance/cascade/scope, and the "Flow -and data architecture" section that defines the split. Read it for the _what_; -this skill is the _how-to_. +canonical contract for the whole design system — the `--cl-*` tokens, the +`.cl-` + `data-` styling API, the CSS build, and the "Flow and data +architecture" section that defines the split. Read it for the _what_; this skill +is the _how-to_. ## Which reference to read -| You are… | Read | -| -------------------------------------------------------------------- | ------------------------------------------------------ | -| Building on / authoring a headless primitive (`@clerk/headless`) | `references/headless.md` | -| Styling a component with StyleX (tokens, `stylex.create`, CSS build) | `references/stylex.md` | -| Building an enter/exit transition, or any motion that reads as wrong | `references/motion.md` | -| Styling a component the legacy way (slot recipes, `useRecipe`) | `references/styling.md` | -| Authoring or debugging a state machine, or wiring one to React | `references/machines.md` → in-tree `machine/README.md` | -| Writing the controller (Clerk adapter, permissions, revalidate) | `references/controllers.md` | -| Writing the view (rendering a snapshot, sending events) | `references/views.md` | -| Testing a machine, controller, or view | `references/testing.md` | -| Migrating a legacy component into Mosaic (the end-to-end workflow) | `references/migration.md` | -| Running the parity audit that guards a migration | `references/parity-audit.md` | +| You are… | Read | +| ---------------------------------------------------------------------- | ------------------------------------------------------ | +| Building on / authoring a headless primitive (`@clerk/headless`) | `references/headless.md` | +| Styling a component (tokens, `stylex.create`, `themeProps`, CSS build) | `references/stylex.md` | +| Building an enter/exit transition, or any motion that reads as wrong | `references/motion.md` | +| Authoring or debugging a state machine, or wiring one to React | `references/machines.md` → in-tree `machine/README.md` | +| Writing the controller (Clerk adapter, permissions, revalidate) | `references/controllers.md` | +| Writing the view (rendering a snapshot, sending events) | `references/views.md` | +| Testing a machine, controller, or view | `references/testing.md` | +| Migrating a legacy component into Mosaic (the end-to-end workflow) | `references/migration.md` | +| Running the parity audit that guards a migration | `references/parity-audit.md` | The migration workflow (`migration.md`) ties the flow references together: it treats the legacy component as the spec and drives you through the machine, diff --git a/.claude/skills/mosaic/references/stylex.md b/.claude/skills/mosaic/references/stylex.md index 38f86642269..995365d31dc 100644 --- a/.claude/skills/mosaic/references/stylex.md +++ b/.claude/skills/mosaic/references/stylex.md @@ -1,14 +1,13 @@ # Styling a component with StyleX -Mosaic styling is migrating off the Emotion slot-recipe engine (`styling.md`) -onto **StyleX** (`@stylexjs/stylex` 0.19). StyleX is compile-time atomic CSS: the -style objects become hashed atom classes plus one static stylesheet, with zero -runtime. This file is the authoring model for the StyleX layer; read it against -the pilot, `packages/ui/src/mosaic/components/button/`. - -The public contract is unchanged from the recipe era — consumers still target -`--cl-*` vars, the `.cl-` class, and `data-` attrs, never StyleX's -hashed `x…` atoms. What changes is how a component is authored internally. +Mosaic is styled with **StyleX** (`@stylexjs/stylex` 0.19). StyleX is +compile-time atomic CSS: the style objects become hashed atom classes plus one +static stylesheet, with zero runtime. This file is the authoring model; read it +against the reference component, `packages/ui/src/mosaic/components/button/`. + +Consumers target `--cl-*` vars, the `.cl-` class, and `data-` attrs, +never StyleX's hashed `x…` atoms. That public contract is fixed; everything +below is how a component is authored internally to produce it. The guidance below is opinionated and evidence-backed: it codifies patterns proven out across a full StyleX component library (tokens, ~100 components, diff --git a/.claude/skills/mosaic/references/styling.md b/.claude/skills/mosaic/references/styling.md deleted file mode 100644 index 8658a015689..00000000000 --- a/.claude/skills/mosaic/references/styling.md +++ /dev/null @@ -1,197 +0,0 @@ -# Styling a component with slot recipes - -> **Migration in progress:** Mosaic styling is moving off this Emotion slot-recipe -> engine onto **StyleX** (compile-time atomic CSS). New/migrated components are -> authored with StyleX — see **`references/stylex.md`**. This file documents the -> recipe system that StyleX is replacing; both coexist until the migration lands. -> The public `--cl-*` / `.cl-` / `data-` contract is identical across -> both, so it is preserved regardless of the internal engine. - -A styled Mosaic component is authored with one **slot recipe**. The recipe owns -everything about how the part looks and is targeted: its slot identity -(`data-cl-slot`), base styles, variants, and the appearance cascade. -`useRecipe(recipe, opts)` resolves it against the active theme + appearance and -hands back **per-slot props** — `css` already merged and every `data-cl-*` -attribute attached — that you spread onto the element. You never hand-thread -`css={[...]}`. - -`references/mosaic-architecture.md` (repo root) is the full contract: the -appearance cascade + scope, the token architecture (`theme.spacing`/`alpha`/ -`mix`/`text`), the condition vocabulary (`_hover`, `_disabled`, …), and the -`data-cl-*` public styling API. This file is the authoring how-to; read it -alongside two real components: - -- `packages/ui/src/mosaic/components/button.tsx` — single-slot, full variants. -- `packages/ui/src/mosaic/components/tabs.tsx` — multi-slot, bridged onto a - headless primitive. - ---- - -## Single-slot (the common case) - -`slot: 'button'` is shorthand for one implicit `root` slot. Define `base`, -`variants`, `compoundVariants`, and `defaultVariants`; register the slot id; -infer the props from the recipe; then destructure variants + state and spread the -resolved `root` props. - -```tsx -import React from 'react'; -import { defineSlotRecipe, useRecipe, type RecipeVariantProps } from '../slot-recipe'; - -export const buttonRecipe = defineSlotRecipe(theme => ({ - slot: 'button', - base: { - display: 'inline-flex', - borderRadius: theme.rounded.md, - ...theme.text('sm'), - _focusVisible: { outline: `2px solid ${theme.alpha('primary', 50)}` }, - _disabled: { opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' }, - }, - variants: { - intent: { primary: {}, destructive: {} }, - variant: { filled: {}, outline: {}, ghost: {} }, - size: { sm: { ...theme.text('xs') }, md: { ...theme.text('sm') } }, - fullWidth: { true: { width: '100%' }, false: {} }, - }, - compoundVariants: [ - { - intent: 'primary', - variant: 'filled', - css: { - backgroundColor: theme.color.primary, - color: theme.color.primaryForeground, - _hover: { backgroundColor: theme.mix('primary', 'primaryForeground', 12) }, - }, - }, - ], - defaultVariants: { intent: 'primary', variant: 'filled', size: 'md', fullWidth: false }, -})); - -// Register the slot id — this is what makes `button` autocomplete in appearance.elements. -declare module '../registry' { - interface MosaicSlotRegistry { - button: true; - } -} - -// Infer variant props (+ sx) from the recipe — don't re-declare them by hand. -export type ButtonProps = React.ComponentPropsWithRef<'button'> & RecipeVariantProps; - -export const Button = React.forwardRef(function MosaicButton(props, ref) { - const { intent, variant, size, fullWidth, disabled, sx, children, ...rest } = props; - const { root } = useRecipe(buttonRecipe, { - variants: { intent, variant, size, fullWidth }, - state: { disabled: !!disabled }, - sx, - }); - return ( - - ); -}); -``` - -Key moves: - -- **`variants` vs `state`.** A visual axis chosen by the caller (`intent`, - `size`) is a **variant**. A runtime condition (`disabled`, `selected`, - `invalid`) is **state** — pass it under `state:` so it emits `data-cl-disabled` - and is styled via the `_disabled` condition, which keeps it overridable through - `appearance.elements`. Don't model `disabled` as a boolean variant. -- **`compoundVariants`** style a combination (`intent: 'primary'` **and** - `variant: 'filled'`) that no single axis owns. -- **`sx`** is the per-instance escape hatch; it merges last, before appearance. -- **`RecipeVariantProps`** derives `{ intent?, variant?, size?, -fullWidth?, sx? }` — the recipe is the single source of the prop types. - -## Multi-slot - -One recipe owns several parts under `slots`, each mapping a key to a public -`data-cl-slot` id (kebab-case). `base` is keyed by slot. Each rendered part calls -`useRecipe(recipe)` and spreads its own slot. `tabs.tsx` bridges the resolved -slots onto headless primitives, so slot identity lives in this styled layer: - -```tsx -export const tabsRecipe = defineSlotRecipe(theme => ({ - slots: { - list: { slot: 'tabs-list' }, - tab: { slot: 'tabs-tab' }, - panel: { slot: 'tabs-panel' }, - indicator: { slot: 'tabs-indicator' }, - }, - base: { - list: { display: 'flex', gap: theme.spacing(4), borderBottom: `1px solid ${theme.alpha('primary', 10)}` }, - tab: { - ...theme.text('sm'), - color: theme.color.mutedForeground, - '&[data-cl-selected]': { color: theme.color.primary }, - '&[data-cl-disabled]': { opacity: 0.5, cursor: 'not-allowed' }, - }, - panel: { '&[data-cl-hidden]': { display: 'none' } }, - indicator: { height: '2px', backgroundColor: theme.color.primary }, - }, -})); - -declare module '../registry' { - interface MosaicSlotRegistry { - 'tabs-list': true; - 'tabs-tab': true; - 'tabs-panel': true; - 'tabs-indicator': true; - } -} - -const List = React.forwardRef>( - function TabsList(props, ref) { - const { list } = useRecipe(tabsRecipe); - return ( - - ); - }, -); -// …Tab / Panel / Indicator each read their own slot from useRecipe(tabsRecipe)… -``` - -Note how `tabs` targets state with **raw attribute selectors** -(`'&[data-cl-selected]'`) because those states come from the headless primitive -rather than a `useRecipe({ state })` call. When your own component owns the -state, prefer `state: { … }` + the condition keys (`_disabled`, `_hover`) so it -stays overridable through `appearance.elements`; drop to raw `&[data-cl-*]` -selectors only for states applied by something else. - -## Lighter sugar: `useSlot` / `slot` - -Not every part needs variants. `slot-recipe.ts` is the heavy end of one spectrum -(`useSlot.ts`): - -- **`useSlot('avatarBox', { state })`** — themeable + targetable + appearance-aware, - but no recipe. `css` is just `sx` + appearance. -- **`slot('badgeText')`** — the barest: returns only `{ 'data-cl-slot': … }` to - make an element targetable. No hook, no css. - -Reach for a full recipe only when you have variants; otherwise use `useSlot` or -`slot`. See `references/mosaic-architecture.md` → "Sugar — useSlot / slot" for -the details. - -## Checklist - -- Recipe with `defineSlotRecipe(theme => …)`; `slot:` for one part, `slots:` for - many. -- `declare module '../registry'` block registering every slot id (co-located - with the recipe) so it autocompletes in `appearance.elements`. -- Caller-chosen axes → `variants`; runtime conditions → `state` + condition keys. -- `RecipeVariantProps` for the prop type; destructure variants + - state, spread the resolved slot(s). -- No `css={t => …}` function form in Mosaic (Emotion would type `t` as - `InternalTheme`); use the recipe's `theme => config` or `useMosaicTheme()`. diff --git a/.claude/skills/mosaic/references/testing.md b/.claude/skills/mosaic/references/testing.md index e7ef866fff2..f7143d66eac 100644 --- a/.claude/skills/mosaic/references/testing.md +++ b/.claude/skills/mosaic/references/testing.md @@ -132,8 +132,9 @@ gate from `checkAuthorization`, `loading` until Clerk is loaded, and that Build a plain `snapshot` object and pass a `vi.fn()` `send`. Assert what renders per `snapshot.value` and that interactions send the right event. **Wrap the view -in ``** — it's not a Clerk provider; it supplies the theme -context that `useRecipe`/`useSlot` read. No Clerk providers or fixtures. +in ``** — it's not a Clerk provider; it supplies the icon-override +context, and wrapping keeps the test tree matching production. No Clerk providers +or fixtures. ```tsx import { fireEvent, render, screen } from '@testing-library/react'; diff --git a/packages/headless/src/primitives/dialog/dialog.test.tsx b/packages/headless/src/primitives/dialog/dialog.test.tsx index d0513bbbe43..98441eae9c6 100644 --- a/packages/headless/src/primitives/dialog/dialog.test.tsx +++ b/packages/headless/src/primitives/dialog/dialog.test.tsx @@ -8,9 +8,9 @@ import { Dialog } from './index'; afterEach(() => cleanup()); -// Headless parts no longer emit `data-cl-slot` — slot identity is applied by the styled -// (mosaic) layer. Tests locate the surface-only parts (backdrop, viewport, trigger) via -// `data-testid` and everything else via its accessible role or text. +// Headless parts carry no slot identity — that is applied by the styled (mosaic) layer. +// Tests locate the surface-only parts (backdrop, viewport, trigger) via `data-testid` and +// everything else via its accessible role or text. function renderDialog(props: Partial> = {}) { return render( diff --git a/packages/swingset/CLAUDE.md b/packages/swingset/CLAUDE.md index 177d3f235ce..ddd0765758f 100644 --- a/packages/swingset/CLAUDE.md +++ b/packages/swingset/CLAUDE.md @@ -22,27 +22,25 @@ These require reading several files together; the `README.md` covers the step-by - **Consumes Mosaic from source, not build.** `@clerk/ui/mosaic` is aliased to `../ui/src/mosaic` in *two* places that must stay in sync: `next.config.mjs` (webpack `resolve.alias`) and `tsconfig.json` (`paths`). Editing Mosaic source in `packages/ui` reflects live in swingset's dev server — no rebuild of `@clerk/ui` needed. -- **Knobs are generated from CVA metadata, not hand-written.** A story's `meta.styles` is a Mosaic CVA style object exposing `_variants` / `_defaultVariants`. `lib/generateKnobs.ts` turns each variant into a control: variants whose keys are only `true`/`false` become boolean toggles, everything else becomes a select. Knob values are passed as props straight into the story component. This is why story functions take `Record` and cast to the real prop type. +- **Knobs are generated from a story's declared variant surface.** A story's `meta.styles` is a hand-written `{ _variants, _defaultVariants }` object describing the component's variant props — StyleX compiles its styles away, so there is no runtime recipe to derive this from. `lib/generateKnobs.ts` turns each variant into a control: variants whose keys are only `true`/`false` become boolean toggles, everything else becomes a select. Knob values are passed as props straight into the story component. This is why story functions take `Record` and cast to the real prop type. - **`lib/registry.ts` is the single source of truth for which components exist**, and they are imported *explicitly* (never `import *`) so sidebar order is deterministic. `getSidebarGroups`, `getModuleBySlug`, and slugging (`lib/slug.ts`, from `meta.title`) read from it. Adding a component touches up to three wiring points: `registry.ts` (sidebar entry + per-page playground lookup), `DocsViewer.tsx`'s `docModules` map (MDX docs), and the hardcoded redirect in `app/page.tsx`. - ⚠️ **Add each new import and its first usage in the same edit.** The on-save lint-fix (`unused-imports/no-unused-imports` is an `error`) deletes any import that isn't referenced yet, so importing a story export in `registry.ts` (or a component in a `*.stories.tsx`) *before* the code that uses it silently drops the import and you get `X is not defined` at runtime. After wiring, `grep` the new symbol to confirm both the import and its use survived. (Repo-wide footgun; see `clerk-monorepo` skill `references/setup-and-footguns.md`.) -- **Routing.** Each component is a single page: `/components/[component]` renders its MDX overview via `DocsViewer`. There are no per-story sub-pages — the interactive playground lives *inside* the overview. `app/page.tsx` is a static redirect (currently to `/components/button`) because `registry.ts` eagerly imports story modules (Emotion / `createContext`), so registry-derived data can't be computed in a Server Component. `DocsViewer` also renders a "View source" link (`ViewSource.tsx`) from `meta.source` — a repo-root-relative path turned into a GitHub URL by `lib/source.ts`. +- **Routing.** Each component is a single page: `/components/[component]` renders its MDX overview via `DocsViewer`. There are no per-story sub-pages — the interactive playground lives *inside* the overview. `app/page.tsx` is a static redirect (currently to `/components/button`) because `registry.ts` eagerly imports story modules (client components / `createContext`), so registry-derived data can't be computed in a Server Component. `DocsViewer` also renders a "View source" link (`ViewSource.tsx`) from `meta.source` — a repo-root-relative path turned into a GitHub URL by `lib/source.ts`. -- **Shared playground state.** `DocsViewer` wraps each overview in a `PlaygroundProvider` (`PlaygroundContext.tsx`), keyed by slug and seeded from the component's `meta` via `getModuleBySlug`. It owns the knob values (props) and live `MosaicVariables`. The `` and the interactive `` both read/write this single context, so editing a prop in the table updates the preview above it. +- **Shared playground state.** `DocsViewer` wraps each overview in a `PlaygroundProvider` (`PlaygroundContext.tsx`), keyed by slug and seeded from the component's `meta` via `getModuleBySlug`. It owns the knob values (props). The `` and the interactive `` both read/write this single context, so editing a prop in the table updates the preview above it. -- **Every story renders inside `MosaicProvider`.** `StoryPreview` (the MDX ``) renders a named story with the playground's knob values as props, applies the variable overrides, and exposes a Reset button plus a collapsible `VariablesPanel` attached to the preview. `StoryEmbed` (the MDX ``) renders a single static variation with default knob values and no controls. +- **Every story renders inside `MosaicProvider`.** `StoryPreview` (the MDX ``) renders a named story with the playground's knob values as props and exposes a Reset button. `StoryEmbed` (the MDX ``) renders a single static variation with default knob values and no controls. -- **The prop table is the knob surface.** `PropTable` (MDX ``) derives rows from `meta.styles._variants`/`_defaultVariants`, then appends the escape-hatch rows for the component's styling engine (`meta.styleEngine`): Emotion components get `sx`, StyleX components get `className` + `style`. Each variant row renders a `KnobControl` in its **Value** column, seeded with the prop's default and bound to the playground context. The engine rows and `extra` stay static. - -- **Variables live in the preview.** The `VariablesPanel` is a collapsible attached to `StoryPreview` (toggled from the preview's header), bound to the shared playground context so editing a Mosaic token override immediately re-themes the story rendered above it. +- **The prop table is the knob surface.** `PropTable` (MDX ``) derives rows from `meta.styles._variants`/`_defaultVariants`, then appends the `className` + `style` escape-hatch rows every Mosaic component accepts. Each variant row renders a `KnobControl` in its **Value** column, seeded with the prop's default and bound to the playground context. The escape-hatch rows and `extra` stay static. - **MDX.** `mdx-components.tsx` injects custom components into all MDX: `` (→ `StoryPreview`), `` (→ `StoryEmbed`, static), `` (→ interactive `PropTable`), `` (→ `UsageBlock`, a live code snippet that reflects the current knob values), and a `
` override routing fenced code through Shiki (`CodeBlock`). `next.config.mjs` configures `remark-gfm` and `rehype-raw` (with MDX node pass-through) so raw HTML in tables works.
 
 - **`` examples can show their source in a collapsible code footer.** When a story module exposes its own source as `__source` — via a `?raw` self-import (`export { default as __source } from './x.stories?raw'`) — `StoryEmbed` runs `extractStorySource` (`lib/extractStorySource.ts`) to pull the *previewed story function's* source out of that raw text, then `toUsageSnippet` (`lib/exampleSnippet.ts`) to reduce that knob harness to a clean usage snippet (unwraps `export function …() { return (…) }` down to the returned JSX and strips the `{...knobsAsProps(props)}` / `{...props}` knob plumbing), and renders a `CodeFooter` (`CodeFooter.tsx`): a "View code" toggle that's collapsed by default and reveals the snippet with a height animation (Base UI's `--collapsible-panel-height` + `data-starting/ending-style`). It's **opt-in per module** — only modules that export `__source` get a footer, and it's keyed to whichever story `name` the `` renders, so each example shows its own code. Shiki highlighting is shared with the `
`/`CodeBlock` path through the `useShikiHtml` hook. A `` can carry both a code footer and a `composition` footer; they stack under the preview.
   - The `?raw` query is wired in `next.config.mjs`: an `asset/source` rule handles `?raw` imports, and — crucially — a recursive `excludeRawQuery` pass adds `resourceQuery: { not: [/raw/] }` to every *other* loader so Next's SWC loader doesn't compile the file first (otherwise `__source` would contain `_jsxDEV(…)` output instead of the authored source).
 
-- **Two component layers.** `src/components/ui/*` are shadcn/ui primitives (`components.json`, `base-nova` style, neutral base) used for swingset's *own* chrome (sidebar, tabs, inputs). The components being *documented* come from `@clerk/ui/mosaic`. Don't confuse the two. Mosaic stories use Emotion (`/** @jsxImportSource @emotion/react */` pragma; `compiler.emotion` enabled in Next).
+- **Two component layers.** `src/components/ui/*` are shadcn/ui primitives (`components.json`, `base-nova` style, neutral base) used for swingset's *own* chrome (sidebar, tabs, inputs). The components being *documented* come from `@clerk/ui/mosaic`. Don't confuse the two.
 
 ## Documenting Mosaic components
 
@@ -61,25 +59,22 @@ Pick the archetype below by the component's **layer** (its `meta.group`), then f
 
 | Group        | What lives here                                                | Archetype |
 | ------------ | -------------------------------------------------------------- | --------- |
-| `AIO`        | All-in-one flows (e.g. `OrganizationProfile`)                  | C         |
-| `Panels`     | A pane within a flow (e.g. `OrganizationProfileGeneral`)       | C         |
-| `Sections`   | Self-contained feature sections (e.g. `DeleteOrganization`)    | C         |
-| `Blocks`     | Reusable composite UI (e.g. `Destructive`)                     | C         |
-| `Components` | Styled Mosaic components — simple CVA recipe (`Button`, `Input`) or compound/slot-based (`Dialog`, `Tabs`) | A         |
+| `User`       | Composed flow UI (e.g. `UserButton`)                           | C         |
+| `Components` | Styled Mosaic components — simple, with a flat variant surface (`Button`, `Input`), or compound (`Card`, `Field`, `Menu`, `Popover`) | A         |
 | `Primitives` | Headless `@clerk/headless` primitives (`Accordion`)            | B         |
 | `Styles`     | Atomic styles that ship as StyleX atoms, not components (`Scroll Area`) | B (adapted) |
 | `Hooks`      | Headless hooks (`useDataTable`)                                | B (adapted) |
 
-`AIO` → `Panels` → `Sections` → `Blocks` → `Components` → `Primitives` runs roughly high-level-composition → low-level-primitive. Composed layers (AIO/Panels/Sections/Blocks) are documented as compositions of lower layers (archetype C); leaf layers (Components, Primitives) get full prop/knob docs (archetypes A and B).
+`User` → `Components` → `Primitives` runs high-level-composition → low-level-primitive. Composed layers are documented as compositions of lower layers (archetype C); leaf layers (Components, Primitives) get full prop/knob docs (archetypes A and B).
 
 `Styles` and `Hooks` are the non-component layers: there is no element to knob, so they follow
 archetype B's shape (Example → Usage → Parts → Styling) with `Props` replaced by whatever the export
 actually surfaces — an argument table for a style function, a return-value table for a hook. A
-`Styles` entry documents the theme tokens its atoms read, since those tokens _are_ its API; the
+`Styles` entry documents the tokens its atoms read, since those tokens _are_ its API; the
 `Hooks` entry (`use-data-table.stories.tsx`) is `meta` alone, with no story exports at all, which is
 the minimum a section entry needs.
 
-Archetype A has two forms, chosen by whether the component exposes a single flat CVA recipe: **simple** components (`Button`, `Input`) are knob-driven; **compound** components built from slot recipes (`Dialog`, `Tabs`) have no flat variant props to knob, so they're documented like a primitive but themed. Both are detailed under Archetype A below.
+Archetype A has two forms, chosen by whether the component exposes a single flat set of variant props: **simple** components (`Button`, `Input`) are knob-driven; **compound** components (`Card`, `Field`, `Menu`, `Popover`) have no flat variant props to knob, so they're documented like a primitive but themed. Both are detailed under Archetype A below.
 
 ### `meta` conventions (all archetypes)
 
@@ -88,22 +83,24 @@ export const meta: StoryMeta = {
   group: 'Components', // exact group string from the table
   title: 'Button', // drives slug + the page 

label: 'Delete Org', // optional friendlier sidebar text - source: 'packages/ui/src/mosaic/components/button.tsx', // repo-root path → "View source" - styleEngine: 'stylex', // set on migrated components; defaults to 'emotion' - styles: buttonRecipe, // CVA recipe — archetype A · simple only + source: 'packages/ui/src/mosaic/components/button/button.tsx', // repo-root path → "View source" + styles: { + // Hand-written variant surface — archetype A · simple only + _variants: { variant: { primary: {}, outline: {} }, size: { sm: {}, md: {} } }, + _defaultVariants: { variant: 'primary', size: 'md' }, + }, }; ``` - `title` is the component's export name; it produces the slug and is what readers match against code. Set `label` only when the sidebar should read differently (the slug and page heading still come from `title`). - `source` is always a path **relative to the monorepo root**, pointing at the file that exports the documented component. Always set it — it powers the "View source" link. -- `styleEngine` names the styling engine behind the component. It only affects which escape-hatch row the `` appends (`sx` vs `className` + `style`), so it matters for archetype A. Set `'stylex'` on migrated components; leave it off for Emotion ones. -- `styles` is the component's CVA recipe/style object and is **required for archetype A's simple (knob-driven) form** (it generates the knobs and the ``). Omit it for compound A components, and for B and C. +- `styles` declares the component's variant props and is **required for archetype A's simple (knob-driven) form** (it generates the knobs and the ``). Keep it in sync with the component's real prop union by hand — StyleX compiles its styles away, so nothing derives it for you. Omit it for compound A components, and for B and C. -Story files that render styled Mosaic components must start with the Emotion pragma `/** @jsxImportSource @emotion/react */`. Headless-primitive demos render raw and don't need it. Always import the component and its recipe explicitly — never `import *`. +Always import the component explicitly — never `import *`. ### Archetype A — styled component (`Components`) -A styled Mosaic component. Which of the two forms below applies is decided by the component's shape, not by preference: if it exposes a single flat CVA recipe (`meta.styles`), use the **simple** form; if it's compound — built from slot recipes with no flat variant props (`Dialog`, `Tabs`) — use the **compound** form. +A styled Mosaic component. Which of the two forms below applies is decided by the component's shape, not by preference: if it exposes a single flat set of variant props (declared as `meta.styles`), use the **simple** form; if it's compound — a set of parts with no flat variant props (`Card`, `Field`, `Menu`, `Popover`) — use the **compound** form. **Every `Components`-layer story file exposes its source so each `` example renders a code footer.** Add the self-import once, right after the imports: @@ -115,9 +112,9 @@ export { default as __source } from './.stories?raw'; That's all the wiring needed — `StoryEmbed` picks `__source` up automatically and renders a collapsible "View code" footer keyed to each example's story function (see the `` code-footer architecture note above). No MDX change is required; keep authoring `` as before. This applies to both A forms (simple and compound) and to every example a Components page ships. -#### A · simple — single CVA recipe (`Button`, `Input`) +#### A · simple — flat variant props (`Button`, `Input`) -Has a CVA recipe, so the page is **knob-driven**: an interactive canvas plus an auto-generated prop table. Required MDX section order: +Has a declared variant surface, so the page is **knob-driven**: an interactive canvas plus a generated prop table. Required MDX section order: ```mdx import * as ButtonStories from './button.stories'; @@ -167,25 +164,25 @@ import * as ButtonStories from './button.stories'; - **Playground / Props / Usage are mandatory and always in this order.** The three share one playground state: editing a row in `` re-renders `` above it and regenerates the `` snippet below it. - The story file exports a primary demo (rendered by ``) plus one named export per variation under **Examples**. Each story takes `props: Record` and casts through a local `knobsAsProps` helper — knobs are dynamically typed, the component isn't. -- Use ``'s `extra` for documenting non-variant props; the styling escape hatch is appended for you (`sx`, or `className` + `style` when `meta.styleEngine` is `'stylex'`). +- Use ``'s `extra` for documenting non-variant props; the `className` + `style` escape-hatch rows are appended for you. - Use `` to pin static, non-knob props in the generated snippet. -- `` renders `Prop | Type | Default | Value`: the **Default** column is filled automatically from the recipe's `_defaultVariants`, and the **Value** column is the live knob seeded with that default. No manual default annotation is needed; see _Document the default value_ under Archetype B. +- `` renders `Prop | Type | Default | Value`: the **Default** column is filled from `meta.styles._defaultVariants`, and the **Value** column is the live knob seeded with that default. No per-row default annotation is needed; see _Document the default value_ under Archetype B. -#### A · compound — slot recipes, no flat CVA (`Dialog`, `Tabs`) +#### A · compound — parts, no flat variant props (`Card`, `Field`, `Menu`, `Popover`) -A compound styled component (`Dialog.Root`/`Dialog.Popup`/…) has no single flat prop interface to knob, so there's no `` or ``. Document it like a primitive (archetype B) but themed — the difference is the **Styling** section, which describes the Mosaic recipe and per-slot `appearance.elements` overrides rather than "bring your own CSS". Required MDX section order: +A compound styled component (`Popover.Root`/`Popover.Popup`/…) has no single flat prop interface to knob, so there's no `` or ``. Document it like a primitive (archetype B) but themed — the difference is the **Styling** section, which lists each part's `.cl-` class and `data-` attributes rather than saying "bring your own CSS". Required MDX section order: ```mdx -import * as DialogStories from './dialog.component.stories'; +import * as PopoverStories from './popover.component.stories'; -# Dialog +# Popover - + ## Example - + ## Usage @@ -193,19 +190,19 @@ import * as DialogStories from './dialog.component.stories'; ## Parts - + ## Styling - + ``` -The story is `meta` (no `styles`) plus a single `Default` export that renders the composed parts. The file pair is named `.component.stories.tsx` / `.component.mdx` so it doesn't collide with the headless `Primitives` entry of the same title (e.g. `Dialog`, `Tabs` exist in both layers); the `docModules` map disambiguates by group. +The story is `meta` (no `styles`) plus a single `Default` export that renders the composed parts. The file pair is named `.component.stories.tsx` / `.component.mdx` so it doesn't collide with the headless `Primitives` entry of the same title (e.g. `Menu`, `Popover` exist in both layers); the `docModules` map disambiguates by group. ### Archetype B — headless primitive (`Primitives`) -No styles, so there's no knob canvas. The single demo renders the primitive **raw (unstyled)** to show only behavior, state, and ARIA wiring. The prop/styling tables are **hand-written** (there's no CVA recipe to derive them). Required MDX section order: +No styles, so there's no knob canvas. The single demo renders the primitive **raw (unstyled)** to show only behavior, state, and ARIA wiring. The prop/styling tables are **hand-written** (there is no variant surface to derive them from). Required MDX section order: ```mdx # Accordion @@ -238,29 +235,29 @@ No styles, so there's no knob canvas. The single demo renders the primitive **ra custom properties (e.g. `--cl-accordion-panel-height`) with a CSS example. --> ``` -The story is `meta` (no `styles`) plus a single `Default` export that renders the primitive unstyled. Don't add a `` or `` — primitives have neither knobs nor a CVA recipe to drive them. +The story is `meta` (no `styles`) plus a single `Default` export that renders the primitive unstyled. Don't add a `` or `` — primitives have neither knobs nor a variant surface to drive them. -**Document the default value for every prop in a dedicated Default column.** Every props table — auto and hand-written — has a **Default** column; the `Type` stays a plain union/enum and the default is named in its own column (the convention every component-doc site and TypeDoc's `@default` tag follow), never inlined into the type. The auto `` renders `Prop | Type | Default | Value` and fills Default from the recipe's `_defaultVariants` (the **Value** column is the live knob seeded with that default); hand-written tables render `Prop | Type | Default | Description` and fill it by hand. Name the default member (`'base'`, `'multiple'`, `'bottom-start'`); use `—` when there is no default (a controlled-only or required prop) and append `(required)` for required props; when the default is behavioral rather than a literal, state it in words (`inherits Root`, `falls back to value`). +**Document the default value for every prop in a dedicated Default column.** Every props table — auto and hand-written — has a **Default** column; the `Type` stays a plain union/enum and the default is named in its own column (the convention every component-doc site and TypeDoc's `@default` tag follow), never inlined into the type. The auto `` renders `Prop | Type | Default | Value` and fills Default from `meta.styles._defaultVariants` (the **Value** column is the live knob seeded with that default); hand-written tables render `Prop | Type | Default | Description` and fill it by hand. Name the default member (`'base'`, `'multiple'`, `'bottom-start'`); use `—` when there is no default (a controlled-only or required prop) and append `(required)` for required props; when the default is behavioral rather than a literal, state it in words (`inherits Root`, `falls back to value`). -### Archetype C — composed layer (`AIO` / `Panels` / `Sections` / `Blocks`) +### Archetype C — composed layer (`User`) These compose lower layers, so the docs lead with the composition rather than knobs. Required MDX: ```mdx -import * as DeleteOrganizationStories from './delete-organization.stories'; +import * as UserButtonStories from './user-button.stories'; -# Delete Organization +# UserButton ``` @@ -277,9 +274,9 @@ import * as DeleteOrganizationStories from './delete-organization.stories'; ### Before you finish - [ ] `meta.source` is set to a repo-root-relative path. -- [ ] Story renders (Emotion pragma present for styled components). +- [ ] Story renders. - [ ] `Components`-layer story files export `__source` (the `?raw` self-import) so every `` example gets a "View code" footer. - [ ] MDX sections match the archetype's required order exactly. -- [ ] Every props-table row states its default in the **Default** column (auto `` fills it from `_defaultVariants`; `—` / `(required)` when none). +- [ ] Every props-table row states its default in the **Default** column (auto `` fills it from `meta.styles._defaultVariants`; `—` / `(required)` when none). - [ ] Wiring done per `README.md`: `registry.ts`, `DocsViewer.tsx`'s `docModules`, and the `app/page.tsx` redirect if this is now the first component. - [ ] `pnpm format --filter @clerk/swingset` is clean. diff --git a/packages/swingset/README.md b/packages/swingset/README.md index 620764a1b68..a527d3427ea 100644 --- a/packages/swingset/README.md +++ b/packages/swingset/README.md @@ -13,15 +13,18 @@ pnpm dev --filter @clerk/swingset **1. Create a story file** — `src/stories/my-component.stories.tsx` ```tsx -/** @jsxImportSource @emotion/react */ import type { StoryMeta } from '@/lib/types'; -import { MyComponent, myComponentStyles } from '@clerk/ui/mosaic/components/my-component'; +import { MyComponent, type MyComponentProps } from '@clerk/ui/mosaic/components/my-component'; export const meta: StoryMeta = { group: 'Components', title: 'My Component', - source: 'packages/ui/src/mosaic/components/my-component.tsx', // repo-root path → "View source" link - styles: myComponentStyles, // CVA style object — knobs auto-generated from _variants + source: 'packages/ui/src/mosaic/components/my-component/my-component.tsx', // repo-root path → "View source" link + // Variant surface, declared by hand — knobs are generated from `_variants` + styles: { + _variants: { variant: { primary: {}, outline: {} }, size: { sm: {}, md: {} } }, + _defaultVariants: { variant: 'primary', size: 'md' }, + }, }; export function Default(props: Record) { @@ -29,7 +32,7 @@ export function Default(props: Record) { } ``` -Knobs are generated automatically from the CVA `_variants` on the style object. Boolean variants (`true`/`false` keys) become toggles; all others become selects. Default values come from `defaultVariants`. +Knobs are generated automatically from the hand-written `_variants` on `meta.styles`. Boolean variants (`true`/`false` keys) become toggles; all others become selects. Default values come from `_defaultVariants`. `source` is the path to the component's exporting file relative to the monorepo root; `DocsViewer` renders it as a "View source" link to the file on GitHub (`lib/source.ts`). @@ -64,10 +67,9 @@ import * as Stories from './my-component.stories'; ``` `` renders the live component inline in the overview — there are no separate -per-story pages. Its props are edited through the controls in the `` below it, -and a collapsible Variables panel attached to the preview exposes Mosaic token overrides -that re-theme it. Both share the page's playground state. Use `` for -additional static demos of specific variations (no controls). +per-story pages. Its props are edited through the controls in the `` below it; +the two share the page's playground state. Use `` for additional +static demos of specific variations (no controls). Register in `src/components/DocsViewer.tsx`: @@ -81,7 +83,7 @@ Also update the root redirect in `src/app/page.tsx` if this is now the first com ## PropTable -In MDX, use `` to auto-generate the props table from the CVA style object: +In MDX, use `` to auto-generate the props table from the story's declared variant surface: ```mdx import * as Stories from './my-component.stories'; @@ -89,7 +91,7 @@ import * as Stories from './my-component.stories'; ``` -Variant props (type and default) are derived from `meta.styles._variants` and `meta.styles._defaultVariants`. The `sx` prop is always appended automatically. Pass `extra` for any other non-variant props. +Variant props (type and default) are derived from `meta.styles._variants` and `meta.styles._defaultVariants`. The `className` and `style` escape-hatch rows are always appended automatically. Pass `extra` for any other non-variant props. ## Architecture @@ -100,17 +102,16 @@ src/ app-sidebar.tsx Left nav (reads from registry) ClientRoot.tsx SidebarProvider + breadcrumb header DocsViewer.tsx Renders MDX docs for /components/[slug]; provides PlaygroundContext - PlaygroundContext.tsx Shared per-page knob values + Mosaic variables + PlaygroundContext.tsx Shared per-page knob values StoryPreview.tsx Live embed, props driven by the playground state StoryEmbed.tsx Static embed: a single variation, no controls PropTable.tsx Interactive props table — controls live in the Value column KnobControl.tsx A single auto-generated control (switch/select/input) - VariablesPanel.tsx Mosaic CSS variable overrides (collapsible, attached to the preview) CodeBlock.tsx Shiki syntax highlighter (css-variables theme) ViewSource.tsx "View source" link to the component's file on GitHub lib/ registry.ts Story registry — add new stories here - generateKnobs.ts CVA _variants → knob definitions + generateKnobs.ts meta.styles._variants → knob definitions types.ts StoryMeta, StoryModule, KnobDef etc. slug.ts URL slug utilities source.ts Builds GitHub URLs from meta.source paths diff --git a/packages/swingset/next.config.mjs b/packages/swingset/next.config.mjs index 6b36f780e26..ce720c0da34 100644 --- a/packages/swingset/next.config.mjs +++ b/packages/swingset/next.config.mjs @@ -26,9 +26,6 @@ const withMDX = createMDX({ /** @type {import('next').NextConfig} */ const nextConfig = { pageExtensions: ['tsx', 'ts', 'mdx'], - compiler: { - emotion: true, - }, webpack(config) { // `import src from './foo.stories.tsx?raw'` must return the file's *untransformed* text. // Next's SWC loader matches by extension and ignores the `?raw` query, so on its own it diff --git a/packages/swingset/package.json b/packages/swingset/package.json index a2510b959ec..beefb939fc5 100644 --- a/packages/swingset/package.json +++ b/packages/swingset/package.json @@ -13,7 +13,6 @@ "@base-ui/react": "^1.5.0", "@clerk/headless": "workspace:*", "@clerk/ui": "workspace:*", - "@emotion/react": "^11.11.1", "@stylexjs/stylex": "0.19.0", "@tailwindcss/typography": "^0.5.19", "class-variance-authority": "^0.7.1", diff --git a/packages/swingset/src/components/Composition.tsx b/packages/swingset/src/components/Composition.tsx index 3724b72d61b..60ee46ad440 100644 --- a/packages/swingset/src/components/Composition.tsx +++ b/packages/swingset/src/components/Composition.tsx @@ -5,15 +5,15 @@ import Link from 'next/link'; export interface CompositionPiece { /** Display name of the piece (e.g. `Destructive`). */ name: string; - /** Route to the piece's page in swingset (e.g. `/blocks/destructive`). */ + /** Route to the piece's page in swingset (e.g. `/components/button`). */ href: string; - /** Which Mosaic layer the piece lives in (e.g. `Blocks`, `Components`, `Primitives`). */ + /** Which Mosaic layer the piece lives in (e.g. `User`, `Components`, `Primitives`). */ layer: string; } // Mosaic layers, high → low. Drives the order the composition groups render in. // Matches the sidebar group names. -const LAYER_ORDER = ['Organization', 'Blocks', 'Components', 'Primitives']; +const LAYER_ORDER = ['User', 'Components', 'Styles', 'Primitives']; function layerRank(layer: string): number { const i = LAYER_ORDER.indexOf(layer); diff --git a/packages/swingset/src/components/DocsViewer.tsx b/packages/swingset/src/components/DocsViewer.tsx index 3895b47ccfe..8ac7bc81d51 100644 --- a/packages/swingset/src/components/DocsViewer.tsx +++ b/packages/swingset/src/components/DocsViewer.tsx @@ -13,23 +13,6 @@ const docModules: Record> = { user: { 'user-button': dynamic(() => import('../stories/user-button.mdx')), }, - organization: { - 'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')), - 'organization-profile-general-panel': dynamic(() => import('../stories/organization-profile-general-panel.mdx')), - 'organization-profile-api-keys-panel': dynamic(() => import('../stories/organization-profile-api-keys-panel.mdx')), - 'organization-profile-members-panel': dynamic(() => import('../stories/organization-profile-members-panel.mdx')), - 'organization-profile-profile-section': dynamic( - () => import('../stories/organization-profile-profile-section.mdx'), - ), - 'organization-profile-domains-section': dynamic( - () => import('../stories/organization-profile-domains-section.mdx'), - ), - 'organization-profile-leave-section': dynamic(() => import('../stories/organization-profile-leave-section.mdx')), - 'organization-profile-delete-section': dynamic(() => import('../stories/organization-profile-delete-section.mdx')), - }, - blocks: { - destructive: dynamic(() => import('../stories/destructive.mdx')), - }, components: { avatar: dynamic(() => import('../stories/avatar.mdx')), badge: dynamic(() => import('../stories/badge.mdx')), @@ -43,7 +26,6 @@ const docModules: Record> = { menu: dynamic(() => import('../stories/menu.component.mdx')), popover: dynamic(() => import('../stories/popover.component.mdx')), section: dynamic(() => import('../stories/section.mdx')), - tabs: dynamic(() => import('../stories/tabs.component.mdx')), text: dynamic(() => import('../stories/text.mdx')), field: dynamic(() => import('../stories/field.component.mdx')), }, diff --git a/packages/swingset/src/components/PlaygroundContext.tsx b/packages/swingset/src/components/PlaygroundContext.tsx index c981d5794b6..de59bd4c5e7 100644 --- a/packages/swingset/src/components/PlaygroundContext.tsx +++ b/packages/swingset/src/components/PlaygroundContext.tsx @@ -1,6 +1,5 @@ 'use client'; -import type { MosaicVariables } from '@clerk/ui/mosaic/variables'; import type React from 'react'; import { createContext, useContext, useMemo, useState } from 'react'; @@ -8,15 +7,12 @@ import { generateKnobs, initKnobValues } from '@/lib/generateKnobs'; import type { KnobRecord, KnobValues, StoryMeta } from '@/lib/types'; interface PlaygroundContextValue { - /** Knob definitions derived from the component's CVA `_variants`. */ + /** Knob definitions derived from `meta.styles._variants`. */ knobs: KnobRecord; /** Current value for each knob (props passed into the live preview). */ values: KnobValues; setValue: (key: string, value: KnobValues[string]) => void; - /** Live Mosaic design-token overrides applied via `MosaicProvider`. */ - variables: MosaicVariables; - setVariables: (variables: MosaicVariables) => void; - /** Restore every knob to its default and clear variable overrides. */ + /** Restore every knob to its default. */ reset: () => void; } @@ -30,21 +26,15 @@ const PlaygroundContext = createContext(null); export function PlaygroundProvider({ meta, children }: { meta?: StoryMeta; children: React.ReactNode }) { const knobs = useMemo(() => (meta ? generateKnobs(meta) : {}), [meta]); const [values, setValues] = useState(() => initKnobValues(knobs)); - const [variables, setVariables] = useState({}); const value = useMemo( () => ({ knobs, values, setValue: (key, v) => setValues(prev => ({ ...prev, [key]: v })), - variables, - setVariables, - reset: () => { - setValues(initKnobValues(knobs)); - setVariables({}); - }, + reset: () => setValues(initKnobValues(knobs)), }), - [knobs, values, variables], + [knobs, values], ); return {children}; diff --git a/packages/swingset/src/components/PropTable.tsx b/packages/swingset/src/components/PropTable.tsx index b7084751802..616c811f6e0 100644 --- a/packages/swingset/src/components/PropTable.tsx +++ b/packages/swingset/src/components/PropTable.tsx @@ -17,7 +17,6 @@ interface PropTableProps { extra?: ExtraProp[]; } -const SX_ROW: ExtraProp = { name: 'sx', type: 'StyleRule | (theme) => StyleRule' }; const STYLEX_ROWS: ExtraProp[] = [ { name: 'className', type: 'string' }, { name: 'style', type: 'CSSProperties' }, @@ -39,7 +38,7 @@ export function PropTable({ meta, extra = [] }: PropTableProps) { return { name, type, default: defDisplay }; }), ...extra, - ...(meta.styleEngine === 'stylex' ? STYLEX_ROWS : [SX_ROW]), + ...STYLEX_ROWS, ]; return ( diff --git a/packages/swingset/src/components/StoryPreview.tsx b/packages/swingset/src/components/StoryPreview.tsx index ac202903489..25519efc451 100644 --- a/packages/swingset/src/components/StoryPreview.tsx +++ b/packages/swingset/src/components/StoryPreview.tsx @@ -1,16 +1,14 @@ 'use client'; import { MosaicProvider } from '@clerk/ui/mosaic/MosaicProvider'; -import { RotateCcwIcon, SlidersHorizontalIcon } from 'lucide-react'; +import { RotateCcwIcon } from 'lucide-react'; import type React from 'react'; import { useEffect, useState } from 'react'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { generateKnobs, initKnobValues } from '@/lib/generateKnobs'; import type { StoryModule } from '@/lib/types'; import { usePlayground } from './PlaygroundContext'; -import { VariablesPanel } from './VariablesPanel'; interface StoryPreviewProps { name: string; @@ -20,13 +18,11 @@ interface StoryPreviewProps { /** * Interactive preview embedded in a component's MDX overview. Renders the named story * inside `MosaicProvider`; its props are driven by the shared playground state, which is - * edited through the controls in the `` below it. A collapsible `VariablesPanel` - * attached to the preview overrides Mosaic design tokens to re-theme it live. + * edited through the controls in the `` below it. */ export function StoryPreview({ name, storyModule }: StoryPreviewProps) { const StoryComp = storyModule[name] as React.ComponentType>; const playground = usePlayground(); - const [variablesOpen, setVariablesOpen] = useState(false); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); @@ -38,14 +34,9 @@ export function StoryPreview({ name, storyModule }: StoryPreviewProps) { // Fall back to the story's own defaults if rendered outside a PlaygroundProvider. const values = playground?.values ?? initKnobValues(generateKnobs(storyModule.meta)); - const variables = playground?.variables ?? {}; return ( - +
- -
- - - Variables - - {variablesOpen && playground ? ( - - ) : null} -
- - {playground ? ( - - - - ) : null} - +
); } diff --git a/packages/swingset/src/components/VariablesPanel.tsx b/packages/swingset/src/components/VariablesPanel.tsx deleted file mode 100644 index d9a01cbbe99..00000000000 --- a/packages/swingset/src/components/VariablesPanel.tsx +++ /dev/null @@ -1,101 +0,0 @@ -'use client'; - -import type { MosaicVariables } from '@clerk/ui/mosaic/variables'; -import { defaultMosaicVariables } from '@clerk/ui/mosaic/variables'; - -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; - -interface VariablesPanelProps { - variables: MosaicVariables; - onChange: (variables: MosaicVariables) => void; -} - -export function VariablesPanel({ variables, onChange }: VariablesPanelProps) { - const colors = defaultMosaicVariables.color; - const radii = defaultMosaicVariables.rounded; - - function setColor(key: keyof typeof colors, value: string) { - onChange({ ...variables, color: { ...variables.color, [key]: value } }); - } - - function setRounded(key: keyof typeof radii, value: string) { - onChange({ ...variables, rounded: { ...variables.rounded, [key]: value } }); - } - - function setSpacing(value: string) { - onChange({ ...variables, spacing: value }); - } - - return ( -
-
-
-
Colors
- {(Object.keys(colors) as Array).map(key => ( -
- - )?.[key] ?? colors[key]} - onChange={e => setColor(key, e.target.value)} - className='h-7 font-mono text-xs' - /> -
- ))} -
- -
-
Radius
- {(Object.keys(radii) as Array) - .filter(k => k !== 'full') - .map(key => ( -
- - )?.[key] ?? radii[key]} - onChange={e => setRounded(key, e.target.value)} - className='h-7 font-mono text-xs' - /> -
- ))} -
- -
-
Spacing
-
- - setSpacing(e.target.value)} - className='h-7 font-mono text-xs' - /> -
-
-
-
- ); -} diff --git a/packages/swingset/src/lib/registry.ts b/packages/swingset/src/lib/registry.ts index 9baf653c30b..daa6b2e8f23 100644 --- a/packages/swingset/src/lib/registry.ts +++ b/packages/swingset/src/lib/registry.ts @@ -22,7 +22,6 @@ import { meta as cardComponentMeta, } from '../stories/card.component.stories'; import { meta as collapsibleMeta } from '../stories/collapsible.stories'; -import { Default as DestructiveDefault, meta as destructiveMeta } from '../stories/destructive.stories'; import { Default as DialogDefault, meta as dialogComponentMeta } from '../stories/dialog.component.stories'; import { meta as dialogMeta } from '../stories/dialog.stories'; import { meta as drawerMeta } from '../stories/drawer.stories'; @@ -57,38 +56,6 @@ import { } from '../stories/item.stories'; import { Default as MenuComponentDefault, meta as menuComponentMeta } from '../stories/menu.component.stories'; import { meta as menuMeta } from '../stories/menu.stories'; -import { - Default as OrganizationProfileDefault, - meta as organizationProfileMeta, -} from '../stories/organization-profile.stories'; -import { - Default as OrganizationProfileApiKeysPanelDefault, - meta as organizationProfileApiKeysPanelMeta, -} from '../stories/organization-profile-api-keys-panel.stories'; -import { - Default as OrganizationProfileDeleteSectionDefault, - meta as organizationProfileDeleteSectionMeta, -} from '../stories/organization-profile-delete-section.stories'; -import { - Default as OrganizationProfileDomainsSectionDefault, - meta as organizationProfileDomainsSectionMeta, -} from '../stories/organization-profile-domains-section.stories'; -import { - Default as OrganizationProfileGeneralPanelDefault, - meta as organizationProfileGeneralPanelMeta, -} from '../stories/organization-profile-general-panel.stories'; -import { - Default as OrganizationProfileLeaveSectionDefault, - meta as organizationProfileLeaveSectionMeta, -} from '../stories/organization-profile-leave-section.stories'; -import { - Default as OrganizationProfileMembersPanelDefault, - meta as organizationProfileMembersPanelMeta, -} from '../stories/organization-profile-members-panel.stories'; -import { - Default as OrganizationProfileProfileSectionDefault, - meta as organizationProfileProfileSectionMeta, -} from '../stories/organization-profile-profile-section.stories'; import { meta as otpMeta } from '../stories/otp.stories'; import { Alignment as PopoverComponentAlignment, @@ -114,7 +81,6 @@ import { MultipleEmailAndPhoneNumbers as SectionMultipleEmailAndPhoneNumbers, } from '../stories/section.stories'; import { meta as selectMeta } from '../stories/select.stories'; -import { Default as TabsComponentDefault, meta as tabsComponentMeta } from '../stories/tabs.component.stories'; import { meta as tabsMeta } from '../stories/tabs.stories'; import { Colors as TextColors, @@ -133,7 +99,6 @@ import { import { toSlug } from './slug'; import type { StoryModule } from './types'; -const destructiveModule: StoryModule = { meta: destructiveMeta, Default: DestructiveDefault }; const sectionModule: StoryModule = { meta: sectionMeta, Default: SectionDefault, @@ -141,35 +106,7 @@ const sectionModule: StoryModule = { ConnectedAccounts: SectionConnectedAccounts, Destructive: SectionDestructive, }; -const organizationProfileLeaveSectionModule: StoryModule = { - meta: organizationProfileLeaveSectionMeta, - Default: OrganizationProfileLeaveSectionDefault, -}; -const organizationProfileDeleteSectionModule: StoryModule = { - meta: organizationProfileDeleteSectionMeta, - Default: OrganizationProfileDeleteSectionDefault, -}; -const organizationProfileProfileSectionModule: StoryModule = { - meta: organizationProfileProfileSectionMeta, - Default: OrganizationProfileProfileSectionDefault, -}; -const organizationProfileDomainsSectionModule: StoryModule = { - meta: organizationProfileDomainsSectionMeta, - Default: OrganizationProfileDomainsSectionDefault, -}; -const organizationProfileModule: StoryModule = { meta: organizationProfileMeta, Default: OrganizationProfileDefault }; -const organizationProfileGeneralPanelModule: StoryModule = { - meta: organizationProfileGeneralPanelMeta, - Default: OrganizationProfileGeneralPanelDefault, -}; -const organizationProfileApiKeysPanelModule: StoryModule = { - meta: organizationProfileApiKeysPanelMeta, - Default: OrganizationProfileApiKeysPanelDefault, -}; -const organizationProfileMembersPanelModule: StoryModule = { - meta: organizationProfileMembersPanelMeta, - Default: OrganizationProfileMembersPanelDefault, -}; +const dialogComponentModule: StoryModule = { meta: dialogComponentMeta, Default: DialogDefault }; const cardComponentModule: StoryModule = { meta: cardComponentMeta, Default: CardDefault, Centered: CardCentered }; @@ -193,8 +130,6 @@ const buttonModule: StoryModule = { meta: buttonMeta, Primary, Sizes, Disabled } const inputModule: StoryModule = { meta: inputMeta, Default, Sizes: InputSizes, Disabled: InputDisabled, Invalid }; -const dialogComponentModule: StoryModule = { meta: dialogComponentMeta, Default: DialogDefault }; - const popoverComponentModule: StoryModule = { meta: popoverComponentMeta, Default: PopoverComponentDefault, @@ -226,8 +161,6 @@ const headingModule: StoryModule = { const menuComponentModule: StoryModule = { meta: menuComponentMeta, Default: MenuComponentDefault }; -const tabsComponentModule: StoryModule = { meta: tabsComponentMeta, Default: TabsComponentDefault }; - const textModule: StoryModule = { meta: textMeta, Default: TextDefault, Sizes: TextSizes, Colors: TextColors }; const fieldModule: StoryModule = { @@ -274,17 +207,6 @@ const useDataTableModule: StoryModule = { meta: useDataTableMeta }; export const registry: StoryModule[] = [ // User userButtonModule, - // Organization - organizationProfileModule, - organizationProfileGeneralPanelModule, - organizationProfileApiKeysPanelModule, - organizationProfileMembersPanelModule, - organizationProfileProfileSectionModule, - organizationProfileDomainsSectionModule, - organizationProfileLeaveSectionModule, - organizationProfileDeleteSectionModule, - // Blocks - destructiveModule, // Components avatarModule, badgeModule, @@ -298,7 +220,6 @@ export const registry: StoryModule[] = [ menuComponentModule, popoverComponentModule, sectionModule, - tabsComponentModule, textModule, fieldModule, // Primitives — alphabetical within the group. diff --git a/packages/swingset/src/lib/types.ts b/packages/swingset/src/lib/types.ts index de1faedc8ad..e031a80e6cd 100644 --- a/packages/swingset/src/lib/types.ts +++ b/packages/swingset/src/lib/types.ts @@ -50,11 +50,9 @@ export interface StoryMeta { */ source?: string; /** - * Which styling engine the documented component is built on. Drives the prop rows that - * are engine-specific: Emotion components take `sx`, StyleX components take `className` - * and `style`. Defaults to `'emotion'` — set `'stylex'` once a component is migrated. + * The component's variant surface, described by hand. Drives the playground knobs and + * the `` rows. StyleX has no runtime recipe to derive this from. */ - styleEngine?: 'emotion' | 'stylex'; styles?: { _variants: Record>; _defaultVariants?: Record; diff --git a/packages/swingset/src/stories/avatar.stories.tsx b/packages/swingset/src/stories/avatar.stories.tsx index bb5e9b39ff8..7bc6f2a542f 100644 --- a/packages/swingset/src/stories/avatar.stories.tsx +++ b/packages/swingset/src/stories/avatar.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import type { AvatarProps } from '@clerk/ui/mosaic/components/avatar'; import { Avatar } from '@clerk/ui/mosaic/components/avatar'; import { Icon } from '@clerk/ui/mosaic/components/icon'; diff --git a/packages/swingset/src/stories/badge.stories.tsx b/packages/swingset/src/stories/badge.stories.tsx index 6cf390eac50..57f4bce4cd4 100644 --- a/packages/swingset/src/stories/badge.stories.tsx +++ b/packages/swingset/src/stories/badge.stories.tsx @@ -13,7 +13,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Badge', source: 'packages/ui/src/mosaic/components/badge/badge.tsx', - styleEngine: 'stylex', styles: { _variants: { color: { primary: {}, neutral: {}, warning: {}, negative: {}, positive: {} }, diff --git a/packages/swingset/src/stories/button.stories.tsx b/packages/swingset/src/stories/button.stories.tsx index 3c74b4ac61d..28ce109c3ac 100644 --- a/packages/swingset/src/stories/button.stories.tsx +++ b/packages/swingset/src/stories/button.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import type { ButtonProps } from '@clerk/ui/mosaic/components/button'; import { Button, SubmitButton } from '@clerk/ui/mosaic/components/button'; import { Icon } from '@clerk/ui/mosaic/components/icon'; @@ -14,7 +13,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Button', source: 'packages/ui/src/mosaic/components/button/button.tsx', - styleEngine: 'stylex', styles: { _variants: { color: { primary: {}, neutral: {}, negative: {} }, diff --git a/packages/swingset/src/stories/card.component.stories.tsx b/packages/swingset/src/stories/card.component.stories.tsx index 1ddb1586f3c..600afdbcb52 100644 --- a/packages/swingset/src/stories/card.component.stories.tsx +++ b/packages/swingset/src/stories/card.component.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Button } from '@clerk/ui/mosaic/components/button'; import type { CardProps } from '@clerk/ui/mosaic/components/card'; import { Card } from '@clerk/ui/mosaic/components/card'; @@ -15,7 +14,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Card', source: 'packages/ui/src/mosaic/components/card/card.tsx', - styleEngine: 'stylex', styles: { _variants: { alignment: { start: {}, center: {} }, diff --git a/packages/swingset/src/stories/destructive.mdx b/packages/swingset/src/stories/destructive.mdx deleted file mode 100644 index a4990e31a23..00000000000 --- a/packages/swingset/src/stories/destructive.mdx +++ /dev/null @@ -1,15 +0,0 @@ -import * as DestructiveStories from './destructive.stories'; - -# Destructive - -A controlled block that composes a trigger button, a confirmation dialog, and a guarded input — the user must type the resource name exactly before the action is enabled. - - diff --git a/packages/swingset/src/stories/destructive.stories.tsx b/packages/swingset/src/stories/destructive.stories.tsx deleted file mode 100644 index e1addc2aeee..00000000000 --- a/packages/swingset/src/stories/destructive.stories.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { Destructive } from '@clerk/ui/mosaic/block/destructive'; -import { Button } from '@clerk/ui/mosaic/components/button'; -import type { HTMLAttributes } from 'react'; -import { useState } from 'react'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Blocks', - title: 'Destructive', - source: 'packages/ui/src/mosaic/block/destructive.tsx', -}; - -function DestructiveTrigger(props: HTMLAttributes) { - return ( - - ); -} - -export function Default() { - const [open, setOpen] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); - const [confirmationValue, setConfirmationValue] = useState(''); - - const handleDelete = async () => { - setIsDeleting(true); - await new Promise(resolve => setTimeout(resolve, 2000)); - setIsDeleting(false); - setOpen(false); - }; - - return ( - - ); -} diff --git a/packages/swingset/src/stories/dialog.component.stories.tsx b/packages/swingset/src/stories/dialog.component.stories.tsx index 2d5e857a58d..4bf132b91fb 100644 --- a/packages/swingset/src/stories/dialog.component.stories.tsx +++ b/packages/swingset/src/stories/dialog.component.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import type { RenderProps } from '@clerk/headless/utils'; import { Button } from '@clerk/ui/mosaic/components/button'; import { Card } from '@clerk/ui/mosaic/components/card'; @@ -23,7 +22,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Dialog', source: 'packages/ui/src/mosaic/components/dialog/dialog.tsx', - styleEngine: 'stylex', styles: { _variants: { size: { prompt: {}, card: {}, panel: {} }, diff --git a/packages/swingset/src/stories/field.component.stories.tsx b/packages/swingset/src/stories/field.component.stories.tsx index 1a7ca15cb8a..79a220f720e 100644 --- a/packages/swingset/src/stories/field.component.stories.tsx +++ b/packages/swingset/src/stories/field.component.stories.tsx @@ -11,7 +11,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Field', source: 'packages/ui/src/mosaic/components/field/field.tsx', - styleEngine: 'stylex', }; const stackStyles = { diff --git a/packages/swingset/src/stories/heading.stories.tsx b/packages/swingset/src/stories/heading.stories.tsx index ad4a9de91f3..cf177cd8446 100644 --- a/packages/swingset/src/stories/heading.stories.tsx +++ b/packages/swingset/src/stories/heading.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import type { HeadingProps } from '@clerk/ui/mosaic/components/heading'; import { Heading } from '@clerk/ui/mosaic/components/heading'; @@ -12,7 +11,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Heading', source: 'packages/ui/src/mosaic/components/heading/heading.tsx', - styleEngine: 'stylex', styles: { _variants: { size: { xs: {}, sm: {}, base: {}, lg: {}, xl: {}, '2xl': {} }, diff --git a/packages/swingset/src/stories/icon.mdx b/packages/swingset/src/stories/icon.mdx index 96ac21d0b92..dd56a6b345b 100644 --- a/packages/swingset/src/stories/icon.mdx +++ b/packages/swingset/src/stories/icon.mdx @@ -2,7 +2,7 @@ import * as IconStories from './icon.stories'; # Icon -Icon renders a named glyph from Mosaic's icon set. It sizes through the `size` variant and inherits color from `currentColor`. Any glyph can be swapped per name through `appearance.icons` on `MosaicProvider`. Mosaic's styling applies to an override just like the built-in glyph — so swapped icons stay visually consistent — and the override also carries the `.cl-icon` class and `data-size` attribute for targeting. +Icon renders a named glyph from Mosaic's icon set. It sizes through the `size` variant and inherits color from `currentColor`. Any glyph can be swapped per name through the `icons` prop on `MosaicProvider`. Mosaic's styling applies to an override just like the built-in glyph — so swapped icons stay visually consistent — and the override also carries the `.cl-icon` class and `data-size` attribute for targeting. Set `placement` when the icon sits beside text inside a container. It adds no styling of its own; it reflects `data-icon="inline-start"` or `data-icon="inline-end"` so the container can react — a `Button`, for example, tightens its padding on the side the icon sits with `:has([data-icon='inline-end'])`. The values are the CSS logical directions, so they follow the writing mode rather than naming a physical edge. Leave it unset for a standalone icon. @@ -51,7 +51,7 @@ Set `placement` when the icon sits beside text inside a container. It adds no st ### Overriding a glyph -Pass `appearance.icons` to `MosaicProvider` to replace a glyph by name. The override is a plain element of any type, not just an `svg`; Mosaic clones it with the resolved sizing `className` and `data-size`, keeping any class the element already had. The replacement only supplies its own content. +Pass `icons` to `MosaicProvider` to replace a glyph by name. The override is a plain element of any type, not just an `svg`; Mosaic clones it with the resolved sizing `className` and `data-size`, keeping any class the element already had. The replacement only supplies its own content. - - - ), - }, + icons={{ + 'chevron-right': ( + + + + ), }} >
diff --git a/packages/swingset/src/stories/input.stories.tsx b/packages/swingset/src/stories/input.stories.tsx index be98937592e..c5f8600ad7b 100644 --- a/packages/swingset/src/stories/input.stories.tsx +++ b/packages/swingset/src/stories/input.stories.tsx @@ -11,7 +11,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Input', source: 'packages/ui/src/mosaic/components/input/input.tsx', - styleEngine: 'stylex', styles: { _variants: { size: { sm: {}, md: {}, lg: {} }, diff --git a/packages/swingset/src/stories/item.stories.tsx b/packages/swingset/src/stories/item.stories.tsx index eabc11f40f7..97594a694c4 100644 --- a/packages/swingset/src/stories/item.stories.tsx +++ b/packages/swingset/src/stories/item.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Avatar } from '@clerk/ui/mosaic/components/avatar'; import { Button } from '@clerk/ui/mosaic/components/button'; import { Icon } from '@clerk/ui/mosaic/components/icon'; diff --git a/packages/swingset/src/stories/menu.component.mdx b/packages/swingset/src/stories/menu.component.mdx index b1a749eaeea..aad2de21a64 100644 --- a/packages/swingset/src/stories/menu.component.mdx +++ b/packages/swingset/src/stories/menu.component.mdx @@ -117,7 +117,7 @@ const [open, setOpen] = useState(false); ## Styling -Unlike the slot-recipe components, the Mosaic menu is themed with **StyleX**. Each styled part +The Mosaic menu is themed with **StyleX**. Each styled part carries a stable `.cl-` class (the slots above) alongside the StyleX atoms. Consumers never target the hashed atomic classes — override by targeting the `.cl-*` slot from a CSS layer that wins over `@clerk/ui/styles.css`: diff --git a/packages/swingset/src/stories/menu.component.stories.tsx b/packages/swingset/src/stories/menu.component.stories.tsx index 82ee976fdac..276f205c8ca 100644 --- a/packages/swingset/src/stories/menu.component.stories.tsx +++ b/packages/swingset/src/stories/menu.component.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Icon } from '@clerk/ui/mosaic/components/icon'; import { Menu } from '@clerk/ui/mosaic/components/menu'; diff --git a/packages/swingset/src/stories/organization-profile-api-keys-panel.mdx b/packages/swingset/src/stories/organization-profile-api-keys-panel.mdx deleted file mode 100644 index 354a713c0bf..00000000000 --- a/packages/swingset/src/stories/organization-profile-api-keys-panel.mdx +++ /dev/null @@ -1,17 +0,0 @@ -import * as OrganizationProfileApiKeysPanelStories from './organization-profile-api-keys-panel.stories'; - -# Organization Profile API Keys Panel - -The API keys tab panel of the Organization Profile — lists an organization's API keys and owns the create and revoke flows. It gates on `org:sys_api_keys:read` to view and `org:sys_api_keys:manage` to create or revoke. - - diff --git a/packages/swingset/src/stories/organization-profile-api-keys-panel.stories.tsx b/packages/swingset/src/stories/organization-profile-api-keys-panel.stories.tsx deleted file mode 100644 index 573624f6832..00000000000 --- a/packages/swingset/src/stories/organization-profile-api-keys-panel.stories.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import type { ApiKeyRow } from '@clerk/ui/mosaic/organization/organization-profile-api-keys-panel.view'; -import { OrganizationProfileApiKeysPanelView } from '@clerk/ui/mosaic/organization/organization-profile-api-keys-panel.view'; -import { organizationProfileApiKeysPanelCreateMachine } from '@clerk/ui/mosaic/organization/organization-profile-api-keys-panel-create.machine'; -import { organizationProfileApiKeysPanelRevokeMachine } from '@clerk/ui/mosaic/organization/organization-profile-api-keys-panel-revoke.machine'; -import { useMemo, useState } from 'react'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileApiKeysPanel', - source: 'packages/ui/src/mosaic/organization/organization-profile-api-keys-panel.tsx', -}; - -const SHOW_DESCRIPTION = false; - -// Fixed dates (not `Date.now()`) so the demo renders deterministically across reloads. -const DEMO_ROWS: ApiKeyRow[] = [ - { - id: 'key_prod', - name: 'Production', - createdAt: new Date('2026-01-12'), - expiration: null, - lastUsedAt: new Date('2026-06-28'), - }, - { - id: 'key_ci', - name: 'CI pipeline', - createdAt: new Date('2026-03-04'), - expiration: new Date('2026-09-04'), - lastUsedAt: null, - }, - { - id: 'key_staging', - name: 'Staging', - createdAt: new Date('2026-05-20'), - expiration: null, - lastUsedAt: new Date('2026-07-01'), - }, -]; - -const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - -export function Default() { - const [searchValue, setSearchValue] = useState(''); - - const rows = useMemo(() => { - const query = searchValue.trim().toLowerCase(); - return query ? DEMO_ROWS.filter(row => row.name.toLowerCase().includes(query)) : DEMO_ROWS; - }, [searchValue]); - - const [createSnapshot, sendCreate, createActor] = useMachine(organizationProfileApiKeysPanelCreateMachine, { - context: { - showDescription: SHOW_DESCRIPTION, - createAPIKey: async params => { - await delay(600); - return { name: params.name, secret: `sk_test_${params.name.replace(/\s+/g, '_').toLowerCase()}` }; - }, - }, - }); - - const [revokeSnapshot, sendRevoke, revokeActor] = useMachine(organizationProfileApiKeysPanelRevokeMachine, { - context: { - confirmationText: 'Revoke', - revokeAPIKey: () => delay(600), - }, - }); - - return ( - undefined, - searchValue, - onSearchChange: setSearchValue, - }} - canManage - create={{ - snapshot: createSnapshot, - send: sendCreate, - canSubmit: createActor.can({ type: 'SUBMIT' }), - showDescription: SHOW_DESCRIPTION, - }} - revoke={{ - snapshot: revokeSnapshot, - send: sendRevoke, - canConfirm: revokeActor.can({ type: 'CONFIRM' }), - }} - /> - ); -} diff --git a/packages/swingset/src/stories/organization-profile-delete-section.mdx b/packages/swingset/src/stories/organization-profile-delete-section.mdx deleted file mode 100644 index 0d885514caa..00000000000 --- a/packages/swingset/src/stories/organization-profile-delete-section.mdx +++ /dev/null @@ -1,16 +0,0 @@ -import * as OrganizationProfileDeleteSectionStories from './organization-profile-delete-section.stories'; - -# Organization Profile Delete Section - -A section that owns the open/deleting state and wires the `Destructive` block to the delete-organization flow. - - diff --git a/packages/swingset/src/stories/organization-profile-delete-section.stories.tsx b/packages/swingset/src/stories/organization-profile-delete-section.stories.tsx deleted file mode 100644 index 8e1282cbe99..00000000000 --- a/packages/swingset/src/stories/organization-profile-delete-section.stories.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import { organizationProfileDeleteSectionMachine } from '@clerk/ui/mosaic/organization/organization-profile-delete-section.machine'; -import { OrganizationProfileDeleteSectionView } from '@clerk/ui/mosaic/organization/organization-profile-delete-section.view'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileDeleteSection', - source: 'packages/ui/src/mosaic/organization/organization-profile-delete-section.tsx', -}; - -export function Default() { - const [snapshot, send, actor] = useMachine(organizationProfileDeleteSectionMachine, { - context: { - organizationName: 'Acme Inc', - destroyOrganization: () => new Promise(resolve => setTimeout(resolve, 800)), - }, - }); - - return ( - - ); -} diff --git a/packages/swingset/src/stories/organization-profile-domains-section.mdx b/packages/swingset/src/stories/organization-profile-domains-section.mdx deleted file mode 100644 index 232b4e3b4a5..00000000000 --- a/packages/swingset/src/stories/organization-profile-domains-section.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import * as OrganizationProfileDomainsSectionStories from './organization-profile-domains-section.stories'; - -# Organization Profile Domains Section - -Manages an organization's domains — lists them, adds and verifies new ones, edits a verified -domain's enrollment mode, and removes them. It owns each flow's state (three machines) and wires -the list, the add/verify wizard, the enrollment editor, and the remove confirmation together. - - diff --git a/packages/swingset/src/stories/organization-profile-domains-section.stories.tsx b/packages/swingset/src/stories/organization-profile-domains-section.stories.tsx deleted file mode 100644 index ad288a294c4..00000000000 --- a/packages/swingset/src/stories/organization-profile-domains-section.stories.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import type { OrganizationDomainResource } from '@clerk/shared/types'; -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import type { OrganizationProfileEnrollmentOption } from '@clerk/ui/mosaic/organization/organization-profile-domains-section.controller'; -import { OrganizationProfileDomainsSectionView } from '@clerk/ui/mosaic/organization/organization-profile-domains-section.view'; -import { organizationProfileDomainsSectionAddVerifyMachine } from '@clerk/ui/mosaic/organization/organization-profile-domains-section-add-verify.machine'; -import { organizationProfileDomainsSectionEnrollmentMachine } from '@clerk/ui/mosaic/organization/organization-profile-domains-section-enrollment.machine'; -import { organizationProfileDomainsSectionRemoveMachine } from '@clerk/ui/mosaic/organization/organization-profile-domains-section-remove.machine'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileDomainsSection', - source: 'packages/ui/src/mosaic/organization/organization-profile-domains-section.tsx', -}; - -// Demo async deps: resolve after a short delay so the loading/saving states are visible. -const delay = () => new Promise(resolve => setTimeout(resolve, 600)); - -const ENROLLMENT_OPTIONS: OrganizationProfileEnrollmentOption[] = [ - { - value: 'manual_invitation', - label: 'No automatic enrollment', - description: 'Users can only be invited manually to the organization.', - }, - { - value: 'automatic_invitation', - label: 'Automatic invitations', - description: 'Users are automatically invited to join the organization when they sign up and can join anytime.', - }, - { - value: 'automatic_suggestion', - label: 'Automatic suggestions', - description: 'Users receive a suggestion to request to join, but must be approved by an admin.', - }, -]; - -// The view reads only id/name/verification/enrollmentMode/pending counts, so a partial fixture -// is enough. The cast is confined to these swingset demo fixtures. -const demoDomains = [ - { - id: 'dmn_1', - name: 'acme.com', - enrollmentMode: 'automatic_invitation', - verification: { status: 'verified' }, - totalPendingInvitations: 3, - totalPendingSuggestions: 1, - }, - { - id: 'dmn_2', - name: 'acme.dev', - enrollmentMode: 'manual_invitation', - verification: { status: 'unverified' }, - totalPendingInvitations: 0, - totalPendingSuggestions: 0, - }, -] as unknown as OrganizationDomainResource[]; - -export function Default() { - const [addVerifySnapshot, sendAddVerify] = useMachine(organizationProfileDomainsSectionAddVerifyMachine, { - context: { - createDomain: async (name: string) => { - await delay(); - return { id: 'dmn_new', name, verified: false }; - }, - prepareVerification: async () => { - await delay(); - }, - attemptVerification: async () => { - await delay(); - return { verified: true }; - }, - updateEnrollmentMode: async () => { - await delay(); - }, - }, - }); - - const [enrollmentSnapshot, sendEnrollment, enrollmentActor] = useMachine( - organizationProfileDomainsSectionEnrollmentMachine, - { - context: { - updateEnrollmentMode: async () => { - await delay(); - }, - }, - }, - ); - - const [removeSnapshot, sendRemove] = useMachine(organizationProfileDomainsSectionRemoveMachine, { - context: { - deleteDomain: async () => { - await delay(); - }, - }, - }); - - return ( - {} }} - enrollmentOptions={ENROLLMENT_OPTIONS} - addVerify={{ snapshot: addVerifySnapshot, send: sendAddVerify }} - enrollment={{ - snapshot: enrollmentSnapshot, - send: sendEnrollment, - canSubmit: enrollmentActor.can({ type: 'SUBMIT' }), - }} - remove={{ snapshot: removeSnapshot, send: sendRemove }} - /> - ); -} diff --git a/packages/swingset/src/stories/organization-profile-general-panel.mdx b/packages/swingset/src/stories/organization-profile-general-panel.mdx deleted file mode 100644 index a5f5e040aa8..00000000000 --- a/packages/swingset/src/stories/organization-profile-general-panel.mdx +++ /dev/null @@ -1,27 +0,0 @@ -import * as OrganizationProfileGeneralPanelStories from './organization-profile-general-panel.stories'; - -# Organization Profile General Panel - -The General tab panel of the Organization Profile — composes the organization-level sections shown under "General". - - diff --git a/packages/swingset/src/stories/organization-profile-general-panel.stories.tsx b/packages/swingset/src/stories/organization-profile-general-panel.stories.tsx deleted file mode 100644 index 54ed789909a..00000000000 --- a/packages/swingset/src/stories/organization-profile-general-panel.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { OrganizationProfileGeneralPanelView } from '@clerk/ui/mosaic/organization/organization-profile-general-panel-view'; - -import type { StoryMeta } from '@/lib/types'; - -import { Default as OrganizationProfileDeleteSectionDemo } from './organization-profile-delete-section.stories'; -import { Default as OrganizationProfileDomainsSectionDemo } from './organization-profile-domains-section.stories'; -import { Default as OrganizationProfileLeaveSectionDemo } from './organization-profile-leave-section.stories'; -import { Default as OrganizationProfileProfileSectionDemo } from './organization-profile-profile-section.stories'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileGeneralPanel', - source: 'packages/ui/src/mosaic/organization/organization-profile-general-panel.tsx', -}; - -export function Default() { - return ( - } - domains={} - leaveOrganization={} - deleteOrganization={} - /> - ); -} diff --git a/packages/swingset/src/stories/organization-profile-leave-section.mdx b/packages/swingset/src/stories/organization-profile-leave-section.mdx deleted file mode 100644 index ce0be64a871..00000000000 --- a/packages/swingset/src/stories/organization-profile-leave-section.mdx +++ /dev/null @@ -1,16 +0,0 @@ -import * as OrganizationProfileLeaveSectionStories from './organization-profile-leave-section.stories'; - -# Organization Profile Leave Section - -A section that owns the open/leaving state and wires the `Destructive` block to the leave-organization flow. - - diff --git a/packages/swingset/src/stories/organization-profile-leave-section.stories.tsx b/packages/swingset/src/stories/organization-profile-leave-section.stories.tsx deleted file mode 100644 index e98a3111159..00000000000 --- a/packages/swingset/src/stories/organization-profile-leave-section.stories.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import { organizationProfileLeaveSectionMachine } from '@clerk/ui/mosaic/organization/organization-profile-leave-section.machine'; -import { OrganizationProfileLeaveSectionView } from '@clerk/ui/mosaic/organization/organization-profile-leave-section.view'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileLeaveSection', - source: 'packages/ui/src/mosaic/organization/organization-profile-leave-section.tsx', -}; - -export function Default() { - const [snapshot, send, actor] = useMachine(organizationProfileLeaveSectionMachine, { - context: { - organizationName: 'Acme Inc', - leaveOrganization: () => new Promise(resolve => setTimeout(resolve, 800)), - }, - }); - - return ( - - ); -} diff --git a/packages/swingset/src/stories/organization-profile-members-panel.mdx b/packages/swingset/src/stories/organization-profile-members-panel.mdx deleted file mode 100644 index 10bca05d65f..00000000000 --- a/packages/swingset/src/stories/organization-profile-members-panel.mdx +++ /dev/null @@ -1,17 +0,0 @@ -import * as OrganizationProfileMembersPanelStories from './organization-profile-members-panel.stories'; - -# Organization Profile Members Panel - -The Members tab panel of the Organization Profile — lists an organization's active members and owns the search and remove flows. It gates on `org:sys_memberships:read` to view and `org:sys_memberships:manage` to remove a member. - - diff --git a/packages/swingset/src/stories/organization-profile-members-panel.stories.tsx b/packages/swingset/src/stories/organization-profile-members-panel.stories.tsx deleted file mode 100644 index 8191d386541..00000000000 --- a/packages/swingset/src/stories/organization-profile-members-panel.stories.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import type { MemberRow } from '@clerk/ui/mosaic/organization/organization-profile-members-panel.controller'; -import { organizationProfileMembersPanelMachine } from '@clerk/ui/mosaic/organization/organization-profile-members-panel.machine'; -import { OrganizationProfileMembersPanelView } from '@clerk/ui/mosaic/organization/organization-profile-members-panel.view'; -import { useState } from 'react'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileMembersPanel', - source: 'packages/ui/src/mosaic/organization/organization-profile-members-panel.tsx', -}; - -const DEMO_MEMBERS: Omit[] = [ - { - id: 'mem_1', - name: 'Ada Lovelace', - identifier: 'ada@example.com', - roleLabel: 'Admin', - joinedAt: '1/12/2024', - isCurrentUser: true, - isBanned: false, - }, - { - id: 'mem_2', - name: 'Alan Turing', - identifier: 'alan@example.com', - roleLabel: 'Member', - joinedAt: '3/4/2024', - isCurrentUser: false, - isBanned: false, - }, - { - id: 'mem_3', - name: 'Grace Hopper', - identifier: 'grace@example.com', - roleLabel: 'Member', - joinedAt: '6/9/2024', - isCurrentUser: false, - isBanned: true, - }, -]; - -export function Default() { - const [snapshot, send] = useMachine(organizationProfileMembersPanelMachine); - const [page, setPage] = useState(1); - - const rows: MemberRow[] = DEMO_MEMBERS.map(member => ({ - ...member, - onRemove: () => - send({ - type: 'REMOVE_MEMBER', - membershipId: member.id, - // Simulate the network round-trip so the row shows its "Removing…" state. - run: () => new Promise(resolve => setTimeout(resolve, 700)), - }), - })); - - return ( - - ); -} diff --git a/packages/swingset/src/stories/organization-profile-profile-section.mdx b/packages/swingset/src/stories/organization-profile-profile-section.mdx deleted file mode 100644 index 5bba1e87ce1..00000000000 --- a/packages/swingset/src/stories/organization-profile-profile-section.mdx +++ /dev/null @@ -1,15 +0,0 @@ -import * as OrganizationProfileProfileSectionStories from './organization-profile-profile-section.stories'; - -# Organization Profile Profile Section - -A section that owns the open/editing/saving state for an organization's name and slug, wiring a `Dialog` around the edit form. Edits live as machine-owned drafts that fall through to the committed organization values, so the form seeds itself and closes on a successful save without a syncing effect. - - diff --git a/packages/swingset/src/stories/organization-profile-profile-section.stories.tsx b/packages/swingset/src/stories/organization-profile-profile-section.stories.tsx deleted file mode 100644 index 5ea047435f3..00000000000 --- a/packages/swingset/src/stories/organization-profile-profile-section.stories.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { useMachine } from '@clerk/ui/mosaic/machine/useMachine'; -import { OrganizationProfileProfileSectionView } from '@clerk/ui/mosaic/organization/organization-profile-profile-section.view'; -import { organizationProfileProfileSectionDetailsMachine } from '@clerk/ui/mosaic/organization/organization-profile-profile-section-details.machine'; - -import type { StoryMeta } from '@/lib/types'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfileProfileSection', - source: 'packages/ui/src/mosaic/organization/organization-profile-profile-section.tsx', -}; - -export function Default() { - const [snapshot, send, actor] = useMachine(organizationProfileProfileSectionDetailsMachine, { - context: { - committedName: 'Acme Inc', - committedSlug: 'acme', - slugEnabled: true, - updateOrganization: () => new Promise(resolve => setTimeout(resolve, 800)), - }, - }); - - return ( - - ); -} diff --git a/packages/swingset/src/stories/organization-profile.mdx b/packages/swingset/src/stories/organization-profile.mdx deleted file mode 100644 index b8ef6cd48be..00000000000 --- a/packages/swingset/src/stories/organization-profile.mdx +++ /dev/null @@ -1,23 +0,0 @@ -import * as OrganizationProfileStories from './organization-profile.stories'; - -# Organization Profile - -The full Organization Profile AIO — lays out the organization panels under a tabbed shell as a single view. - - diff --git a/packages/swingset/src/stories/organization-profile.stories.tsx b/packages/swingset/src/stories/organization-profile.stories.tsx deleted file mode 100644 index 0d389ec1ab2..00000000000 --- a/packages/swingset/src/stories/organization-profile.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { OrganizationProfileView } from '@clerk/ui/mosaic/organization/organization-profile-view'; - -import type { StoryMeta } from '@/lib/types'; - -import { Default as OrganizationProfileApiKeysPanelDemo } from './organization-profile-api-keys-panel.stories'; -import { Default as OrganizationProfileGeneralPanelDemo } from './organization-profile-general-panel.stories'; -import { Default as OrganizationProfileMembersPanelDemo } from './organization-profile-members-panel.stories'; - -export const meta: StoryMeta = { - group: 'Organization', - title: 'OrganizationProfile', - source: 'packages/ui/src/mosaic/organization/organization-profile.tsx', -}; - -export function Default() { - return ( - } - members={} - apiKeys={} - /> - ); -} diff --git a/packages/swingset/src/stories/popover.component.mdx b/packages/swingset/src/stories/popover.component.mdx index e1b8e2aecad..52350546230 100644 --- a/packages/swingset/src/stories/popover.component.mdx +++ b/packages/swingset/src/stories/popover.component.mdx @@ -186,7 +186,7 @@ them through your own typography (`Heading`, `Text`) inside the surface. ## Styling -Unlike the slot-recipe components, the Mosaic popover is themed with **StyleX**. Each styled part +The Mosaic popover is themed with **StyleX**. Each styled part carries a stable `.cl-` class (the slots in the table above) alongside the StyleX atoms. Consumers never target the hashed atomic classes — override by targeting the `.cl-*` slot from a CSS layer that wins over `@clerk/ui/styles.css`: diff --git a/packages/swingset/src/stories/popover.component.stories.tsx b/packages/swingset/src/stories/popover.component.stories.tsx index b775dbc7f64..60f8dc5af1e 100644 --- a/packages/swingset/src/stories/popover.component.stories.tsx +++ b/packages/swingset/src/stories/popover.component.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Button } from '@clerk/ui/mosaic/components/button'; import { Card } from '@clerk/ui/mosaic/components/card'; import { Heading } from '@clerk/ui/mosaic/components/heading'; @@ -15,7 +14,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Popover', source: 'packages/ui/src/mosaic/components/popover/popover.tsx', - styleEngine: 'stylex', }; export function Default() { diff --git a/packages/swingset/src/stories/scroll-area.stories.tsx b/packages/swingset/src/stories/scroll-area.stories.tsx index 3e64cd2b3ab..17994801316 100644 --- a/packages/swingset/src/stories/scroll-area.stories.tsx +++ b/packages/swingset/src/stories/scroll-area.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Avatar } from '@clerk/ui/mosaic/components/avatar'; import { Button } from '@clerk/ui/mosaic/components/button'; import { Item } from '@clerk/ui/mosaic/components/item'; @@ -212,8 +211,11 @@ export function HoverReveal() {
{manyRows.map(name => ( @@ -236,29 +238,36 @@ export function ThemedScrollbar() { const root = stylex.props(scrollAreaRoot); return ( -
- - {manyRows.map(name => ( - - ))} - -
+ <> + {/* Both halves of the amber → teal pair live here: an inline base value would outrank the + hover rule, since inline styles beat any stylesheet selector. */} + +
+ + {manyRows.map(name => ( + + ))} + +
+ ); } diff --git a/packages/swingset/src/stories/section.stories.tsx b/packages/swingset/src/stories/section.stories.tsx index 387b5173464..44dfec684c8 100644 --- a/packages/swingset/src/stories/section.stories.tsx +++ b/packages/swingset/src/stories/section.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Avatar } from '@clerk/ui/mosaic/components/avatar'; import { Badge } from '@clerk/ui/mosaic/components/badge'; import { Button } from '@clerk/ui/mosaic/components/button'; @@ -25,7 +24,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Section', source: 'packages/ui/src/mosaic/components/section/section.tsx', - styleEngine: 'stylex', }; export function Default() { diff --git a/packages/swingset/src/stories/tabs.component.mdx b/packages/swingset/src/stories/tabs.component.mdx deleted file mode 100644 index d22e625cfd8..00000000000 --- a/packages/swingset/src/stories/tabs.component.mdx +++ /dev/null @@ -1,58 +0,0 @@ -import * as TabsStories from './tabs.component.stories'; - -# Tabs - -The styled Mosaic `Tabs` — the headless `@clerk/headless` tabs primitives composed with Mosaic -slot recipes. It inherits selection state, roving-tabindex keyboard navigation, and ARIA wiring -(`role="tablist"` / `tab` / `tabpanel`) from the primitive, and adds Mosaic's themed styling for -each part. Slot identity (`data-cl-slot`) is applied by this styled layer, not by the headless -parts. - -## Example - -Click a tab or focus the list and use the arrow keys. The active tab is tracked by a sliding -`Indicator`; the third tab is `disabled`. - - - -## Usage - -```tsx -import { Tabs } from '@clerk/ui/mosaic/components/tabs'; - - - - Account - Password - - - Manage your account settings here. - Change your password here. -; -``` - -`Tab` participates in roving-tabindex keyboard navigation (arrow keys move focus). `Trigger` is a -click-only alternative for cases that don't want keyboard roving. Any part accepts a `render` prop -for polymorphic rendering — pass a function that receives the part's computed props and spreads -them onto your element. - -## Parts - -| Part | Slot | Description | -| ---------------- | ---------------- | ------------------------------------------------------------ | -| `Tabs.Root` | none (context) | Owns the selected value, orientation, and activation mode | -| `Tabs.List` | `tabs-list` | `role="tablist"` container; anchors the indicator | -| `Tabs.Tab` | `tabs-tab` | Selectable tab with roving-tabindex keyboard navigation | -| `Tabs.Trigger` | `tabs-trigger` | Click-only tab (no keyboard roving) | -| `Tabs.Panel` | `tabs-panel` | `role="tabpanel"`; hidden via `data-hidden` when inactive | -| `Tabs.Indicator` | `tabs-indicator` | Sliding underline tracking the active tab (positions inline) | - -## Styling - -Each styled part is themed by the Mosaic tabs recipe and stays targetable through its -`data-cl-slot` plus the state attributes the primitive emits (`data-selected`, -`data-disabled` on tabs; `data-hidden` on panels). Override per slot through -`appearance.elements` — e.g. `{ 'tabs-tab': { fontWeight: 600 } }`. diff --git a/packages/swingset/src/stories/tabs.component.stories.tsx b/packages/swingset/src/stories/tabs.component.stories.tsx deleted file mode 100644 index b037ef09938..00000000000 --- a/packages/swingset/src/stories/tabs.component.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { Tabs } from '@clerk/ui/mosaic/components/tabs'; - -import type { StoryMeta } from '@/lib/types'; - -// Exposes this file's own source (via the `?raw` webpack rule) so each `` example -// renders a code footer with its function's source. See `StoryModule.__source`. -export { default as __source } from './tabs.component.stories?raw'; - -export const meta: StoryMeta = { - group: 'Components', - title: 'Tabs', - source: 'packages/ui/src/mosaic/components/tabs.tsx', -}; - -export function Default() { - return ( - - - Account - Password - - Disabled - - - - Manage your account settings here. - Change your password here. - This panel is unreachable. - - ); -} diff --git a/packages/swingset/src/stories/text.stories.tsx b/packages/swingset/src/stories/text.stories.tsx index 0b5a4860917..2fba0639dcf 100644 --- a/packages/swingset/src/stories/text.stories.tsx +++ b/packages/swingset/src/stories/text.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import type { TextProps } from '@clerk/ui/mosaic/components/text'; import { Text } from '@clerk/ui/mosaic/components/text'; @@ -12,7 +11,6 @@ export const meta: StoryMeta = { group: 'Components', title: 'Text', source: 'packages/ui/src/mosaic/components/text/text.tsx', - styleEngine: 'stylex', styles: { _variants: { size: { xs: {}, sm: {}, base: {}, lg: {}, xl: {}, '2xl': {} }, diff --git a/packages/swingset/src/stories/user-button.stories.tsx b/packages/swingset/src/stories/user-button.stories.tsx index 88cbe8836f3..06e1b30a79b 100644 --- a/packages/swingset/src/stories/user-button.stories.tsx +++ b/packages/swingset/src/stories/user-button.stories.tsx @@ -1,4 +1,3 @@ -/** @jsxImportSource @emotion/react */ import { Icon } from '@clerk/ui/mosaic/components/icon'; import { userButtonBusyKeys, diff --git a/packages/swingset/src/types/global.d.ts b/packages/swingset/src/types/global.d.ts index 5478227e618..4fbe7772b46 100644 --- a/packages/swingset/src/types/global.d.ts +++ b/packages/swingset/src/types/global.d.ts @@ -1,5 +1,3 @@ -/// - declare module '*.css'; declare module '*.mdx' { diff --git a/packages/swingset/src/types/react.d.ts b/packages/swingset/src/types/react.d.ts new file mode 100644 index 00000000000..cec85bdcf3a --- /dev/null +++ b/packages/swingset/src/types/react.d.ts @@ -0,0 +1,8 @@ +import type {} from 'react'; + +declare module 'react' { + // Mosaic is themed through `--cl-*` custom properties, which stories set inline. + interface CSSProperties { + [key: `--${string}`]: string | number | undefined; + } +} diff --git a/packages/ui/src/mosaic/MosaicProvider.tsx b/packages/ui/src/mosaic/MosaicProvider.tsx index 1a817750aed..60e2b464d01 100644 --- a/packages/ui/src/mosaic/MosaicProvider.tsx +++ b/packages/ui/src/mosaic/MosaicProvider.tsx @@ -1,126 +1,19 @@ -// eslint-disable-next-line no-restricted-imports -import createCache from '@emotion/cache'; -// eslint-disable-next-line no-restricted-imports -import { CacheProvider } from '@emotion/react'; import React from 'react'; -import type { MosaicAppearance } from './appearance'; -import { MosaicAppearanceProvider, MosaicIconsProvider, parseMosaicAppearance } from './appearance'; -import type { MosaicTheme } from './variables'; -import { defaultMosaicVariables, resolveVariables } from './variables'; - -const INSERTION_POINT_ID = 'cl-mosaic-style-insertion-point'; - -// Anchor Emotion's