@sesamehr/react-design-system 3.3.1 → 3.4.1

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 (82) hide show
  1. package/dist/Button/ButtonGroup/ButtonGroup.d.ts +11 -0
  2. package/dist/Button/ButtonGroup/index.d.ts +1 -0
  3. package/dist/Button/Fab/Fab.d.ts +24 -0
  4. package/dist/Button/Fab/index.d.ts +9 -0
  5. package/dist/Button/IconButton/IconButton.d.ts +2 -1
  6. package/dist/Button/IconButton/index.d.ts +5 -4
  7. package/dist/Button/SplitButton/SplitButton.d.ts +48 -0
  8. package/dist/Button/SplitButton/index.d.ts +1 -0
  9. package/dist/Button/index.d.ts +1 -1
  10. package/dist/Data/Table/hooks/useTableVirtual/useTableVirtual.types.d.ts +1 -1
  11. package/dist/Display/Badge/Badge.d.ts +3 -1
  12. package/dist/Display/Badge/index.d.ts +7 -5
  13. package/dist/Display/Mention/Mention.d.ts +28 -0
  14. package/dist/Display/Mention/index.d.ts +6 -0
  15. package/dist/Display/PlayerPill/PlayerPill.d.ts +11 -0
  16. package/dist/Display/PlayerPill/PlayerPillAction.d.ts +10 -0
  17. package/dist/Display/PlayerPill/PlayerPillStatus.d.ts +11 -0
  18. package/dist/Display/PlayerPill/PlayerPillTime.d.ts +18 -0
  19. package/dist/Display/PlayerPill/index.d.ts +15 -0
  20. package/dist/Display/PlayerPill/playerPillContext.d.ts +16 -0
  21. package/dist/Display/PlayerPill/useElapsedSeconds.d.ts +18 -0
  22. package/dist/Display/Tag/Tag.d.ts +17 -0
  23. package/dist/Display/Tag/index.d.ts +14 -0
  24. package/dist/Feedback/Alert/AlertDescription.d.ts +1 -1
  25. package/dist/Feedback/Alert/alert-context.d.ts +1 -1
  26. package/dist/Feedback/Bullet/index.d.ts +1 -1
  27. package/dist/Feedback/Dialog/DialogFullScreenContent.d.ts +33 -0
  28. package/dist/Feedback/Dialog/index.d.ts +1 -0
  29. package/dist/Feedback/EmptyState/EmptyState.d.ts +26 -0
  30. package/dist/Feedback/EmptyState/EmptyStateOption/EmptyStateOption.d.ts +16 -0
  31. package/dist/Feedback/EmptyState/EmptyStateOption/index.d.ts +1 -0
  32. package/dist/Feedback/EmptyState/index.d.ts +10 -0
  33. package/dist/Feedback/Toast/Toast.d.ts +15 -0
  34. package/dist/Feedback/Toast/ToastAction.d.ts +12 -0
  35. package/dist/Feedback/Toast/ToastClose.d.ts +12 -0
  36. package/dist/Feedback/Toast/ToastDescription.d.ts +4 -0
  37. package/dist/Feedback/Toast/ToastIcon.d.ts +5 -0
  38. package/dist/Feedback/Toast/ToastTitle.d.ts +4 -0
  39. package/dist/Feedback/Toast/Toaster.d.ts +24 -0
  40. package/dist/Feedback/Toast/index.d.ts +26 -0
  41. package/dist/Feedback/Toast/toast-context.d.ts +6 -0
  42. package/dist/Feedback/Toast/useToast.d.ts +51 -0
  43. package/dist/Forms/Field/useFieldGroup.d.ts +1 -1
  44. package/dist/Forms/Inputs/ColorPicker/ColorPicker.d.ts +15 -0
  45. package/dist/Forms/Inputs/ColorPicker/ColorPickerContent.d.ts +14 -0
  46. package/dist/Forms/Inputs/ColorPicker/ColorPickerSwatch.d.ts +11 -0
  47. package/dist/Forms/Inputs/ColorPicker/ColorPickerTrigger.d.ts +17 -0
  48. package/dist/Forms/Inputs/ColorPicker/index.d.ts +5 -0
  49. package/dist/Forms/Inputs/ColorPicker/useColorPicker.d.ts +21 -0
  50. package/dist/Forms/Inputs/DatePicker/DatePickerTrigger.d.ts +1 -1
  51. package/dist/Forms/Inputs/IconPicker/IconPicker.d.ts +42 -0
  52. package/dist/Forms/Inputs/IconPicker/IconPickerContent.d.ts +26 -0
  53. package/dist/Forms/Inputs/IconPicker/IconPickerItem.d.ts +31 -0
  54. package/dist/Forms/Inputs/IconPicker/IconPickerTrigger.d.ts +19 -0
  55. package/dist/Forms/Inputs/IconPicker/index.d.ts +5 -0
  56. package/dist/Forms/Inputs/IconPicker/useIconPicker.d.ts +44 -0
  57. package/dist/Forms/Inputs/InputOtp/InputOtpGroup/InputOtpGroup.d.ts +5 -1
  58. package/dist/Forms/Inputs/InputOtp/types.d.ts +1 -1
  59. package/dist/Forms/Inputs/Select/SelectTrigger.d.ts +1 -1
  60. package/dist/Forms/Inputs/Textarea/Textarea.d.ts +48 -3
  61. package/dist/Forms/Inputs/Textarea/index.d.ts +3 -2
  62. package/dist/Forms/Inputs/Toggle/Switch/Switch.d.ts +11 -0
  63. package/dist/Forms/Inputs/index.d.ts +2 -0
  64. package/dist/Forms/Inputs/inputSize.d.ts +1 -1
  65. package/dist/Layout/Scrollbar/Scrollbar.d.ts +2 -2
  66. package/dist/Navigation/Stepper/Stepper.d.ts +23 -0
  67. package/dist/Navigation/Stepper/StepperItem.d.ts +13 -0
  68. package/dist/Navigation/Stepper/StepperSubsteps.d.ts +12 -0
  69. package/dist/Navigation/Stepper/index.d.ts +4 -0
  70. package/dist/Navigation/Stepper/useStepper.d.ts +25 -0
  71. package/dist/Navigation/Stepper/useStepperPill.d.ts +28 -0
  72. package/dist/Navigation/ToggleTabs/useToggleTabsPill.d.ts +1 -1
  73. package/dist/Transitions/TransitionBase.d.ts +5 -1
  74. package/dist/hooks/useAutoGrow/useAutoGrow.d.ts +7 -0
  75. package/dist/main.d.ts +12 -2
  76. package/dist/react-design-system.css +1 -1
  77. package/dist/react-design-system.js +21787 -18689
  78. package/dist/react-design-system.umd.cjs +118 -87
  79. package/package.json +4 -2
  80. package/src/assets/styles/theme-v2.css +14 -22
  81. package/src/assets/styles/theme-v3.css +161 -23
  82. package/src/assets/styles/theme-v3.test.ts +242 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sesamehr/react-design-system",
3
- "version": "3.3.1",
3
+ "version": "3.4.1",
4
4
  "description": "Sesame HR React design system — components and design tokens",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,7 @@
14
14
  "require": "./dist/react-design-system.umd.cjs"
15
15
  },
16
16
  "./style.css": "./dist/react-design-system.css",
17
+ "./preflight.css": "./dist/preflight.css",
17
18
  "./assets/styles/mainTheme.css": "./src/assets/styles/mainTheme.css",
18
19
  "./lib/utils": {
19
20
  "types": "./dist/lib/utils.d.ts",
@@ -56,8 +57,9 @@
56
57
  "@radix-ui/react-slot": "^1.2.3",
57
58
  "@radix-ui/react-switch": "^1.2.5",
58
59
  "@radix-ui/react-tabs": "^1.1.12",
60
+ "@radix-ui/react-toast": "^1.2.24",
59
61
  "@radix-ui/react-tooltip": "^1.2.7",
60
- "@sesame/orxata-icons": "1.8.0",
62
+ "@sesame/orxata-icons": "1.8.1",
61
63
  "@tailwindcss/vite": "^4.1.13",
62
64
  "@tanstack/react-table": "^8.21.2",
63
65
  "@tanstack/react-virtual": "^3.14.13",
@@ -6,7 +6,7 @@
6
6
  * Aligns with Figma: 3. Mode → 2. Theme → 1. TailwindCSS
7
7
  *
8
8
  * DS EXTENSIONS (pending design approval):
9
- * - success, neutral-*, font-*, border-*, bg-*
9
+ * - success, neutral-*, border-*, bg-*
10
10
  *
11
11
  * Dark mode: .dark class on <html>
12
12
  */
@@ -16,13 +16,19 @@
16
16
  =========================================== */
17
17
 
18
18
  :root {
19
- /* Font */
20
- --font-primary: var(--color-indigo-950);
21
- --font-secondary: var(--color-slate-500);
22
- --font-tertiary: var(--color-slate-400);
23
- --font-error: var(--color-red-600);
24
- --font-success: var(--color-green-600);
25
- --font-warning: var(--color-amber-500);
19
+ /* No `font-*` family, for the same reason v3 has none: every one of them was
20
+ a second name for a semantic declared in this same file. `--font-primary`
21
+ and `--foreground` are both indigo-950 here (white in dark),
22
+ `--font-secondary` and `--muted-foreground` both slate-500 (slate-300),
23
+ and the feedback three are `--error` / `--success` / `--warning`. The
24
+ values below are untouched; only the aliases are gone, so nothing that
25
+ reactivates this file loses a colour — `text-foreground` and
26
+ `text-muted-foreground` resolve here exactly as they do under v3.
27
+
28
+ `--font-tertiary` (slate-400) is the one with no equivalent, in v2 as in
29
+ v3. Its call sites read `--muted-foreground` now, which in this file's
30
+ dark block is slate-300 — a step lighter rather than darker. Academic
31
+ while `mainTheme.css` imports v3 and nothing imports this. */
26
32
 
27
33
  /* Border */
28
34
  --border-default: var(--color-slate-200);
@@ -65,14 +71,6 @@
65
71
  }
66
72
 
67
73
  .dark {
68
- /* Font */
69
- --font-primary: var(--color-white);
70
- --font-secondary: var(--color-slate-300);
71
- --font-tertiary: var(--color-slate-400);
72
- --font-error: var(--color-red-400);
73
- --font-success: var(--color-green-600);
74
- --font-warning: var(--color-amber-500);
75
-
76
74
  /* Border */
77
75
  --border-default: var(--color-slate-700);
78
76
  --border-hover: var(--color-indigo-400);
@@ -192,12 +190,6 @@
192
190
  /* Expose tokens to Tailwind */
193
191
  @theme inline {
194
192
  /* DS tokens */
195
- --color-font-primary: var(--font-primary);
196
- --color-font-secondary: var(--font-secondary);
197
- --color-font-tertiary: var(--font-tertiary);
198
- --color-font-error: var(--font-error);
199
- --color-font-success: var(--font-success);
200
- --color-font-warning: var(--font-warning);
201
193
  --color-border-default: var(--border-default);
202
194
  --color-border-hover: var(--border-hover);
203
195
  --color-bg-base: var(--bg-base);
@@ -20,18 +20,21 @@
20
20
  */
21
21
 
22
22
  :root {
23
- --font-primary: var(--color-zinc-900);
24
- --font-secondary: var(--color-zinc-500);
25
- --font-tertiary: var(--color-zinc-400);
26
- --font-error: var(--color-red-500);
27
- --font-success: var(--color-green-500);
28
- --font-warning: var(--color-amber-500);
29
- --font-info: var(--color-blue-500);
30
-
31
23
  --border-default: var(--color-zinc-200);
32
24
  --border-hover: var(--color-brand-300);
33
25
 
34
- --success: var(--color-green-500);
26
+ /* Not the 500 every other semantic colour takes, and `--success-500` sitting
27
+ just below holding green-500 makes that look like a slip. It is not: the
28
+ v3 board binds `success` to green-700 and its hover to green-800, because
29
+ white text on green-500 does not clear contrast on a filled button. The
30
+ `-100/-300/-500` beneath are a tint ramp with the shade in the name, and
31
+ they are a different thing from the semantic colour.
32
+
33
+ Same shape for `--error`, which is red-600 rather than red-500, and for
34
+ `--brand-hover`, which *darkens* to brand-600 — it used to mix towards
35
+ white, which is the opposite of what every hover on that board does. */
36
+ --success: var(--color-green-700);
37
+ --success-hover: var(--color-green-800);
35
38
  --success-foreground: var(--color-white);
36
39
  --success-100: var(--color-green-100);
37
40
  --success-300: var(--color-green-300);
@@ -55,9 +58,24 @@
55
58
  --neutral-950: var(--color-zinc-950);
56
59
 
57
60
  --brand: var(--color-brand-500);
58
- --brand-hover: color-mix(in srgb, var(--color-brand-500) 90%, white);
61
+ --brand-hover: var(--color-brand-600);
62
+ --brand-active: var(--color-brand-700);
59
63
  --brand-foreground: var(--color-white);
60
64
 
65
+ /* A brand *surface*, not a brand fill: the 100 tint carrying the 700 of the
66
+ same hue, which is what clears 4.5:1 on it. Figma's `brand-subtle`
67
+ description names Button's outline and ghost hover, and the board binds the
68
+ family on Tag, Stepper, Mention and IconPicker too.
69
+
70
+ `-active` is the pressed/hover step on such a surface and `-border` the 1px
71
+ line around one. Both are brand-200 today, and they stay two names because
72
+ the board binds them to two different properties — repainting the line must
73
+ not drag the pressed fill with it. */
74
+ --brand-subtle: var(--color-brand-100);
75
+ --brand-subtle-foreground: var(--color-brand-700);
76
+ --brand-subtle-border: var(--color-brand-200);
77
+ --brand-subtle-active: var(--color-brand-200);
78
+
61
79
  --background-custom: #f5f5f7;
62
80
  --background-custom-gradient: #e0e7ee;
63
81
 
@@ -70,6 +88,29 @@
70
88
  --background: var(--color-white);
71
89
  --foreground: var(--color-zinc-900);
72
90
 
91
+ /* There is no `font-*` colour family here, and that is the answer rather
92
+ than an omission. Figma v3 carries 434 colour variables across 14
93
+ collections and not one of them is a font colour: text colour comes from
94
+ the same semantic as everything else that means it. `--font-primary` was
95
+ this file's second name for `--foreground`, `--font-secondary` for
96
+ `--muted-foreground`, and `--font-error` / `--font-success` /
97
+ `--font-warning` / `--font-info` forwarded to `--error` / `--success` /
98
+ `--warning` / `--info` — so message text could never drift from the
99
+ control border that shares its meaning, which is the only thing the
100
+ aliases bought.
101
+
102
+ `--font-tertiary` was the one with no counterpart at all. Figma deleted
103
+ the concept, so placeholder text, weekday headers and the scrollbar thumb
104
+ read `--muted-foreground` now: zinc-500 where they used to be zinc-400.
105
+
106
+ Three roles are easy to confuse, so to say which is which:
107
+ `--muted-foreground` is grey *text on the page background*, `--success` is
108
+ a green *fill*, and `--success-foreground` is white text sitting *on* that
109
+ fill. A green message under a field is `text-success`; a filled success
110
+ button is `bg-success text-success-foreground`.
111
+
112
+ `theme-v3.test.ts` fails if any of the seven comes back. */
113
+
73
114
  --card: var(--color-white);
74
115
  --card-foreground: var(--color-zinc-900);
75
116
 
@@ -80,6 +121,7 @@
80
121
  --primary-foreground: var(--color-white);
81
122
 
82
123
  --secondary: var(--color-zinc-100);
124
+ --secondary-hover: var(--color-zinc-200);
83
125
  --secondary-foreground: var(--color-zinc-900);
84
126
 
85
127
  --muted: var(--color-zinc-200);
@@ -88,10 +130,26 @@
88
130
  --accent: var(--color-zinc-100);
89
131
  --accent-foreground: var(--color-zinc-900);
90
132
 
91
- --success: var(--color-green-500);
133
+ --success: var(--color-green-700);
134
+ --success-hover: var(--color-green-800);
92
135
  --success-foreground: var(--color-white);
136
+ /* A semantic colour used as a *surface* instead of as a fill behind white
137
+ text: the 100 tint for the background, the 700 of the same hue for the
138
+ text and the icon sitting on it, which is what clears 4.5:1 there. The v3
139
+ board names the pair `*-subtle` / `*-subtle-foreground` and binds it on
140
+ Alert. `muted` already had this shape under its plain name — `--muted` is
141
+ the tint and `--muted-foreground` the text on it — so there is no
142
+ `--muted-subtle` to add. */
143
+ --success-subtle: var(--color-green-100);
144
+ --success-subtle-foreground: var(--color-green-700);
93
145
  --warning: var(--color-amber-500);
94
- --warning-foreground: var(--color-white);
146
+ /* Dark on amber, which looks inverted next to every other `*-foreground`
147
+ here, and is the only pairing that passes: white on amber-500 is 2.1:1,
148
+ zinc-950 on it is 9.3:1. Figma binds `warning-foreground` to zinc/950 in
149
+ both modes. */
150
+ --warning-foreground: var(--color-zinc-950);
151
+ --warning-subtle: var(--color-amber-100);
152
+ --warning-subtle-foreground: var(--color-amber-700);
95
153
  /* The one semantic name for this red. Figma's Primitive collection calls it
96
154
  `error`, so the code does too. There used to be a `--destructive` beside it
97
155
  holding the identical value — two names for one colour, which means two
@@ -99,16 +157,81 @@
99
157
  red. The *component* variant stays `destructive` (Button, Badge and
100
158
  StatusBadge), which is also what Figma names that style: the action is
101
159
  destructive, the colour is error. */
102
- --error: var(--color-red-500);
160
+ --error: var(--color-red-600);
161
+ --error-hover: var(--color-red-700);
103
162
  --error-foreground: var(--color-white);
104
- --info: var(--color-blue-500);
163
+ --error-subtle: var(--color-red-100);
164
+ --error-subtle-foreground: var(--color-red-700);
165
+ /* blue-600, not the 500 most semantic colours take — same reason `--success`
166
+ is green-700. `--info-foreground` is white, and white on blue-500 reaches
167
+ only 3.8:1; on blue-600 it is 5.2:1. Figma binds `info` to blue/600. */
168
+ --info: var(--color-blue-600);
105
169
  --info-foreground: var(--color-white);
170
+ --info-subtle: var(--color-blue-100);
171
+ --info-subtle-foreground: var(--color-blue-700);
172
+
173
+ /* Content that is *yours*, or that matched what you typed — a mention of you,
174
+ a search hit. Figma binds it to the same amber-100 as `--warning-subtle`,
175
+ and it is deliberately a separate name rather than an alias of it: one says
176
+ "look, this is about you" and the other says "careful". Re-pointing either
177
+ must not drag the other along. */
178
+ --highlight: var(--color-amber-100);
179
+ --highlight-hover: var(--color-amber-200);
180
+ --highlight-foreground: var(--color-amber-900);
106
181
 
107
182
  --border: var(--color-zinc-200);
183
+ --border-strong: var(--color-zinc-300);
108
184
  --input: var(--color-zinc-200);
109
- --ring: var(--color-zinc-200);
185
+ /* The focus colour for every control, and the only one: `Semantic-Colors`
186
+ binds `ring` to brand/500 and describes it as the focus-visible ring at
187
+ >= 3:1 contrast in both modes.
188
+
189
+ Two geometries read it, which is why a focused Button and a focused Input
190
+ look different without either being drift. The Accessibility board
191
+ (node 1600:30) sets out the split:
192
+
193
+ - Outside and separated -- the default, and what Button, Icon button,
194
+ Checkbox, RadioButton and Switch draw:
195
+ `0 0 0 2px var(--color-background), 0 0 0 4px var(--color-ring)`,
196
+ which `ring-2 ring-offset-2 ring-offset-background` emits. Drawn with
197
+ shadows, so the control is the same size focused as at rest.
198
+ - Inside -- for a control that already has a border to recolour, or whose
199
+ pieces sit flush: every text field, Select, Combobox, InputOtp. The
200
+ border turns `ring`. An outside ring on a column of form fields would
201
+ climb over its neighbours, which the same board warns about.
202
+
203
+ Destructive and success controls swap the colour for their own and keep the
204
+ geometry; `OxButton`'s destructive variant is the live case. */
205
+ --ring: var(--color-brand-500);
206
+ /* Figma's value (red-200), mirrored so the scale still matches the
207
+ collection -- but nothing in `src` reads it. The variable's own description
208
+ scopes it to Horchata, and the component boards paint an invalid control's
209
+ focus ring with `error` instead: the Textarea board's `Error (Focus)`
210
+ variant (node 1096:14751) binds that ring to `error`, not to this. Do not
211
+ re-point it to make a component work; reach for `--error`. */
110
212
  --ring-error: var(--color-red-200);
111
213
 
214
+ /* The glass behind a modal, a drawer or a sheet. Figma
215
+ `Semantic-Colors/overlay` (Overlay board, node 1845:51257) aliases
216
+ `base/alpha-60`, whose name counts the opacity of what shows *through* it
217
+ and whose value is therefore 0.4 — white at 40%, not 60. Paired with
218
+ `backdrop-blur-lg` at the call site, which is the 16px the component's
219
+ effect is bound to.
220
+
221
+ Figma's dark mode points the same semantic name at `base/black-alpha-40`,
222
+ black at 40%. There is no `.dark` block in v3 yet, so that value is
223
+ recorded here rather than declared, for whoever wires dark mode up. */
224
+ --overlay: var(--color-alpha-60);
225
+
226
+ /* The overlay a control tints itself with while it is hovered, focused or
227
+ pressed, drawn at 8–30% paint opacity over the colour underneath — the
228
+ scale Figma's `Semantic-Colors` prints in the variable's own description.
229
+ White is the only value it takes in either mode, because it is meant for
230
+ coloured surfaces: a brand-filled switch track, a chip, a filled button.
231
+ Over a near-white surface it has nothing to lighten, which is a reason to
232
+ pick a darker surface rather than a reason to tint it a different colour. */
233
+ --state-layer: var(--color-white);
234
+
112
235
  --chart-1: var(--color-brand-400);
113
236
  --chart-2: var(--color-brand-500);
114
237
  --chart-3: var(--color-brand-600);
@@ -123,7 +246,7 @@
123
246
  --category-3: var(--color-amber-500); /* amber */
124
247
  --category-4: var(--color-teal-500); /* teal */
125
248
  --category-5: var(--color-orange-500); /* orange */
126
- --category-6: var(--color-zinc-500); /* zinc */
249
+ --category-6: var(--color-zinc-700); /* zinc */
127
250
  --category-7: var(--color-red-500); /* red */
128
251
  --category-8: var(--color-yellow-500); /* yellow */
129
252
  --category-9: var(--color-lime-500); /* lime */
@@ -154,13 +277,6 @@
154
277
  emission: without it Tailwind only declares the variables some generated
155
278
  utility happens to mention. Kept in step with the Vue package's copy. */
156
279
  @theme static inline {
157
- --color-font-primary: var(--font-primary);
158
- --color-font-secondary: var(--font-secondary);
159
- --color-font-tertiary: var(--font-tertiary);
160
- --color-font-error: var(--font-error);
161
- --color-font-success: var(--font-success);
162
- --color-font-warning: var(--font-warning);
163
- --color-font-info: var(--font-info);
164
280
  --color-border-default: var(--border-default);
165
281
  --color-border-hover: var(--border-hover);
166
282
  --color-bg-base: var(--bg-base);
@@ -180,8 +296,16 @@
180
296
  --color-neutral-950: var(--neutral-950);
181
297
  --color-brand: var(--brand);
182
298
  --color-brand-hover: var(--brand-hover);
299
+ --color-brand-active: var(--brand-active);
183
300
  --color-brand-foreground: var(--brand-foreground);
301
+ --color-brand-subtle: var(--brand-subtle);
302
+ --color-brand-subtle-foreground: var(--brand-subtle-foreground);
303
+ --color-brand-subtle-border: var(--brand-subtle-border);
304
+ --color-brand-subtle-active: var(--brand-subtle-active);
184
305
  --color-success: var(--success);
306
+ --color-success-hover: var(--success-hover);
307
+ --color-success-subtle: var(--success-subtle);
308
+ --color-success-subtle-foreground: var(--success-subtle-foreground);
185
309
  --color-success-foreground: var(--success-foreground);
186
310
  --color-success-100: var(--success-100);
187
311
  --color-success-300: var(--success-300);
@@ -202,6 +326,7 @@
202
326
  --color-primary: var(--primary);
203
327
  --color-primary-foreground: var(--primary-foreground);
204
328
  --color-secondary: var(--secondary);
329
+ --color-secondary-hover: var(--secondary-hover);
205
330
  --color-secondary-foreground: var(--secondary-foreground);
206
331
  --color-muted: var(--muted);
207
332
  --color-muted-foreground: var(--muted-foreground);
@@ -209,14 +334,27 @@
209
334
  --color-accent-foreground: var(--accent-foreground);
210
335
  --color-warning: var(--warning);
211
336
  --color-warning-foreground: var(--warning-foreground);
337
+ --color-warning-subtle: var(--warning-subtle);
338
+ --color-warning-subtle-foreground: var(--warning-subtle-foreground);
212
339
  --color-error: var(--error);
340
+ --color-error-hover: var(--error-hover);
213
341
  --color-error-foreground: var(--error-foreground);
342
+ --color-error-subtle: var(--error-subtle);
343
+ --color-error-subtle-foreground: var(--error-subtle-foreground);
214
344
  --color-info: var(--info);
215
345
  --color-info-foreground: var(--info-foreground);
346
+ --color-info-subtle: var(--info-subtle);
347
+ --color-info-subtle-foreground: var(--info-subtle-foreground);
348
+ --color-highlight: var(--highlight);
349
+ --color-highlight-hover: var(--highlight-hover);
350
+ --color-highlight-foreground: var(--highlight-foreground);
216
351
  --color-border: var(--border);
352
+ --color-border-strong: var(--border-strong);
217
353
  --color-input: var(--input);
218
354
  --color-ring: var(--ring);
219
355
  --color-ring-error: var(--ring-error);
356
+ --color-state-layer: var(--state-layer);
357
+ --color-overlay: var(--overlay);
220
358
  --color-chart-1: var(--chart-1);
221
359
  --color-chart-2: var(--chart-2);
222
360
  --color-chart-3: var(--chart-3);
@@ -0,0 +1,242 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ /**
7
+ * jsdom and happy-dom apply no CSS, so a computed colour here is always empty
8
+ * and a token that resolves to nothing looks exactly like one that resolves.
9
+ * The stylesheet text is the only thing that can be pinned, so it is what these
10
+ * assert — the same approach as `internal/Listbox/ListboxItem.test.tsx`.
11
+ *
12
+ * Two halves have to hold together for a token to be usable at all, and each
13
+ * fails silently without the other:
14
+ *
15
+ * - the `:root` declaration, which gives it a value;
16
+ * - the `@theme static inline` entry, which is what makes Tailwind emit a
17
+ * `--color-*` custom property and a `bg-*` / `text-*` / `border-*` utility
18
+ * for it. Declare only the first and `@apply bg-brand-subtle` is an unknown
19
+ * utility; expose only the second and the utility paints with nothing.
20
+ */
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ const theme = readFileSync(join(here, 'theme-v3.css'), 'utf8');
23
+ const src = join(here, '..', '..');
24
+
25
+ /** `--name: value;`, whitespace flattened, comments irrelevant. */
26
+ const declares = (name: string, value: string) =>
27
+ new RegExp(
28
+ `^\\s*${name}:\\s*${value.replace(/[()\-*]/g, '\\$&')};`,
29
+ 'm'
30
+ ).test(theme);
31
+
32
+ /** The `@theme static inline` block, which is the last one in the file. */
33
+ const themeBlock = theme.slice(theme.indexOf('@theme static inline'));
34
+
35
+ /** `--brand-subtle` is exposed as `--color-brand-subtle`, so drop one dash. */
36
+ const exposes = (name: string) =>
37
+ themeBlock.includes(`--color${name.slice(1)}: var(${name});`);
38
+
39
+ describe('theme-v3 brand-subtle', () => {
40
+ const family = {
41
+ '--brand-subtle': 'var(--color-brand-100)',
42
+ '--brand-subtle-foreground': 'var(--color-brand-700)',
43
+ '--brand-subtle-border': 'var(--color-brand-200)',
44
+ '--brand-subtle-active': 'var(--color-brand-200)',
45
+ };
46
+
47
+ it.each(Object.entries(family))(
48
+ 'should declare %s as the Figma binding %s',
49
+ (name, value) => {
50
+ expect(declares(name, value)).toBe(true);
51
+ }
52
+ );
53
+
54
+ it.each(Object.keys(family))('should expose %s as a utility', name => {
55
+ expect(exposes(name)).toBe(true);
56
+ });
57
+ });
58
+
59
+ describe('theme-v3 highlight', () => {
60
+ const family = {
61
+ '--highlight': 'var(--color-amber-100)',
62
+ '--highlight-hover': 'var(--color-amber-200)',
63
+ '--highlight-foreground': 'var(--color-amber-900)',
64
+ };
65
+
66
+ it.each(Object.entries(family))(
67
+ 'should declare %s as the Figma binding %s',
68
+ (name, value) => {
69
+ expect(declares(name, value)).toBe(true);
70
+ }
71
+ );
72
+
73
+ it.each(Object.keys(family))('should expose %s as a utility', name => {
74
+ expect(exposes(name)).toBe(true);
75
+ });
76
+
77
+ /**
78
+ * `--highlight` and `--warning-subtle` are both amber-100 in the v3 file, so
79
+ * one looks like a redundant copy of the other. They are not: "this is about
80
+ * you" and "careful" are different questions and Figma names them
81
+ * separately, so each has to keep its own declaration rather than alias the
82
+ * other — otherwise repainting one silently repaints the other.
83
+ */
84
+ it('should keep --highlight independent of --warning-subtle', () => {
85
+ expect(declares('--warning-subtle', 'var(--color-amber-100)')).toBe(true);
86
+ expect(theme).not.toContain('--highlight: var(--warning-subtle)');
87
+ expect(theme).not.toContain('--warning-subtle: var(--highlight)');
88
+ });
89
+ });
90
+
91
+ /**
92
+ * Every one of these reached for a ramp step directly and carried a comment
93
+ * naming the token it was standing in for. The tokens exist now, so the comment
94
+ * is gone and the call site has to be the semantic name — and must not quietly
95
+ * drift back, which is the only thing a reader of this file would not notice.
96
+ *
97
+ * `Button` was on this list and is not any more: `Outline` and `Ghost` are
98
+ * neutral now, so it reaches for no brand token at all and there is no call site
99
+ * left to pin. Its own suite asserts `brand-subtle` appears nowhere in the file,
100
+ * which is the stronger version of the same check.
101
+ */
102
+ describe('theme-v3 semantic call sites', () => {
103
+ const callSites: [string, string[], string[]][] = [
104
+ [
105
+ 'Display/Tag/Tag.module.css',
106
+ ['var(--color-brand-subtle)'],
107
+ ['var(--color-brand-100)'],
108
+ ],
109
+ [
110
+ 'Navigation/Stepper/Stepper.module.css',
111
+ ['bg-brand-subtle'],
112
+ ['bg-brand-100'],
113
+ ],
114
+ [
115
+ 'Navigation/Stepper/StepperItem.module.css',
116
+ ['bg-brand-subtle', 'text-brand-subtle-foreground'],
117
+ ['bg-brand-100', 'text-brand-700'],
118
+ ],
119
+ [
120
+ 'Navigation/Stepper/StepperSubsteps.module.css',
121
+ ['border-brand-subtle-border'],
122
+ ['border-brand-200'],
123
+ ],
124
+ [
125
+ 'Display/Mention/Mention.module.css',
126
+ ['bg-brand-subtle', 'bg-highlight', 'text-highlight-foreground'],
127
+ ['bg-amber-100', 'text-amber-900', 'var(--color-amber-200)'],
128
+ ],
129
+ [
130
+ 'Forms/Inputs/IconPicker/IconPickerItem.module.css',
131
+ ['bg-brand-subtle'],
132
+ ['bg-brand-100'],
133
+ ],
134
+ ];
135
+
136
+ it.each(callSites)('should use the token in %s', (file, wanted, retired) => {
137
+ const css = readFileSync(join(src, file), 'utf8');
138
+ for (const token of wanted) expect(css).toContain(token);
139
+ for (const stale of retired) expect(css).not.toContain(stale);
140
+ });
141
+ });
142
+
143
+ /**
144
+ * The `font-*` colour family is retired. Six of the seven were a second name
145
+ * for a semantic this file already declares — `--font-primary` for
146
+ * `--foreground`, `--font-secondary` for `--muted-foreground`, and the
147
+ * feedback four forwarding to `--error` / `--success` / `--warning` /
148
+ * `--info` — and `--font-tertiary` had no Figma counterpart at all, so its
149
+ * call sites took `--muted-foreground` and went one shade darker.
150
+ *
151
+ * Half of this is self-policing and half is not, which is why the two halves
152
+ * are asserted separately:
153
+ *
154
+ * - Taking a name out of `@theme static inline` makes `@apply text-font-primary`
155
+ * an unknown utility, and the build says so. Putting the entry *back* is the
156
+ * silent move: the utility starts resolving again, under a name Figma does
157
+ * not have, and nothing else would complain.
158
+ * - A bare `:root` declaration with no `@theme` entry is invisible both ways —
159
+ * no utility, no error, just a variable nobody reads.
160
+ */
161
+ describe('theme-v3 retired font-* family', () => {
162
+ const retired = [
163
+ '--font-primary',
164
+ '--font-secondary',
165
+ '--font-tertiary',
166
+ '--font-error',
167
+ '--font-success',
168
+ '--font-warning',
169
+ '--font-info',
170
+ ];
171
+
172
+ it.each(retired)('should not declare %s', name => {
173
+ expect(declares(name, '[^;]+')).toBe(false);
174
+ });
175
+
176
+ it.each(retired)('should not expose %s as a utility', name => {
177
+ expect(themeBlock).not.toContain(`--color${name.slice(1)}:`);
178
+ });
179
+
180
+ /**
181
+ * `--font-sans`, `--font-mono` and `--font-weight-*` share the prefix and are
182
+ * Tailwind's own font-family and weight tokens. Nothing above should have
183
+ * taken them with it.
184
+ */
185
+ it('should keep the font-family tokens, which are a different family', () => {
186
+ expect(
187
+ theme.includes('--font-sans') || themeBlock.includes('--font-sans')
188
+ ).toBe(true);
189
+ });
190
+ });
191
+
192
+ /**
193
+ * One red, and `--error` is where it lives. Figma's `Semantic-Colors` binds
194
+ * `error` to red/600 and has no second red for the same meaning, so another
195
+ * name holding the same value could only ever be drift — `--destructive` was
196
+ * collapsed into this one for exactly that reason, and `--font-error` held
197
+ * red-500 against it until the whole `font-*` family was retired, which is why
198
+ * `OxFieldGroup` drew a message and a border in two different reds.
199
+ */
200
+ describe('theme-v3 error family', () => {
201
+ it('should give the error family a single red', () => {
202
+ expect(declares('--error', 'var(--color-red-600)')).toBe(true);
203
+ });
204
+
205
+ it('should not reintroduce a second name holding the same red', () => {
206
+ expect(declares('--destructive', '[^;]+')).toBe(false);
207
+ });
208
+
209
+ /**
210
+ * Figma's value, mirrored for parity, and deliberately unread: the component
211
+ * boards paint an invalid control's focus ring with `error` instead. Pinned
212
+ * so re-pointing it looks like the change it is.
213
+ */
214
+ it('should keep ring-error at the value the collection holds', () => {
215
+ expect(declares('--ring-error', 'var(--color-red-200)')).toBe(true);
216
+ });
217
+ });
218
+
219
+ /**
220
+ * `--ring` is the focus colour for every control and the only one, which is
221
+ * what makes it the override a consumer re-points to restyle focus. A focus
222
+ * state spelled `brand` resolves to the same value today and stops tracking the
223
+ * moment that happens.
224
+ */
225
+ describe('theme-v3 focus ring', () => {
226
+ it('should bind the focus ring to the brand colour', () => {
227
+ expect(declares('--ring', 'var(--color-brand-500)')).toBe(true);
228
+ });
229
+
230
+ it.each([
231
+ '--error',
232
+ '--error-hover',
233
+ '--error-foreground',
234
+ '--error-subtle',
235
+ '--error-subtle-foreground',
236
+ '--ring',
237
+ '--ring-error',
238
+ ])('should expose %s as a utility as well as declaring it', name => {
239
+ expect(declares(name, '[^;]+')).toBe(true);
240
+ expect(exposes(name)).toBe(true);
241
+ });
242
+ });