@urbicon-ui/blocks 6.21.1 → 6.21.3

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 (78) hide show
  1. package/dist/components/NumberInput/NumberInput.svelte +197 -0
  2. package/dist/components/NumberInput/NumberInput.svelte.d.ts +4 -0
  3. package/dist/components/NumberInput/index.d.ts +55 -0
  4. package/dist/components/NumberInput/index.js +1 -0
  5. package/dist/components/index.d.ts +2 -0
  6. package/dist/components/index.js +1 -0
  7. package/dist/i18n/index.d.ts +6 -0
  8. package/dist/primitives/Avatar/avatar.variants.d.ts +6 -6
  9. package/dist/primitives/Badge/Badge.svelte +18 -8
  10. package/dist/primitives/Badge/index.d.ts +26 -6
  11. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +11 -6
  12. package/dist/primitives/Breadcrumb/index.d.ts +2 -2
  13. package/dist/primitives/Button/button.variants.d.ts +4 -4
  14. package/dist/primitives/ButtonGroup/index.d.ts +1 -1
  15. package/dist/primitives/Card/card.variants.d.ts +5 -5
  16. package/dist/primitives/Checkbox/Checkbox.svelte +3 -3
  17. package/dist/primitives/Combobox/Combobox.svelte +478 -104
  18. package/dist/primitives/Combobox/combobox.variants.d.ts +89 -17
  19. package/dist/primitives/Combobox/combobox.variants.js +94 -13
  20. package/dist/primitives/Combobox/index.d.ts +166 -51
  21. package/dist/primitives/ConfirmDialog/index.d.ts +19 -0
  22. package/dist/primitives/Dialog/Dialog.svelte +74 -0
  23. package/dist/primitives/Dialog/dialog.variants.d.ts +9 -9
  24. package/dist/primitives/Dialog/index.d.ts +12 -1
  25. package/dist/primitives/Drawer/Drawer.svelte +2 -1
  26. package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
  27. package/dist/primitives/Drawer/drawer.variants.js +45 -10
  28. package/dist/primitives/Drawer/index.d.ts +13 -4
  29. package/dist/primitives/FormField/FormField.svelte +6 -6
  30. package/dist/primitives/FormField/index.d.ts +13 -4
  31. package/dist/primitives/Input/Input.svelte +8 -4
  32. package/dist/primitives/Input/input.variants.d.ts +9 -9
  33. package/dist/primitives/Menu/Menu.svelte +66 -4
  34. package/dist/primitives/Menu/index.d.ts +25 -0
  35. package/dist/primitives/Pagination/Pagination.svelte +28 -13
  36. package/dist/primitives/Pagination/index.d.ts +44 -0
  37. package/dist/primitives/Popover/Popover.svelte +34 -3
  38. package/dist/primitives/Popover/index.d.ts +3 -3
  39. package/dist/primitives/RadioGroup/RadioGroup.svelte +3 -3
  40. package/dist/primitives/Select/Select.svelte +21 -11
  41. package/dist/primitives/Select/index.d.ts +7 -0
  42. package/dist/primitives/Select/select.variants.d.ts +17 -17
  43. package/dist/primitives/Select/select.variants.js +18 -0
  44. package/dist/primitives/Skeleton/skeleton.variants.d.ts +3 -3
  45. package/dist/primitives/Slider/Slider.svelte +4 -8
  46. package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
  47. package/dist/primitives/Textarea/Textarea.svelte +13 -5
  48. package/dist/primitives/Textarea/textarea.variants.d.ts +7 -7
  49. package/dist/primitives/Textarea/textarea.variants.js +5 -2
  50. package/dist/primitives/Toast/Toaster.svelte +42 -2
  51. package/dist/primitives/Toast/index.d.ts +30 -0
  52. package/dist/primitives/Toast/toast.store.svelte.d.ts +25 -1
  53. package/dist/primitives/Toast/toast.store.svelte.js +91 -1
  54. package/dist/primitives/Toast/toast.variants.d.ts +18 -0
  55. package/dist/primitives/Toast/toast.variants.js +15 -0
  56. package/dist/primitives/Toggle/Toggle.svelte +17 -3
  57. package/dist/primitives/Toggle/index.d.ts +8 -2
  58. package/dist/primitives/Toggle/toggle.variants.d.ts +7 -0
  59. package/dist/primitives/Toggle/toggle.variants.js +26 -0
  60. package/dist/primitives/Toolbar/toolbar.variants.d.ts +6 -6
  61. package/dist/primitives/Tooltip/Tooltip.svelte +47 -21
  62. package/dist/primitives/Tooltip/Tooltip.svelte.d.ts +1 -1
  63. package/dist/primitives/Tooltip/index.d.ts +31 -3
  64. package/dist/primitives/Tooltip/tooltip.variants.d.ts +4 -4
  65. package/dist/primitives/Tooltip/tooltip.variants.js +2 -2
  66. package/dist/primitives/index.d.ts +3 -3
  67. package/dist/style/semantic.css +55 -29
  68. package/dist/translations/de.d.ts +3 -0
  69. package/dist/translations/de.js +4 -1
  70. package/dist/translations/en.d.ts +3 -0
  71. package/dist/translations/en.js +4 -1
  72. package/dist/utils/figma-token-export.d.ts +13 -0
  73. package/dist/utils/figma-token-export.js +99 -26
  74. package/dist/utils/guide.svelte.d.ts +8 -2
  75. package/dist/utils/guide.svelte.js +86 -5
  76. package/dist/utils/use-form-field.svelte.d.ts +9 -9
  77. package/dist/utils/use-form-field.svelte.js +12 -12
  78. package/package.json +3 -3
@@ -1,18 +1,18 @@
1
1
  import { type SlotNames, type VariantProps } from '../../utils/variants.js';
2
2
  export declare const tooltipVariants: ((props?: {
3
- visible?: boolean | undefined;
3
+ open?: boolean | undefined;
4
4
  intent?: "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
5
5
  size?: "sm" | "md" | "lg" | undefined;
6
6
  } | undefined) => {
7
7
  base: (props?: ({
8
- visible?: boolean | undefined;
8
+ open?: boolean | undefined;
9
9
  intent?: "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
10
10
  size?: "sm" | "md" | "lg" | undefined;
11
11
  } & {
12
12
  class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
13
13
  }) | undefined) => string;
14
14
  arrow: (props?: ({
15
- visible?: boolean | undefined;
15
+ open?: boolean | undefined;
16
16
  intent?: "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
17
17
  size?: "sm" | "md" | "lg" | undefined;
18
18
  } & {
@@ -21,6 +21,6 @@ export declare const tooltipVariants: ((props?: {
21
21
  }) & {
22
22
  readonly config: import("../../utils/variants.js").TVConfig;
23
23
  };
24
- export type TooltipVariants = Omit<VariantProps<typeof tooltipVariants>, 'visible'>;
24
+ export type TooltipVariants = Omit<VariantProps<typeof tooltipVariants>, 'open'>;
25
25
  /** Slot names derived from the `tv()` config above — single source of truth for `slotClasses`. */
26
26
  export type TooltipSlots = SlotNames<typeof tooltipVariants>;
@@ -33,7 +33,7 @@ export const tooltipVariants = tv({
33
33
  arrow: ['absolute w-2 h-2', 'bg-inherit transform rotate-45']
34
34
  },
35
35
  variants: {
36
- visible: {
36
+ open: {
37
37
  true: { base: 'opacity-100' },
38
38
  false: { base: 'opacity-0' }
39
39
  },
@@ -53,7 +53,7 @@ export const tooltipVariants = tv({
53
53
  }
54
54
  },
55
55
  defaultVariants: {
56
- visible: false,
56
+ open: false,
57
57
  intent: 'neutral',
58
58
  size: 'md'
59
59
  }
@@ -18,7 +18,7 @@ export type { CheckboxProps } from './Checkbox/index.js';
18
18
  export * from './Checkbox/index.js';
19
19
  export type { CollapsibleProps } from './Collapsible/index.js';
20
20
  export * from './Collapsible/index.js';
21
- export type { ComboboxOptionType, ComboboxProps } from './Combobox/index.js';
21
+ export type { ComboboxGroup, ComboboxOptionType, ComboboxProps } from './Combobox/index.js';
22
22
  export * from './Combobox/index.js';
23
23
  export type { ConfirmDialogProps, ConfirmIntent } from './ConfirmDialog/index.js';
24
24
  export * from './ConfirmDialog/index.js';
@@ -34,7 +34,7 @@ export type { JourneyNode, JourneyStatus, JourneyTimelineProps } from './Journey
34
34
  export * from './JourneyTimeline/index.js';
35
35
  export type { MenuContext, MenuCustomSlots, MenuItemType, MenuObjectOption, MenuOption, MenuProps, MenuSectionHeader } from './Menu/index.js';
36
36
  export * from './Menu/index.js';
37
- export type { PaginationItemProps, PaginationProps } from './Pagination/index.js';
37
+ export type { PaginationItemContext, PaginationItemProps, PaginationProps } from './Pagination/index.js';
38
38
  export * from './Pagination/index.js';
39
39
  export * from './Popover/index.js';
40
40
  export type { ProgressProps } from './Progress/index.js';
@@ -61,7 +61,7 @@ export type { TabItemProps, TabPanelProps, TabProps } from './Tab/index.js';
61
61
  export * from './Tab/index.js';
62
62
  export type { TextareaProps } from './Textarea/index.js';
63
63
  export * from './Textarea/index.js';
64
- export type { ToastData, ToastInput, ToastIntent, ToastPlacement, ToastProps, ToastShorthandOpts } from './Toast/index.js';
64
+ export type { ToastAction, ToastData, ToastInput, ToastIntent, ToastPlacement, ToastPromiseOptions, ToastProps, ToastShorthandOpts } from './Toast/index.js';
65
65
  export * from './Toast/index.js';
66
66
  export { TOAST_INTENTS } from './Toast/index.js';
67
67
  export type { ToggleProps } from './Toggle/index.js';
@@ -183,7 +183,12 @@
183
183
 
184
184
  /* === SHADOWS ===
185
185
  Dark mode uses higher alpha to compensate for low contrast against
186
- dark surfaces. light-dark() handles the whole multi-layer shadow.
186
+ dark surfaces. light-dark() may wrap ONLY the per-layer <color> —
187
+ it is a color function, and layers must stay top-level
188
+ comma-separated. Wrapping the whole multi-layer list made every
189
+ `box-shadow: var(--blocks-shadow-*)` invalid at computed-value time
190
+ (shadows silently rendered as none, library-wide) — verified in
191
+ chromium before the 2026-07-13 fix.
187
192
 
188
193
  The shadow tint (`--blocks-shadow-tint`) is an oklch L C H triplet
189
194
  WITHOUT the alpha channel — declared on `:root` (not inside @theme)
@@ -192,34 +197,55 @@
192
197
  their shadow palette — e.g. editorial sets it to a dark brown so
193
198
  shadows blend with cream surfaces instead of reading as cool
194
199
  smudges. */
195
- --color-shadow-xs: light-dark(
196
- 0 1px 2px 0 oklch(var(--blocks-shadow-tint) / 0.05),
197
- 0 1px 2px 0 oklch(var(--blocks-shadow-tint) / 0.2)
198
- );
199
- --color-shadow-sm: light-dark(
200
- 0 1px 3px 0 oklch(var(--blocks-shadow-tint) / 0.1) 0 1px 2px -1px
201
- oklch(var(--blocks-shadow-tint) / 0.1),
202
- 0 1px 3px 0 oklch(var(--blocks-shadow-tint) / 0.3) 0 1px 2px -1px
203
- oklch(var(--blocks-shadow-tint) / 0.3)
204
- );
205
- --color-shadow-base: light-dark(
206
- 0 4px 6px -1px oklch(var(--blocks-shadow-tint) / 0.1) 0 2px 4px -2px
207
- oklch(var(--blocks-shadow-tint) / 0.1),
208
- 0 4px 6px -1px oklch(var(--blocks-shadow-tint) / 0.4) 0 2px 4px -2px
209
- oklch(var(--blocks-shadow-tint) / 0.4)
210
- );
211
- --color-shadow-md: light-dark(
212
- 0 10px 15px -3px oklch(var(--blocks-shadow-tint) / 0.1) 0 4px 6px -4px
213
- oklch(var(--blocks-shadow-tint) / 0.1),
214
- 0 10px 15px -3px oklch(var(--blocks-shadow-tint) / 0.4) 0 4px 6px -4px
215
- oklch(var(--blocks-shadow-tint) / 0.4)
216
- );
217
- --color-shadow-lg: light-dark(
218
- 0 20px 25px -5px oklch(var(--blocks-shadow-tint) / 0.1) 0 8px 10px -6px
219
- oklch(var(--blocks-shadow-tint) / 0.1),
220
- 0 20px 25px -5px oklch(var(--blocks-shadow-tint) / 0.5) 0 8px 10px -6px
221
- oklch(var(--blocks-shadow-tint) / 0.5)
222
- );
200
+ --color-shadow-xs: 0 1px 2px 0
201
+ light-dark(
202
+ oklch(var(--blocks-shadow-tint) / 0.05),
203
+ oklch(var(--blocks-shadow-tint) / 0.2)
204
+ );
205
+ --color-shadow-sm:
206
+ 0 1px 3px 0
207
+ light-dark(
208
+ oklch(var(--blocks-shadow-tint) / 0.1),
209
+ oklch(var(--blocks-shadow-tint) / 0.3)
210
+ ),
211
+ 0 1px 2px -1px
212
+ light-dark(
213
+ oklch(var(--blocks-shadow-tint) / 0.1),
214
+ oklch(var(--blocks-shadow-tint) / 0.3)
215
+ );
216
+ --color-shadow-base:
217
+ 0 4px 6px -1px
218
+ light-dark(
219
+ oklch(var(--blocks-shadow-tint) / 0.1),
220
+ oklch(var(--blocks-shadow-tint) / 0.4)
221
+ ),
222
+ 0 2px 4px -2px
223
+ light-dark(
224
+ oklch(var(--blocks-shadow-tint) / 0.1),
225
+ oklch(var(--blocks-shadow-tint) / 0.4)
226
+ );
227
+ --color-shadow-md:
228
+ 0 10px 15px -3px
229
+ light-dark(
230
+ oklch(var(--blocks-shadow-tint) / 0.1),
231
+ oklch(var(--blocks-shadow-tint) / 0.4)
232
+ ),
233
+ 0 4px 6px -4px
234
+ light-dark(
235
+ oklch(var(--blocks-shadow-tint) / 0.1),
236
+ oklch(var(--blocks-shadow-tint) / 0.4)
237
+ );
238
+ --color-shadow-lg:
239
+ 0 20px 25px -5px
240
+ light-dark(
241
+ oklch(var(--blocks-shadow-tint) / 0.1),
242
+ oklch(var(--blocks-shadow-tint) / 0.5)
243
+ ),
244
+ 0 8px 10px -6px
245
+ light-dark(
246
+ oklch(var(--blocks-shadow-tint) / 0.1),
247
+ oklch(var(--blocks-shadow-tint) / 0.5)
248
+ );
223
249
  }
224
250
 
225
251
  /* Shadow tint — declared on :root rather than inside @theme because
@@ -1,6 +1,8 @@
1
1
  declare const _default: {
2
2
  readonly accessibility: {
3
3
  readonly avatar: "Avatar";
4
+ readonly breadcrumb: "Pfadnavigation";
5
+ readonly breadcrumbExpand: "Alle Pfadebenen anzeigen";
4
6
  readonly clearInput: "Eingabe löschen";
5
7
  readonly clearSearch: "Suche löschen";
6
8
  readonly clearSelection: "Auswahl löschen";
@@ -19,6 +21,7 @@ declare const _default: {
19
21
  readonly removableBadge: "Entfernbarer Badge";
20
22
  readonly removeBadge: "Badge entfernen";
21
23
  readonly removeFile: "{{name}} entfernen";
24
+ readonly removeTag: "{{label}} entfernen";
22
25
  };
23
26
  readonly button: {
24
27
  readonly close: "Schließen";
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  accessibility: {
3
3
  avatar: 'Avatar',
4
+ breadcrumb: 'Pfadnavigation',
5
+ breadcrumbExpand: 'Alle Pfadebenen anzeigen',
4
6
  clearInput: 'Eingabe löschen',
5
7
  clearSearch: 'Suche löschen',
6
8
  clearSelection: 'Auswahl löschen',
@@ -18,7 +20,8 @@ export default {
18
20
  toggleOptions: 'Optionen umschalten',
19
21
  removableBadge: 'Entfernbarer Badge',
20
22
  removeBadge: 'Badge entfernen',
21
- removeFile: '{{name}} entfernen'
23
+ removeFile: '{{name}} entfernen',
24
+ removeTag: '{{label}} entfernen'
22
25
  },
23
26
  button: {
24
27
  close: 'Schließen',
@@ -1,6 +1,8 @@
1
1
  declare const _default: {
2
2
  readonly accessibility: {
3
3
  readonly avatar: "Avatar";
4
+ readonly breadcrumb: "Breadcrumb";
5
+ readonly breadcrumbExpand: "Show all breadcrumb items";
4
6
  readonly clearInput: "Clear input";
5
7
  readonly clearSearch: "Clear search";
6
8
  readonly clearSelection: "Clear selection";
@@ -19,6 +21,7 @@ declare const _default: {
19
21
  readonly removableBadge: "Removable badge";
20
22
  readonly removeBadge: "Remove badge";
21
23
  readonly removeFile: "Remove {{name}}";
24
+ readonly removeTag: "Remove {{label}}";
22
25
  };
23
26
  readonly button: {
24
27
  readonly close: "Close";
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  accessibility: {
3
3
  avatar: 'Avatar',
4
+ breadcrumb: 'Breadcrumb',
5
+ breadcrumbExpand: 'Show all breadcrumb items',
4
6
  clearInput: 'Clear input',
5
7
  clearSearch: 'Clear search',
6
8
  clearSelection: 'Clear selection',
@@ -18,7 +20,8 @@ export default {
18
20
  toggleOptions: 'Toggle options',
19
21
  removableBadge: 'Removable badge',
20
22
  removeBadge: 'Remove badge',
21
- removeFile: 'Remove {{name}}'
23
+ removeFile: 'Remove {{name}}',
24
+ removeTag: 'Remove {{label}}'
22
25
  },
23
26
  button: {
24
27
  close: 'Close',
@@ -4,6 +4,19 @@
4
4
  * Generates a Tokens Studio-compatible JSON export from the Urbicon UI
5
5
  * OKLCH design token system. Suitable for Figma plugins like "Tokens Studio
6
6
  * for Figma" (formerly Figma Tokens).
7
+ *
8
+ * The values below mirror the token truth in `../style/foundation.css`
9
+ * (palettes, radii) and `../style/semantic.css` (surface/text/border roles,
10
+ * shadows). They are hardcoded — this module runs in the browser and cannot
11
+ * read the CSS files at runtime — but guarded against drift by the
12
+ * co-located `figma-token-export.test.ts`, which parses both CSS files and
13
+ * compares every value. If you change a token in the CSS, that test fails
14
+ * until the export here is updated to match.
15
+ *
16
+ * Semantic tokens resolve light/dark via CSS `light-dark()`; this export
17
+ * carries the **light-mode** resolution (Figma has no mode-aware token
18
+ * values in plain Tokens Studio JSON). Dark mode swaps at the foundation
19
+ * layer, so the palette ramps below contain every shade both modes use.
7
20
  */
8
21
  export interface FigmaToken {
9
22
  value: string;
@@ -4,10 +4,24 @@
4
4
  * Generates a Tokens Studio-compatible JSON export from the Urbicon UI
5
5
  * OKLCH design token system. Suitable for Figma plugins like "Tokens Studio
6
6
  * for Figma" (formerly Figma Tokens).
7
+ *
8
+ * The values below mirror the token truth in `../style/foundation.css`
9
+ * (palettes, radii) and `../style/semantic.css` (surface/text/border roles,
10
+ * shadows). They are hardcoded — this module runs in the browser and cannot
11
+ * read the CSS files at runtime — but guarded against drift by the
12
+ * co-located `figma-token-export.test.ts`, which parses both CSS files and
13
+ * compares every value. If you change a token in the CSS, that test fails
14
+ * until the export here is updated to match.
15
+ *
16
+ * Semantic tokens resolve light/dark via CSS `light-dark()`; this export
17
+ * carries the **light-mode** resolution (Figma has no mode-aware token
18
+ * values in plain Tokens Studio JSON). Dark mode swaps at the foundation
19
+ * layer, so the palette ramps below contain every shade both modes use.
7
20
  */
8
21
  const foundationColors = {
9
22
  neutral: {
10
23
  '0': 'oklch(1 0 0)',
24
+ '25': 'oklch(0.99 0.002 240)',
11
25
  '50': 'oklch(0.98 0.005 240)',
12
26
  '100': 'oklch(0.95 0.008 240)',
13
27
  '200': 'oklch(0.89 0.012 240)',
@@ -15,8 +29,11 @@ const foundationColors = {
15
29
  '400': 'oklch(0.7 0.015 240)',
16
30
  '500': 'oklch(0.55 0.016 240)',
17
31
  '600': 'oklch(0.42 0.017 240)',
32
+ '650': 'oklch(0.38 0.016 240)',
18
33
  '700': 'oklch(0.32 0.016 240)',
34
+ '750': 'oklch(0.28 0.014 240)',
19
35
  '800': 'oklch(0.23 0.015 240)',
36
+ '850': 'oklch(0.18 0.014 240)',
20
37
  '900': 'oklch(0.15 0.012 240)',
21
38
  '950': 'oklch(0.08 0.008 240)'
22
39
  },
@@ -46,18 +63,33 @@ const foundationColors = {
46
63
  '900': 'oklch(0.25 0.08 280)',
47
64
  '950': 'oklch(0.18 0.05 280)'
48
65
  },
66
+ 'warm-neutral': {
67
+ '50': 'oklch(0.96 0.004 45)',
68
+ '100': 'oklch(0.92 0.006 45)',
69
+ '200': 'oklch(0.86 0.008 45)',
70
+ '300': 'oklch(0.78 0.01 45)',
71
+ '400': 'oklch(0.65 0.012 45)',
72
+ '500': 'oklch(0.5 0.008 45)',
73
+ '600': 'oklch(0.42 0.01 45)',
74
+ '700': 'oklch(0.35 0.012 45)',
75
+ '800': 'oklch(0.28 0.01 45)',
76
+ '900': 'oklch(0.2 0.008 45)',
77
+ '950': 'oklch(0.12 0.006 45)'
78
+ },
79
+ /* success/danger 400–700 are the WCAG-darkened ramps: white text on the
80
+ solid intent backgrounds passes AA (4.5:1). */
49
81
  success: {
50
82
  '50': 'oklch(0.95 0.03 140)',
51
83
  '100': 'oklch(0.9 0.05 140)',
52
84
  '200': 'oklch(0.82 0.08 140)',
53
85
  '300': 'oklch(0.74 0.11 140)',
54
- '400': 'oklch(0.66 0.13 140)',
55
- '500': 'oklch(0.65 0.15 140)',
56
- '600': 'oklch(0.55 0.15 140)',
57
- '700': 'oklch(0.45 0.13 140)',
58
- '800': 'oklch(0.35 0.11 140)',
59
- '900': 'oklch(0.25 0.08 140)',
60
- '950': 'oklch(0.15 0.05 140)'
86
+ '400': 'oklch(0.62 0.14 140)',
87
+ '500': 'oklch(0.5 0.15 140)',
88
+ '600': 'oklch(0.44 0.14 140)',
89
+ '700': 'oklch(0.38 0.12 140)',
90
+ '800': 'oklch(0.3 0.1 140)',
91
+ '900': 'oklch(0.22 0.08 140)',
92
+ '950': 'oklch(0.14 0.05 140)'
61
93
  },
62
94
  warning: {
63
95
  '50': 'oklch(0.95 0.05 80)',
@@ -77,39 +109,67 @@ const foundationColors = {
77
109
  '100': 'oklch(0.9 0.08 25)',
78
110
  '200': 'oklch(0.82 0.1 25)',
79
111
  '300': 'oklch(0.74 0.12 25)',
80
- '400': 'oklch(0.66 0.14 25)',
81
- '500': 'oklch(0.65 0.18 25)',
82
- '600': 'oklch(0.55 0.15 25)',
83
- '700': 'oklch(0.45 0.13 25)',
84
- '800': 'oklch(0.35 0.11 25)',
85
- '900': 'oklch(0.25 0.08 25)',
86
- '950': 'oklch(0.15 0.05 25)'
112
+ '400': 'oklch(0.62 0.16 25)',
113
+ '500': 'oklch(0.5 0.17 25)',
114
+ '600': 'oklch(0.44 0.16 25)',
115
+ '700': 'oklch(0.38 0.14 25)',
116
+ '800': 'oklch(0.3 0.12 25)',
117
+ '900': 'oklch(0.22 0.1 25)',
118
+ '950': 'oklch(0.14 0.07 25)'
119
+ },
120
+ info: {
121
+ '50': 'oklch(0.96 0.025 220)',
122
+ '100': 'oklch(0.92 0.045 220)',
123
+ '200': 'oklch(0.84 0.075 220)',
124
+ '300': 'oklch(0.76 0.105 220)',
125
+ '400': 'oklch(0.66 0.13 220)',
126
+ '500': 'oklch(0.55 0.14 220)',
127
+ '600': 'oklch(0.48 0.13 220)',
128
+ '700': 'oklch(0.4 0.11 220)',
129
+ '800': 'oklch(0.32 0.09 220)',
130
+ '900': 'oklch(0.24 0.07 220)',
131
+ '950': 'oklch(0.16 0.05 220)'
87
132
  }
88
133
  };
134
+ /** Light-mode resolution of the semantic color roles in semantic.css. */
89
135
  const semanticTokens = {
90
136
  surface: {
91
- base: { value: '{color.neutral.0}', description: 'Page background' },
92
- raised: { value: '{color.neutral.50}', description: 'Slightly elevated surface' },
93
- elevated: { value: '{color.neutral.50}', description: 'Elevated surface (cards)' },
94
- overlay: { value: '{color.neutral.0}', description: 'Dialog/popover background' },
137
+ base: { value: '{color.neutral.0}', description: 'L1 — the page itself' },
138
+ quiet: { value: '{color.neutral.25}', description: 'L0 ground beneath the page' },
139
+ elevated: { value: '{color.neutral.50}', description: 'L2 lifted surfaces (popover, menu)' },
140
+ overlay: { value: '{color.neutral.0}', description: 'L3 — dialog/drawer/toast background' },
141
+ interactive: { value: '{color.neutral.100}', description: 'Interactive control background' },
142
+ hover: { value: '{color.neutral.100}', description: 'Hover state background' },
143
+ active: { value: '{color.neutral.200}', description: 'Active/pressed state background' },
144
+ disabled: { value: '{color.neutral.100}', description: 'Disabled surface' },
145
+ selected: { value: '{color.primary.50}', description: 'Selected item background' },
95
146
  subtle: { value: '{color.neutral.50}', description: 'Subtle background' },
96
147
  inverted: { value: '{color.neutral.900}', description: 'Inverted surface' }
97
148
  },
98
149
  text: {
99
150
  primary: { value: '{color.neutral.900}', description: 'Primary text' },
151
+ inverted: { value: '{color.neutral.0}', description: 'Inverted text (on dark bg)' },
100
152
  secondary: { value: '{color.neutral.700}', description: 'Secondary text' },
101
- tertiary: { value: '{color.neutral.500}', description: 'Tertiary text' },
153
+ tertiary: { value: '{color.neutral.600}', description: 'Tertiary text (WCAG AA on base)' },
154
+ quaternary: { value: '{color.neutral.500}', description: 'Quaternary text (lowest emphasis)' },
102
155
  disabled: { value: '{color.neutral.300}', description: 'Disabled text' },
103
- inverted: { value: '{color.neutral.0}', description: 'Inverted text (on dark bg)' },
104
- 'on-primary': { value: '{color.neutral.0}', description: 'Text on primary color bg' }
156
+ 'on-dark': { value: '{color.neutral.0}', description: 'Text on dark surfaces' },
157
+ 'on-primary': { value: '{color.neutral.0}', description: 'Text on primary color bg' },
158
+ 'on-surface': { value: '{color.neutral.900}', description: 'Text on tinted surfaces' }
105
159
  },
106
160
  border: {
161
+ hairline: {
162
+ value: 'rgb(0 0 0 / 0.08)',
163
+ description: 'Quietest structural divider (rows, separators)'
164
+ },
107
165
  subtle: { value: '{color.neutral.200}', description: 'Subtle border' },
108
166
  default: { value: '{color.neutral.300}', description: 'Default border' },
109
167
  emphasis: { value: '{color.neutral.400}', description: 'Emphasized border' },
110
168
  strong: { value: '{color.neutral.500}', description: 'Strong border' }
111
169
  }
112
170
  };
171
+ /* Tailwind 4 default spacing scale (4px per unit) — blocks defines no
172
+ custom spacing tokens. */
113
173
  const spacingTokens = {
114
174
  '0': '0',
115
175
  '1': '4px',
@@ -123,14 +183,24 @@ const spacingTokens = {
123
183
  '12': '48px',
124
184
  '16': '64px'
125
185
  };
186
+ /* Physical radius scale + the semantic 3-tier vocabulary (commit/modify/
187
+ contain, plus the bridge adjacency radius) from foundation.css.
188
+ rem values are exported as px (1rem = 16px) for Figma. */
126
189
  const borderRadiusTokens = {
127
- none: '0',
128
- sm: '2px',
190
+ xs: '2px',
191
+ sm: '4px',
129
192
  md: '6px',
130
193
  lg: '8px',
131
194
  xl: '12px',
132
- full: '9999px'
195
+ '2xl': '16px',
196
+ '3xl': '24px',
197
+ '4xl': '32px',
198
+ commit: { value: '9999px', description: 'Tier: actions, identity, status (pill)' },
199
+ modify: { value: '{borderRadius.sm}', description: 'Tier: editable surfaces, navigation' },
200
+ contain: { value: '{borderRadius.xs}', description: 'Tier: containers, panels, layout' },
201
+ bridge: { value: '{borderRadius.md}', description: 'Adjacency radius: panel anchored to a pill' }
133
202
  };
203
+ /** Light-mode resolution of the shadow tokens (shadow tint = pure black). */
134
204
  const shadowTokens = {
135
205
  xs: '0 1px 2px 0 rgba(0,0,0,0.05)',
136
206
  sm: '0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)',
@@ -174,8 +244,11 @@ function buildSpacingTokens() {
174
244
  }
175
245
  function buildBorderRadiusTokens() {
176
246
  const result = {};
177
- for (const [name, value] of Object.entries(borderRadiusTokens)) {
178
- result[name] = { value, type: 'borderRadius' };
247
+ for (const [name, def] of Object.entries(borderRadiusTokens)) {
248
+ result[name] =
249
+ typeof def === 'string'
250
+ ? { value: def, type: 'borderRadius' }
251
+ : { value: def.value, type: 'borderRadius', description: def.description };
179
252
  }
180
253
  return result;
181
254
  }
@@ -103,8 +103,14 @@ export interface GuideTour {
103
103
  * consumer can inject a server-state adapter. Kept deliberately tiny.
104
104
  */
105
105
  export interface GuideStorageAdapter {
106
- /** Returns the persisted set of seen/completed ids. */
107
- load(): string[];
106
+ /**
107
+ * Returns the persisted set of seen/completed ids. Return a `string[]`
108
+ * synchronously (localStorage, the default) or a `Promise<string[]>` for a
109
+ * DB-/remote-backed store. While an async load is pending the seen set starts
110
+ * empty and is merged (union) with the resolved ids once they arrive — so a
111
+ * tour may briefly be startable before the store answers.
112
+ */
113
+ load(): string[] | Promise<string[]>;
108
114
  /** Persists the full set of seen/completed ids. */
109
115
  save(ids: string[]): void;
110
116
  }
@@ -156,6 +156,22 @@ export class GuideController {
156
156
  #targets = new SvelteMap();
157
157
  /** Persisted "seen" ids. */
158
158
  #seen;
159
+ /** False only while an async `storage.load()` is still pending. */
160
+ #seenLoaded = true;
161
+ /**
162
+ * Set when the user writes (markSeen/resetSeen) while an async load is still
163
+ * pending, so the load's `.then` persists the merged union once — writing
164
+ * mid-load directly would clobber the remote store before its ids are merged in.
165
+ */
166
+ #pendingSeenWrite = false;
167
+ /**
168
+ * Ids the user forgot during the load window, so the merge doesn't resurrect
169
+ * them from the freshly-loaded remote set. A plain `Set` — pure bookkeeping,
170
+ * never rendered, so the `SvelteSet` rule doesn't apply.
171
+ */
172
+ #pendingRemovals = new Set();
173
+ /** `resetSeen()` (clear-all) during the load window: drop the remote set entirely. */
174
+ #pendingClearAll = false;
159
175
  #storage;
160
176
  #overlayStack;
161
177
  #dev;
@@ -209,7 +225,58 @@ export class GuideController {
209
225
  // `?? false` keeps `#dev` a strict boolean and means non-Vite consumers
210
226
  // simply get no dev warnings (rather than a crash).
211
227
  this.#dev = options.dev ?? import.meta.env?.DEV ?? false;
212
- this.#seen = new SvelteSet(this.#storage.load());
228
+ // Sync adapters (localStorage) seed the set immediately. Async adapters
229
+ // (DB/remote) start empty and get their ids merged in when the promise
230
+ // settles — union, not replace, so anything marked seen in the meantime
231
+ // survives. A rejected load is swallowed (dev-warned): the tour just stays
232
+ // "unseen", which fails open rather than hiding help behind a broken store.
233
+ const loaded = this.#storage.load();
234
+ if (Array.isArray(loaded)) {
235
+ this.#seen = new SvelteSet(loaded);
236
+ }
237
+ else {
238
+ this.#seen = new SvelteSet();
239
+ this.#seenLoaded = false;
240
+ loaded
241
+ .then((ids) => {
242
+ // Merge the loaded set in, but honour writes made during the window:
243
+ // skip everything if the user cleared all, and don't resurrect ids the
244
+ // user explicitly forgot. Adds are safe (union is idempotent).
245
+ if (!this.#pendingClearAll) {
246
+ for (const id of ids) {
247
+ if (!this.#pendingRemovals.has(id))
248
+ this.#seen.add(id);
249
+ }
250
+ }
251
+ this.#seenLoaded = true;
252
+ // Reconcile persistence: if the user wrote during the window, the store
253
+ // still holds the pre-merge state (we deliberately didn't save then), so
254
+ // persist the union now. Nothing wrote → leave the store untouched (no
255
+ // write-amplification of a remote we just read).
256
+ if (this.#pendingSeenWrite)
257
+ this.#storage.save([...this.#seen]);
258
+ })
259
+ .catch((err) => {
260
+ // Read failed: fail open (tour stays "unseen"). We don't persist the
261
+ // window's in-memory writes here — the store is in an unknown state, and
262
+ // blindly saving could destroy data we simply couldn't read ("read
263
+ // tolerant, write strict"). Later writes persist normally.
264
+ this.#seenLoaded = true;
265
+ if (this.#dev)
266
+ console.warn('[Guide] storage.load() rejected:', err);
267
+ });
268
+ }
269
+ }
270
+ /**
271
+ * Persist the seen-set — unless an async load is still pending, in which case a
272
+ * direct save would clobber the not-yet-loaded remote store. Defer to the load's
273
+ * `.then` reconciliation instead (see the constructor).
274
+ */
275
+ #persistSeen() {
276
+ if (this.#seenLoaded)
277
+ this.#storage.save([...this.#seen]);
278
+ else
279
+ this.#pendingSeenWrite = true;
213
280
  }
214
281
  // ─── Target registry ──────────────────────────────────────────────────────
215
282
  /**
@@ -738,15 +805,29 @@ export class GuideController {
738
805
  if (this.#seen.has(id))
739
806
  return;
740
807
  this.#seen.add(id);
741
- this.#storage.save([...this.#seen]);
808
+ // Re-seeing an id undoes a forget that's still pending reconciliation.
809
+ if (!this.#seenLoaded)
810
+ this.#pendingRemovals.delete(id);
811
+ this.#persistSeen();
742
812
  }
743
813
  /** Forget one id, or all of them when called without an argument. */
744
814
  resetSeen(id) {
745
- if (id === undefined)
815
+ if (id === undefined) {
746
816
  this.#seen.clear();
747
- else
817
+ // Clear-all during a pending load must survive the merge: drop the whole
818
+ // remote set, superseding any earlier pending removals.
819
+ if (!this.#seenLoaded) {
820
+ this.#pendingClearAll = true;
821
+ this.#pendingRemovals.clear();
822
+ }
823
+ }
824
+ else {
748
825
  this.#seen.delete(id);
749
- this.#storage.save([...this.#seen]);
826
+ // Record the forget so the pending-load merge doesn't resurrect it.
827
+ if (!this.#seenLoaded)
828
+ this.#pendingRemovals.add(id);
829
+ }
830
+ this.#persistSeen();
750
831
  }
751
832
  /** Snapshot of all seen ids. */
752
833
  get seenIds() {