@sableui/react 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +87 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/DESIGN.md +7 -7
  5. package/README.md +2 -2
  6. package/bin/sable-lint.mjs +0 -0
  7. package/bin/sable.mjs +4 -3
  8. package/dist/{_contract-BlKMR4eX.d.ts → _contract-Cn3lUpih.d.ts} +7 -7
  9. package/dist/blocks/index.js +4 -4
  10. package/dist/{chunk-YXBOYWBN.js → chunk-C4Z3CF54.js} +1 -1
  11. package/dist/{chunk-MXJX3NZI.js → chunk-EFLC4NA3.js} +4 -4
  12. package/dist/{chunk-EP55KSYA.js → chunk-EHH4MHO6.js} +6 -6
  13. package/dist/{chunk-FBSL2LZQ.js → chunk-GU3T7SBA.js} +11 -30
  14. package/dist/{chunk-NGBDP3BM.js → chunk-QJ6FWTUG.js} +1 -1
  15. package/dist/{chunk-CCUDFLU3.js → chunk-RCLKXF2N.js} +43 -9
  16. package/dist/{chunk-TH563WLW.js → chunk-XAZGMY73.js} +10 -3
  17. package/dist/{chunk-D2ETQBHV.js → chunk-ZBZMKOO7.js} +4 -4
  18. package/dist/icon/index.js +2 -2
  19. package/dist/icon-sets/brands.d.ts +0 -27
  20. package/dist/icon-sets/brands.js +7 -1
  21. package/dist/icon-sets/reicon.js +1 -1
  22. package/dist/index.d.ts +44 -19
  23. package/dist/index.js +104 -27
  24. package/dist/patterns/index.js +4 -4
  25. package/dist/presets/index.d.ts +1 -1
  26. package/dist/presets/index.js +1 -1
  27. package/dist/styles.css +19 -11
  28. package/dist/{theme-provider-CuYYxF0o.d.ts → theme-provider-CGeiusgu.d.ts} +3 -3
  29. package/dist/themes/atelier/index.d.ts +1 -1
  30. package/dist/themes/atelier/index.js +1 -1
  31. package/dist/themes/ember/ember.css +8 -5
  32. package/dist/themes/ember/index.d.ts +1 -1
  33. package/dist/themes/index.d.ts +3 -3
  34. package/dist/themes/index.js +3 -3
  35. package/dist/themes/meridian/index.d.ts +1 -1
  36. package/dist/themes/poppy/index.d.ts +1 -1
  37. package/dist/themes/poppy/poppy.css +1 -1
  38. package/dist/themes/prism/index.d.ts +1 -1
  39. package/dist/themes/sable/index.d.ts +9 -0
  40. package/dist/themes/sable/index.js +1 -0
  41. package/dist/themes/{base/base.css → sable/sable.css} +6 -6
  42. package/dist/themes/verdant/index.d.ts +1 -1
  43. package/dist/themes.css +3 -3
  44. package/llms/components/tabs.md +1 -1
  45. package/llms/components/theme-selector.md +1 -1
  46. package/llms/components/toast.md +12 -1
  47. package/llms/reference/token-intent.md +1 -1
  48. package/llms/tasks/building-a-page.md +1 -1
  49. package/llms/tasks/creating-a-preset.md +1 -1
  50. package/llms/tasks/designing-a-surface.md +1 -1
  51. package/llms-full.txt +19 -8
  52. package/llms.txt +2 -2
  53. package/package.json +5 -5
  54. package/registry.json +43 -14
  55. package/scripts/design-presets.mjs +1 -1
  56. package/scripts/grade.mjs +18 -5
  57. package/scripts/lint-rules.mjs +20 -1
  58. package/dist/themes/base/index.d.ts +0 -9
  59. package/dist/themes/base/index.js +0 -1
package/dist/themes.css CHANGED
@@ -6,8 +6,8 @@
6
6
  @import "@sableui/react/themes.css"; ← this file
7
7
 
8
8
  For an app that offers the bundled set as user-selectable themes (a
9
- `<ThemeSelector>` over Base / Meridian / Prism / Verdant / Ember / Poppy). `styles.css`
10
- carries Base alone, since the alternates are `[data-sable-theme]`-scoped
9
+ `<ThemeSelector>` over Sable / Meridian / Prism / Verdant / Ember / Poppy). `styles.css`
10
+ carries the Sable theme alone, since the alternates are `[data-sable-theme]`-scoped
11
11
  and dead weight in the overwhelmingly common single-theme app.
12
12
 
13
13
  Needing exactly one alternate? Import just that one instead — this file is a
@@ -15,7 +15,7 @@
15
15
 
16
16
  @import "@sableui/react/theme/prism.css";
17
17
 
18
- Base is NOT here: it ships in `styles.css` and must, since it declares
18
+ The Sable theme is NOT here: it ships in `styles.css` and must, since it declares
19
19
  the layout floor (`--page-inset`, `--region-*`, `--rhythm-unit`) on a bare
20
20
  `:root` that every other theme inherits.
21
21
  ========================================================================== */
@@ -31,7 +31,7 @@ Use Tabs to switch between related panels within the same page context.
31
31
 
32
32
  | Name | Type | Description |
33
33
  | --- | --- | --- |
34
- | TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger. Triggers inherit the variant. |
34
+ | TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger, which takes the theme's button shape (--button-radius). Triggers inherit the variant. |
35
35
  | TabsTrigger | Base UI Tabs.Tab props (value, disabled) + className | A single tab button. value pairs it with the panel of the same value. |
36
36
  | TabsPanel | Base UI Tabs.Panel props (value, keepMounted) + className | The content shown when the matching trigger is active. |
37
37
  | TabsContent | Alias of TabsPanel | Re-export of TabsPanel for consumers that prefer this name. |
@@ -62,7 +62,7 @@ const { themeId, setThemeId, themes, mode } = useSableTheme()
62
62
  ```tsx
63
63
  import { ThemeSelector, type ThemeId } from "@sableui/react"
64
64
 
65
- const [themeId, setThemeId] = useState<ThemeId>("base")
65
+ const [themeId, setThemeId] = useState<ThemeId>("sable")
66
66
  <ThemeSelector value={themeId} onValueChange={setThemeId} />
67
67
  ```
68
68
 
@@ -34,6 +34,7 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
34
34
  | Name | Type | Description |
35
35
  | --- | --- | --- |
36
36
  | ToastRoot | Base UI Toast.Root props + className (requires the `toast` object) | A single toast surface. Animates enter/leave via data-[starting-style]/data-[ending-style]; swipe-to-dismiss is wired by Base UI. |
37
+ | ToastContent | Base UI Toast.Content props + className | The toast's contents as one row. In a stacked viewport it fades on the cards behind the front one and returns when the stack fans out. Wrap a hand-composed toast's parts in it so a stacked viewport can hide them. |
37
38
  | ToastTitle | Base UI Toast.Title props + className | The toast headline (text-title-sm). Auto-associated as the toast's accessible name. |
38
39
  | ToastDescription | Base UI Toast.Description props + className | Supporting body copy beneath the title (text-body-sm, muted). Auto-associated as the toast's accessible description. |
39
40
  | ToastClose | Base UI Toast.Close props + className | Dismiss button. Base UI handles the close and leave animation; labelled "Dismiss" by default. |
@@ -43,8 +44,9 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
43
44
 
44
45
  | Name | Type | Default | Description |
45
46
  | --- | --- | --- | --- |
46
- | ToastProvider | { children, timeout?, limit?, closeIcon?, position?, viewportClassName? } | — | Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast(). |
47
+ | ToastProvider | { children, timeout?, limit?, closeIcon?, position?, stacked?, viewportClassName? } | — | Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast(). |
47
48
  | position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "top-right" | Screen anchor for the toast stack. |
49
+ | stacked | boolean | false | Stack the toasts like a deck of cards, the newest in front and the older ones peeking out behind it, a step smaller, instead of listing them one under another. Hovering or focusing the stack fans it out into the list. Use it when an app can raise several toasts in a row, so they don't take over the corner of the page. |
48
50
  | viewportClassName | string | — | Extra classes merged LAST onto the fixed viewport region — for safe-area insets or an offset (e.g. clearing a sidebar). |
49
51
  | timeout | number | 5000 | Default auto-dismiss time in ms for toasts. 0 keeps a toast until it is dismissed. Error toasts, loading toasts, and any toast carrying actionProps default to 0 regardless of this value; a per-toast timeout overrides both. |
50
52
  | limit | number | 3 | Maximum toasts shown at once; older toasts are pushed out when the limit is exceeded. |
@@ -62,6 +64,7 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
62
64
  - **arrival** · ToastRoot · `motion-slide-left` — A toast interrupts without being asked, so where it came from is the reader's first question. Sliding in from the viewport edge answers it and marks the toast as belonging to the edge rather than to whatever it happens to cover.
63
65
  - **response** · ToastAction · `motion-interactive` — Toasts are on a timer, so the action has to look actionable at a glance — there is no time to hunt for the hit target.
64
66
  - **transition** · ToastRoot · `motion-slide-left` — Leaving the same way it entered keeps the stack legible when several dismiss at once; toasts that fade in place leave the remaining ones jumping to fill gaps with no visible cause.
67
+ - **transition** · ToastRoot (stacked) · _custom (outside the recipe layer)_ — A stacked toast arrives from the anchored edge in front of the others, which step back and shrink to make room; hovering fans the deck out into the list. Moving every card, rather than snapping them, shows that the new one went on top and the old ones are still there.
65
68
  - **activity** · ToastRoot · _no motion_ — A toast reports a result that has already happened. Anything still in flight belongs in the surface that started it, where the reader is still looking.
66
69
 
67
70
  ## Accessibility
@@ -121,6 +124,14 @@ import { toast } from "@sableui/react"
121
124
  toast.message("Post archived", { actionProps: { children: "Undo", onClick: () => restorePost() } })
122
125
  ```
123
126
 
127
+ ### Stacked
128
+
129
+ ```tsx
130
+ import { ToastProvider } from "@sableui/react"
131
+
132
+ <ToastProvider stacked position="bottom-right">{children}</ToastProvider>
133
+ ```
134
+
124
135
  ### Provider with app chrome
125
136
 
126
137
  ```tsx
@@ -319,7 +319,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
319
319
  - **`panel-raised`** — One step up from `panel`: a selected row, a hover fill, a nested well. *Use for:* Tree row selection, hovered panel rows, and the light square of a checkerboard.
320
320
  - **Not** `accent`. In a workspace the tonal band is only a few units wide; `accent` is a page-register step and reads as a shout in chrome.
321
321
  - **`panel-field`** — The inset fill of a compact control docked in a panel. *Use for:* The `size="xs"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant="field">`. A panel control of your own that should sit among those fields.
322
- - **Not** `canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.
322
+ - **Not** `canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.
323
323
  - **`panel-field-hover`** — The hover step of a `panel-field` control. *Use for:* `hover:bg-panel-field-hover` on anything filled with `panel-field`.
324
324
  - **Not** `hover:bg-panel-raised`. A fixed surface token as a hover is what the surface-hover lint rejects; this one is defined against the panel field it steps from.
325
325
  - **`hairline`** — The 1px structural line that carries all separation in tool chrome. *Use for:* Between panel sections, under a toolbar, above a status bar.
@@ -62,7 +62,7 @@ chosen by what the surface *does*:
62
62
  - `secondary` — a control fill (something you can press)
63
63
  - `accent` — a transient state (hover, selected, current)
64
64
 
65
- Pick by appearance and it works in Base and breaks in Meridian. Every token's
65
+ Pick by appearance and it works in the Sable theme and breaks in Meridian. Every token's
66
66
  intent, and what it gets confused with, is in
67
67
  `llms/reference/token-intent.md`.
68
68
 
@@ -21,7 +21,7 @@ pnpm exec sable preset --check .sable/presets/clay.json --json
21
21
  pnpm exec sable preset --css .sable/presets/clay.json
22
22
  ```
23
23
 
24
- The template starts from a complete Base theme snapshot. Give it a unique ID and
24
+ The template starts from a complete Sable theme snapshot. Give it a unique ID and
25
25
  label; author its tokens, direction, and reference observations. Avoid IDs of
26
26
  bundled themes. Save the record at `.sable/presets/<id>.json`. Advance `revision`
27
27
  when changing a shared record; keep earlier revisions when reproducibility matters.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Tokens make decisions consistent; they do not decide what deserves attention.
4
4
  Start with the user's task and content, then choose a visual direction. Sable's
5
- default Base identity is a starting point. A consumer can be light-first,
5
+ default Sable identity is a starting point. A consumer can be light-first,
6
6
  photographic, editorial, playful, or quiet while retaining semantic tokens,
7
7
  accessible behavior, and a coherent interaction policy.
8
8
 
package/llms-full.txt CHANGED
@@ -243,7 +243,7 @@ On npm as `@sableui/react`. The structured recipe is the `consume` block in `reg
243
243
  @source "<path-to>/@sableui/react/dist/**/*.{js,mjs}"; /* workspace: packages/sable/src; published: node_modules/@sableui/react/dist */
244
244
  ```
245
245
 
246
- 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — base: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
246
+ 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — sable: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
247
247
  5. **Providers + theme switching:**
248
248
 
249
249
  ```tsx
@@ -653,7 +653,7 @@ chosen by what the surface *does*:
653
653
  - `secondary` — a control fill (something you can press)
654
654
  - `accent` — a transient state (hover, selected, current)
655
655
 
656
- Pick by appearance and it works in Base and breaks in Meridian. Every token's
656
+ Pick by appearance and it works in the Sable theme and breaks in Meridian. Every token's
657
657
  intent, and what it gets confused with, is in
658
658
  `llms/reference/token-intent.md`.
659
659
 
@@ -1309,7 +1309,7 @@ pnpm exec sable preset --check .sable/presets/clay.json --json
1309
1309
  pnpm exec sable preset --css .sable/presets/clay.json
1310
1310
  ```
1311
1311
 
1312
- The template starts from a complete Base theme snapshot. Give it a unique ID and
1312
+ The template starts from a complete Sable theme snapshot. Give it a unique ID and
1313
1313
  label; author its tokens, direction, and reference observations. Avoid IDs of
1314
1314
  bundled themes. Save the record at `.sable/presets/<id>.json`. Advance `revision`
1315
1315
  when changing a shared record; keep earlier revisions when reproducibility matters.
@@ -1435,7 +1435,7 @@ requirement that every future brand use the same illustration or font pairing.
1435
1435
 
1436
1436
  Tokens make decisions consistent; they do not decide what deserves attention.
1437
1437
  Start with the user's task and content, then choose a visual direction. Sable's
1438
- default Base identity is a starting point. A consumer can be light-first,
1438
+ default Sable identity is a starting point. A consumer can be light-first,
1439
1439
  photographic, editorial, playful, or quiet while retaining semantic tokens,
1440
1440
  accessible behavior, and a coherent interaction policy.
1441
1441
 
@@ -18634,7 +18634,7 @@ Use Tabs to switch between related panels within the same page context.
18634
18634
 
18635
18635
  | Name | Type | Description |
18636
18636
  | --- | --- | --- |
18637
- | TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger. Triggers inherit the variant. |
18637
+ | TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger, which takes the theme's button shape (--button-radius). Triggers inherit the variant. |
18638
18638
  | TabsTrigger | Base UI Tabs.Tab props (value, disabled) + className | A single tab button. value pairs it with the panel of the same value. |
18639
18639
  | TabsPanel | Base UI Tabs.Panel props (value, keepMounted) + className | The content shown when the matching trigger is active. |
18640
18640
  | TabsContent | Alias of TabsPanel | Re-export of TabsPanel for consumers that prefer this name. |
@@ -18843,7 +18843,7 @@ const { themeId, setThemeId, themes, mode } = useSableTheme()
18843
18843
  ```tsx
18844
18844
  import { ThemeSelector, type ThemeId } from "@sableui/react"
18845
18845
 
18846
- const [themeId, setThemeId] = useState<ThemeId>("base")
18846
+ const [themeId, setThemeId] = useState<ThemeId>("sable")
18847
18847
  <ThemeSelector value={themeId} onValueChange={setThemeId} />
18848
18848
  ```
18849
18849
 
@@ -19081,6 +19081,7 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
19081
19081
  | Name | Type | Description |
19082
19082
  | --- | --- | --- |
19083
19083
  | ToastRoot | Base UI Toast.Root props + className (requires the `toast` object) | A single toast surface. Animates enter/leave via data-[starting-style]/data-[ending-style]; swipe-to-dismiss is wired by Base UI. |
19084
+ | ToastContent | Base UI Toast.Content props + className | The toast's contents as one row. In a stacked viewport it fades on the cards behind the front one and returns when the stack fans out. Wrap a hand-composed toast's parts in it so a stacked viewport can hide them. |
19084
19085
  | ToastTitle | Base UI Toast.Title props + className | The toast headline (text-title-sm). Auto-associated as the toast's accessible name. |
19085
19086
  | ToastDescription | Base UI Toast.Description props + className | Supporting body copy beneath the title (text-body-sm, muted). Auto-associated as the toast's accessible description. |
19086
19087
  | ToastClose | Base UI Toast.Close props + className | Dismiss button. Base UI handles the close and leave animation; labelled "Dismiss" by default. |
@@ -19090,8 +19091,9 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
19090
19091
 
19091
19092
  | Name | Type | Default | Description |
19092
19093
  | --- | --- | --- | --- |
19093
- | ToastProvider | { children, timeout?, limit?, closeIcon?, position?, viewportClassName? } | — | Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast(). |
19094
+ | ToastProvider | { children, timeout?, limit?, closeIcon?, position?, stacked?, viewportClassName? } | — | Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast(). |
19094
19095
  | position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "top-right" | Screen anchor for the toast stack. |
19096
+ | stacked | boolean | false | Stack the toasts like a deck of cards, the newest in front and the older ones peeking out behind it, a step smaller, instead of listing them one under another. Hovering or focusing the stack fans it out into the list. Use it when an app can raise several toasts in a row, so they don't take over the corner of the page. |
19095
19097
  | viewportClassName | string | — | Extra classes merged LAST onto the fixed viewport region — for safe-area insets or an offset (e.g. clearing a sidebar). |
19096
19098
  | timeout | number | 5000 | Default auto-dismiss time in ms for toasts. 0 keeps a toast until it is dismissed. Error toasts, loading toasts, and any toast carrying actionProps default to 0 regardless of this value; a per-toast timeout overrides both. |
19097
19099
  | limit | number | 3 | Maximum toasts shown at once; older toasts are pushed out when the limit is exceeded. |
@@ -19109,6 +19111,7 @@ Use Toast for transient feedback caused by a user action: saved, failed, copied,
19109
19111
  - **arrival** · ToastRoot · `motion-slide-left` — A toast interrupts without being asked, so where it came from is the reader's first question. Sliding in from the viewport edge answers it and marks the toast as belonging to the edge rather than to whatever it happens to cover.
19110
19112
  - **response** · ToastAction · `motion-interactive` — Toasts are on a timer, so the action has to look actionable at a glance — there is no time to hunt for the hit target.
19111
19113
  - **transition** · ToastRoot · `motion-slide-left` — Leaving the same way it entered keeps the stack legible when several dismiss at once; toasts that fade in place leave the remaining ones jumping to fill gaps with no visible cause.
19114
+ - **transition** · ToastRoot (stacked) · _custom (outside the recipe layer)_ — A stacked toast arrives from the anchored edge in front of the others, which step back and shrink to make room; hovering fans the deck out into the list. Moving every card, rather than snapping them, shows that the new one went on top and the old ones are still there.
19112
19115
  - **activity** · ToastRoot · _no motion_ — A toast reports a result that has already happened. Anything still in flight belongs in the surface that started it, where the reader is still looking.
19113
19116
 
19114
19117
  ## Accessibility
@@ -19168,6 +19171,14 @@ import { toast } from "@sableui/react"
19168
19171
  toast.message("Post archived", { actionProps: { children: "Undo", onClick: () => restorePost() } })
19169
19172
  ```
19170
19173
 
19174
+ ### Stacked
19175
+
19176
+ ```tsx
19177
+ import { ToastProvider } from "@sableui/react"
19178
+
19179
+ <ToastProvider stacked position="bottom-right">{children}</ToastProvider>
19180
+ ```
19181
+
19171
19182
  ### Provider with app chrome
19172
19183
 
19173
19184
  ```tsx
@@ -20304,7 +20315,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
20304
20315
  - **`panel-raised`** — One step up from `panel`: a selected row, a hover fill, a nested well. *Use for:* Tree row selection, hovered panel rows, and the light square of a checkerboard.
20305
20316
  - **Not** `accent`. In a workspace the tonal band is only a few units wide; `accent` is a page-register step and reads as a shout in chrome.
20306
20317
  - **`panel-field`** — The inset fill of a compact control docked in a panel. *Use for:* The `size="xs"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant="field">`. A panel control of your own that should sit among those fields.
20307
- - **Not** `canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.
20318
+ - **Not** `canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.
20308
20319
  - **`panel-field-hover`** — The hover step of a `panel-field` control. *Use for:* `hover:bg-panel-field-hover` on anything filled with `panel-field`.
20309
20320
  - **Not** `hover:bg-panel-raised`. A fixed surface token as a hover is what the surface-hover lint rejects; this one is defined against the panel field it steps from.
20310
20321
  - **`hairline`** — The 1px structural line that carries all separation in tool chrome. *Use for:* Between panel sections, under a toolbar, above a status bar.
package/llms.txt CHANGED
@@ -243,7 +243,7 @@ On npm as `@sableui/react`. The structured recipe is the `consume` block in `reg
243
243
  @source "<path-to>/@sableui/react/dist/**/*.{js,mjs}"; /* workspace: packages/sable/src; published: node_modules/@sableui/react/dist */
244
244
  ```
245
245
 
246
- 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — base: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
246
+ 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — sable: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
247
247
  5. **Providers + theme switching:**
248
248
 
249
249
  ```tsx
@@ -446,7 +446,7 @@ import { Button, Card, IconProvider, MotionProvider, SableThemeProvider, useSabl
446
446
  - **ThemeSelector** — A presentational radiogroup for choosing a Sable design theme from theme manifests — swatch-strip cards for a settings page, or compact rows for tool chrome. → [`llms/components/theme-selector.md`](./llms/components/theme-selector.md)
447
447
  - **ThemeSwitcher** — A segmented light / dark / system switch. → [`llms/components/theme-switcher.md`](./llms/components/theme-switcher.md)
448
448
  - **ThinkingOrb** — Canvas dot-cloud 'agent is thinking' indicator. → [`llms/components/thinking-orb.md`](./llms/components/thinking-orb.md)
449
- - **Toast** — Imperative notification system over Base UI Toast — auto-dismiss timers, swipe-to-dismiss, focus management, ARIA live-region announcements, and a limit-aware stack. *Parts:* ToastRoot, ToastTitle, ToastDescription, ToastClose, ToastAction. → [`llms/components/toast.md`](./llms/components/toast.md)
449
+ - **Toast** — Imperative notification system over Base UI Toast — auto-dismiss timers, swipe-to-dismiss, focus management, ARIA live-region announcements, and a limit-aware stack. *Parts:* ToastRoot, ToastContent, ToastTitle, ToastDescription, ToastClose, ToastAction. → [`llms/components/toast.md`](./llms/components/toast.md)
450
450
  - **Toggle** — Pressed-state button over Base UI Toggle, plus ToggleGroup for shared single- or multi-select rows. *Parts:* ToggleGroup, ToggleGroupItem. → [`llms/components/toggle.md`](./llms/components/toggle.md)
451
451
  - **Toolbar** — A row or column of controls that is one tab stop, with arrow-key movement between its items (role="toolbar"), over Base UI Toolbar. *Parts:* ToolbarButton, ToolbarLink, ToolbarInput, ToolbarGroup, ToolbarSeparator. → [`llms/components/toolbar.md`](./llms/components/toolbar.md)
452
452
  - **Tooltip** — Hover/focus label over Base UI Tooltip (collision-aware positioning, ARIA wiring, delay grouping via TooltipProvider). *Parts:* TooltipProvider, TooltipTrigger, TooltipContent, TooltipArrow, SimpleTooltip. → [`llms/components/tooltip.md`](./llms/components/tooltip.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sableui/react",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Sable: themeable React components, layout patterns, and tools for coding agents.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -71,11 +71,11 @@
71
71
  "default": "./dist/themes/ember/index.js"
72
72
  },
73
73
  "./theme/ember.css": "./dist/themes/ember/ember.css",
74
- "./theme/base": {
75
- "types": "./dist/themes/base/index.d.ts",
76
- "default": "./dist/themes/base/index.js"
74
+ "./theme/sable": {
75
+ "types": "./dist/themes/sable/index.d.ts",
76
+ "default": "./dist/themes/sable/index.js"
77
77
  },
78
- "./theme/base.css": "./dist/themes/base/base.css",
78
+ "./theme/sable.css": "./dist/themes/sable/sable.css",
79
79
  "./theme/poppy": {
80
80
  "types": "./dist/themes/poppy/index.d.ts",
81
81
  "default": "./dist/themes/poppy/index.js"
package/registry.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://sable.design/registry.schema.json",
3
3
  "name": "@sableui/react",
4
- "version": "0.3.2",
5
- "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Base.",
4
+ "version": "0.5.0",
5
+ "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
6
6
  "engine": {
7
7
  "behavior": "@base-ui/react",
8
8
  "styling": "tailwindcss@4",
@@ -42,9 +42,9 @@
42
42
  "designTheme": "Set data-sable-theme for alternate or app-owned themes; omit the attribute or use base for the default. Register portable presets with presetManifest() through SableThemeProvider customThemes, and load their scoped CSS and fonts.",
43
43
  "import": "import { Button, Card } from \"@sableui/react\"",
44
44
  "cssOptional": {
45
- "themes.css": "@import \"@sableui/react/themes.css\"; — all bundled alternatives. styles.css already includes Base. Alternate CSS is scoped by data-sable-theme.",
45
+ "themes.css": "@import \"@sableui/react/themes.css\"; — all bundled alternatives. styles.css already includes the Sable theme. Alternate CSS is scoped by data-sable-theme.",
46
46
  "theme/<id>.css": "@import \"@sableui/react/theme/prism.css\"; — one bundled alternative. Prefer this when the app uses exactly one.",
47
- "symptom": "The active theme renders as Base when its stylesheet has not been imported. Load the selected theme CSS before applying its ID."
47
+ "symptom": "The active theme renders as the Sable theme when its stylesheet has not been imported. Load the selected theme CSS before applying its ID."
48
48
  },
49
49
  "designWorkflow": {
50
50
  "brief": "sable docs designing-a-surface",
@@ -91,14 +91,14 @@
91
91
  }
92
92
  },
93
93
  "themes": {
94
- "default": "base",
94
+ "default": "sable",
95
95
  "available": [
96
96
  {
97
- "id": "base",
98
- "label": "Base",
97
+ "id": "sable",
98
+ "label": "Sable",
99
99
  "featured": true,
100
- "css": "./src/themes/base/base.css",
101
- "manifest": "./src/themes/base/index.ts",
100
+ "css": "./src/themes/sable/sable.css",
101
+ "manifest": "./src/themes/sable/index.ts",
102
102
  "import": "@sableui/react/styles.css",
103
103
  "modes": [
104
104
  "dark",
@@ -233,7 +233,7 @@
233
233
  "id": "atelier",
234
234
  "label": "Atelier",
235
235
  "deprecated": {
236
- "replacement": "base"
236
+ "replacement": "sable"
237
237
  },
238
238
  "featured": false,
239
239
  "css": "./src/themes/atelier/atelier.css",
@@ -1313,7 +1313,7 @@
1313
1313
  "name": "panel-field",
1314
1314
  "intent": "The inset fill of a compact control docked in a panel.",
1315
1315
  "whenToUse": "The `size=\"xs\"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant=\"field\">`. A panel control of your own that should sit among those fields.",
1316
- "insteadOf": "`canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.",
1316
+ "insteadOf": "`canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.",
1317
1317
  "pairsWith": [
1318
1318
  "panel-field-hover"
1319
1319
  ]
@@ -16798,7 +16798,7 @@
16798
16798
  {
16799
16799
  "name": "TabsList",
16800
16800
  "type": "Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: \"underline\" | \"pill\" + className",
16801
- "description": "The tablist row. variant=\"underline\" (default) draws a divider rule beneath the triggers; variant=\"pill\" drops the rule and fills the active trigger. Triggers inherit the variant."
16801
+ "description": "The tablist row. variant=\"underline\" (default) draws a divider rule beneath the triggers; variant=\"pill\" drops the rule and fills the active trigger, which takes the theme's button shape (--button-radius). Triggers inherit the variant."
16802
16802
  },
16803
16803
  {
16804
16804
  "name": "TabsTrigger",
@@ -17085,7 +17085,7 @@
17085
17085
  },
17086
17086
  {
17087
17087
  "label": "Controlled",
17088
- "code": "const [themeId, setThemeId] = useState<ThemeId>(\"base\")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />",
17088
+ "code": "const [themeId, setThemeId] = useState<ThemeId>(\"sable\")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />",
17089
17089
  "imports": "import { ThemeSelector, type ThemeId } from \"@sableui/react\""
17090
17090
  },
17091
17091
  {
@@ -17450,7 +17450,7 @@
17450
17450
  "props": [
17451
17451
  {
17452
17452
  "name": "ToastProvider",
17453
- "type": "{ children, timeout?, limit?, closeIcon?, position?, viewportClassName? }",
17453
+ "type": "{ children, timeout?, limit?, closeIcon?, position?, stacked?, viewportClassName? }",
17454
17454
  "default": "—",
17455
17455
  "description": "Wraps Base UI Toast.Provider and renders the themed Viewport + toast list. Mount once near the app root, above any caller of useToast()."
17456
17456
  },
@@ -17460,6 +17460,12 @@
17460
17460
  "default": "\"top-right\"",
17461
17461
  "description": "Screen anchor for the toast stack."
17462
17462
  },
17463
+ {
17464
+ "name": "stacked",
17465
+ "type": "boolean",
17466
+ "default": "false",
17467
+ "description": "Stack the toasts like a deck of cards, the newest in front and the older ones peeking out behind it, a step smaller, instead of listing them one under another. Hovering or focusing the stack fans it out into the list. Use it when an app can raise several toasts in a row, so they don't take over the corner of the page."
17468
+ },
17463
17469
  {
17464
17470
  "name": "viewportClassName",
17465
17471
  "type": "string",
@@ -17508,6 +17514,11 @@
17508
17514
  "type": "Base UI Toast.Root props + className (requires the `toast` object)",
17509
17515
  "description": "A single toast surface. Animates enter/leave via data-[starting-style]/data-[ending-style]; swipe-to-dismiss is wired by Base UI."
17510
17516
  },
17517
+ {
17518
+ "name": "ToastContent",
17519
+ "type": "Base UI Toast.Content props + className",
17520
+ "description": "The toast's contents as one row. In a stacked viewport it fades on the cards behind the front one and returns when the stack fans out. Wrap a hand-composed toast's parts in it so a stacked viewport can hide them."
17521
+ },
17511
17522
  {
17512
17523
  "name": "ToastTitle",
17513
17524
  "type": "Base UI Toast.Title props + className",
@@ -17563,6 +17574,12 @@
17563
17574
  "part": "ToastRoot",
17564
17575
  "why": "Leaving the same way it entered keeps the stack legible when several dismiss at once; toasts that fade in place leave the remaining ones jumping to fill gaps with no visible cause."
17565
17576
  },
17577
+ {
17578
+ "slot": "transition",
17579
+ "recipe": "custom",
17580
+ "part": "ToastRoot (stacked)",
17581
+ "why": "A stacked toast arrives from the anchored edge in front of the others, which step back and shrink to make room; hovering fans the deck out into the list. Moving every card, rather than snapping them, shows that the new one went on top and the old ones are still there."
17582
+ },
17566
17583
  {
17567
17584
  "slot": "activity",
17568
17585
  "recipe": "none",
@@ -17604,6 +17621,11 @@
17604
17621
  "code": "toast.message(\"Post archived\", { actionProps: { children: \"Undo\", onClick: () => restorePost() } })",
17605
17622
  "imports": "import { toast } from \"@sableui/react\""
17606
17623
  },
17624
+ {
17625
+ "label": "Stacked",
17626
+ "code": "<ToastProvider stacked position=\"bottom-right\">{children}</ToastProvider>",
17627
+ "imports": "import { ToastProvider } from \"@sableui/react\""
17628
+ },
17607
17629
  {
17608
17630
  "label": "Provider with app chrome",
17609
17631
  "code": "<ToastProvider position=\"bottom-right\" limit={3}><AppShell><Region max=\"feed\"><Button onClick={() => toast.success(\"Saved\")}>Save</Button></Region></AppShell></ToastProvider>",
@@ -23699,6 +23721,11 @@
23699
23721
  "render",
23700
23722
  "style"
23701
23723
  ],
23724
+ "ToastContent": [
23725
+ "className",
23726
+ "render",
23727
+ "style"
23728
+ ],
23702
23729
  "ToastDescription": [
23703
23730
  "className",
23704
23731
  "render",
@@ -23709,6 +23736,7 @@
23709
23736
  "closeIcon",
23710
23737
  "limit",
23711
23738
  "position",
23739
+ "stacked",
23712
23740
  "timeout",
23713
23741
  "viewportClassName"
23714
23742
  ],
@@ -23728,6 +23756,7 @@
23728
23756
  "className",
23729
23757
  "position",
23730
23758
  "render",
23759
+ "stacked",
23731
23760
  "style"
23732
23761
  ],
23733
23762
  "Toggle": [
@@ -15,7 +15,7 @@ try {
15
15
  const action = modes[0]
16
16
  if (action === "--template") {
17
17
  const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"))
18
- const css = readFileSync(resolve(packageRoot, "dist/themes/base/base.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "")
18
+ const css = readFileSync(resolve(packageRoot, "dist/themes/sable/sable.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "")
19
19
  const groups = { light: {}, dark: {} }
20
20
  for (const match of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
21
21
  const target = match[1].includes(".dark") ? groups.dark : groups.light
package/scripts/grade.mjs CHANGED
@@ -33,7 +33,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
33
33
  import { createHash } from "node:crypto"
34
34
  import { dirname, extname, join, relative, resolve } from "node:path"
35
35
  import { fileURLToPath } from "node:url"
36
- import { lintText, makeRules } from "./lint-rules.mjs"
36
+ import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
37
37
  import { skeletonize } from "./skeleton.mjs"
38
38
  import { assessReview, reviewTemplate } from "./review-evidence.mjs"
39
39
 
@@ -44,6 +44,7 @@ const CWD = process.cwd()
44
44
  const argv = process.argv.slice(2)
45
45
  const flags = new Map()
46
46
  const targets = []
47
+ const ignores = []
47
48
  const BOOLEAN_FLAGS = new Set(["json", "help", "template", "strict"])
48
49
  const VALUE_FLAGS = new Set(["cli-log", "review", "icons"])
49
50
  const JSON_OUT = argv.includes("--json")
@@ -55,7 +56,11 @@ function abort(message) {
55
56
  for (let i = 0; i < argv.length; i++) {
56
57
  const a = argv[i]
57
58
  if (!a.startsWith("--")) targets.push(a)
58
- else if (BOOLEAN_FLAGS.has(a.slice(2))) flags.set(a.slice(2), true)
59
+ else if (a === "--ignore" || a.startsWith("--ignore=")) {
60
+ const glob = a === "--ignore" ? argv[++i] : a.slice(9)
61
+ if (!glob || glob.startsWith("--")) abort("--ignore requires a glob.")
62
+ ignores.push(glob)
63
+ } else if (BOOLEAN_FLAGS.has(a.slice(2))) flags.set(a.slice(2), true)
59
64
  else if (VALUE_FLAGS.has(a.slice(2))) {
60
65
  if (!argv[i + 1] || argv[i + 1].startsWith("--")) abort(`${a} requires a file path.`)
61
66
  flags.set(a.slice(2), argv[++i])
@@ -72,13 +77,14 @@ if (flags.has("help") || !targets.length) {
72
77
  [
73
78
  "grade — check the static contract and recorded review evidence. No model calls.",
74
79
  "",
75
- " sable grade <dir...> [--json] [--review <review.json>] [--icons <icons.json>] [--strict]",
80
+ " sable grade <dir...> [--json] [--review <review.json>] [--icons <icons.json>] [--ignore <glob>] [--strict]",
76
81
  "",
77
82
  " --json machine-readable scorecard",
78
83
  " --cli-log a SABLE_CLI_LOG jsonl, to report whether the CLI was used at all",
79
84
  " --template print a review JSON template; fill after inspecting the rendered build",
80
85
  " --review validate evidence files and observations; paths are relative to this JSON",
81
86
  ' --icons icon names the app adds with composeIconSets: { "names": [...] }. Default: .sable/icons.json',
87
+ " --ignore skip matching paths in the checks, as sable-lint --ignore (repeatable)",
82
88
  " --strict exit 1 for static findings or a missing/incomplete/unresolved review",
83
89
  "",
84
90
  "Checks: design-contract violations · hallucinated components and props ·",
@@ -194,7 +200,13 @@ for (const t of targets) {
194
200
  else snapshotFiles.push(abs)
195
201
  }
196
202
  snapshotFiles.splice(0, snapshotFiles.length, ...new Set(snapshotFiles.sort()))
197
- const files = snapshotFiles.filter((file) => CODE.test(file))
203
+ /* --ignore takes the same globs sable-lint does, so the grade judges the same
204
+ files the lint does. A data layer (palette fixtures, document colours) is
205
+ skipped by the checks but still fingerprinted: it is part of the build. */
206
+ const ignoreRes = ignores.map(ignoreGlob)
207
+ const isIgnored = (file) => ignoreRes.some((re) => re.test(relative(CWD, file).replaceAll("\\", "/")))
208
+ const ignoredFiles = snapshotFiles.filter((file) => CODE.test(file) && isIgnored(file))
209
+ const files = snapshotFiles.filter((file) => CODE.test(file) && !isIgnored(file))
198
210
  if (!files.length) abort("No source files found.")
199
211
  if (!files.some((file) => readFileSync(file, "utf8").trim().length > 0)) abort("No non-empty source content found.")
200
212
  // Git checkouts may use CRLF on Windows and LF in CI. Normalize only known text
@@ -463,6 +475,7 @@ const result = {
463
475
  sourceHash,
464
476
  fingerprint: { algorithm: "sha256", files: snapshotFiles.length, packageHash, coverage: "All non-test files in supplied targets, plus Sable runtime sources, registry, and lint contract. Known text formats normalize CRLF to LF; binary assets retain their bytes. Pass public/ for external asset directories; remote resources are not covered." },
465
477
  files: files.length,
478
+ ignored: { globs: ignores, files: ignoredFiles.length },
466
479
  loc,
467
480
  score,
468
481
  scoreMeaning: "Static contract only. Pattern similarity does not affect this score; visual quality is not scored.",
@@ -485,7 +498,7 @@ if (JSON_OUT) {
485
498
 
486
499
  const L = (s = "") => process.stdout.write(`${s}\n`)
487
500
  L()
488
- L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}`)
501
+ L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}${ignoredFiles.length ? ` (${ignoredFiles.length} ignored)` : ""}`)
489
502
  L(` ${"─".repeat(66)}`)
490
503
  const row = (label, n, note = "") =>
491
504
  L(` ${label.padEnd(26)}${String(n).padStart(4)}${note ? ` ${note}` : ""}`)
@@ -537,10 +537,14 @@ export function makeRules({ semanticTokens, motionTokens }) {
537
537
  since: "0.1.0",
538
538
  code: true,
539
539
  why:
540
- "A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason.",
540
+ "A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason. " +
541
+ "Exempt: the value of a `--theme-*` hook (`--theme-panel-field: color-mix(…)`), which is how a theme tunes a derived token.",
541
542
  find(line) {
542
543
  const out = []
543
544
  for (const m of line.matchAll(/color-mix\([^;"'`]*?var\(--foreground\)/g)) {
545
+ // A theme hook is the sanctioned place to retune a derived surface,
546
+ // in CSS or as an arbitrary property ([--theme-x:color-mix(…)]).
547
+ if (/--theme-[\w-]+\s*:\s*$/.test(line.slice(0, m.index))) continue
544
548
  out.push({ match: m[0].length > 60 ? `${m[0].slice(0, 57)}…` : m[0] })
545
549
  }
546
550
  return out
@@ -964,6 +968,21 @@ function stripComments(src) {
964
968
  .replace(/(^|[^:])\/\/[^\n]*/gm, (m, p) => p + " ".repeat(m.length - p.length))
965
969
  }
966
970
 
971
+ /**
972
+ * An `--ignore` glob as a RegExp over a CWD-relative, forward-slash path:
973
+ * `**` crosses directories, `*` stays within one. Shared by sable-lint and
974
+ * sable grade so the same flag skips the same files in both.
975
+ */
976
+ export function ignoreGlob(glob) {
977
+ return new RegExp(
978
+ `^${glob
979
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
980
+ .replace(/\*\*/g, "\0")
981
+ .replace(/\*/g, "[^/]*")
982
+ .replace(/\0/g, ".*")}$`,
983
+ )
984
+ }
985
+
967
986
  export function lintText(text, rules, ctx) {
968
987
  const lines = text.split(/\r?\n/)
969
988
  const codeLines = stripComments(text).split(/\r?\n/)
@@ -1,9 +0,0 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
2
-
3
- /**
4
- * Base — the default Sable theme. Concrete token values live in
5
- * `base.css`; this manifest is the typed, machine-readable description.
6
- */
7
- declare const baseTheme: ThemeManifest;
8
-
9
- export { baseTheme, baseTheme as default };
@@ -1 +0,0 @@
1
- export { baseTheme, base_default as default } from '../../chunk-EP55KSYA.js';