@nationaldesignstudio/react 0.2.0 → 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 (97) hide show
  1. package/dist/component-registry.md +1310 -127
  2. package/dist/components/atoms/background/background.d.ts +13 -27
  3. package/dist/components/atoms/button/button.d.ts +64 -72
  4. package/dist/components/atoms/button/button.figma.d.ts +1 -0
  5. package/dist/components/atoms/button/icon-button.d.ts +62 -110
  6. package/dist/components/atoms/input/input-group.d.ts +278 -0
  7. package/dist/components/atoms/input/input.d.ts +121 -0
  8. package/dist/components/atoms/popover/popover.d.ts +195 -0
  9. package/dist/components/atoms/select/select.d.ts +131 -0
  10. package/dist/components/atoms/tooltip/tooltip.d.ts +161 -0
  11. package/dist/components/organisms/card/card.d.ts +3 -3
  12. package/dist/components/sections/hero/hero.d.ts +2 -2
  13. package/dist/components/sections/prose/prose.d.ts +3 -3
  14. package/dist/components/sections/river/river.d.ts +1 -1
  15. package/dist/components/sections/tout/tout.d.ts +4 -4
  16. package/dist/components/shared/floating-arrow.d.ts +34 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +13935 -7622
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/form-control.d.ts +106 -0
  21. package/dist/tokens.css +4725 -19065
  22. package/package.json +2 -1
  23. package/src/components/atoms/accordion/accordion.stories.tsx +1 -1
  24. package/src/components/atoms/accordion/accordion.tsx +2 -2
  25. package/src/components/atoms/background/background.tsx +71 -109
  26. package/src/components/atoms/button/button.figma.tsx +37 -0
  27. package/src/components/atoms/button/button.stories.tsx +253 -115
  28. package/src/components/atoms/button/button.test.tsx +289 -5
  29. package/src/components/atoms/button/button.tsx +40 -101
  30. package/src/components/atoms/button/button.visual.test.tsx +28 -32
  31. package/src/components/atoms/button/icon-button.stories.tsx +44 -101
  32. package/src/components/atoms/button/icon-button.test.tsx +26 -94
  33. package/src/components/atoms/button/icon-button.tsx +81 -224
  34. package/src/components/atoms/input/index.ts +17 -0
  35. package/src/components/atoms/input/input-group.stories.tsx +646 -0
  36. package/src/components/atoms/input/input-group.test.tsx +362 -0
  37. package/src/components/atoms/input/input-group.tsx +409 -0
  38. package/src/components/atoms/input/input.stories.tsx +228 -0
  39. package/src/components/atoms/input/input.test.tsx +167 -0
  40. package/src/components/atoms/input/input.tsx +104 -0
  41. package/src/components/atoms/pager-control/pager-control.stories.tsx +6 -8
  42. package/src/components/atoms/pager-control/pager-control.tsx +12 -12
  43. package/src/components/atoms/popover/index.ts +30 -0
  44. package/src/components/atoms/popover/popover.stories.tsx +531 -0
  45. package/src/components/atoms/popover/popover.test.tsx +486 -0
  46. package/src/components/atoms/popover/popover.tsx +488 -0
  47. package/src/components/atoms/select/index.ts +18 -0
  48. package/src/components/atoms/select/select.stories.tsx +455 -0
  49. package/src/components/atoms/select/select.tsx +324 -0
  50. package/src/components/atoms/tooltip/index.ts +24 -0
  51. package/src/components/atoms/tooltip/tooltip.stories.tsx +348 -0
  52. package/src/components/atoms/tooltip/tooltip.test.tsx +363 -0
  53. package/src/components/atoms/tooltip/tooltip.tsx +347 -0
  54. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +8 -17
  55. package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +3 -3
  56. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  57. package/src/components/organisms/card/card.stories.tsx +19 -19
  58. package/src/components/organisms/card/card.test.tsx +1 -1
  59. package/src/components/organisms/card/card.tsx +3 -3
  60. package/src/components/organisms/card/card.visual.test.tsx +11 -11
  61. package/src/components/organisms/navbar/navbar.tsx +2 -2
  62. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  63. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +2 -2
  64. package/src/components/sections/banner/banner.stories.tsx +1 -5
  65. package/src/components/sections/banner/banner.test.tsx +2 -2
  66. package/src/components/sections/banner/banner.tsx +6 -6
  67. package/src/components/sections/card-grid/card-grid.tsx +5 -5
  68. package/src/components/sections/faq-section/faq-section.tsx +2 -2
  69. package/src/components/sections/hero/hero.stories.tsx +7 -7
  70. package/src/components/sections/hero/hero.test.tsx +5 -5
  71. package/src/components/sections/hero/hero.tsx +10 -11
  72. package/src/components/sections/prose/prose.test.tsx +2 -2
  73. package/src/components/sections/prose/prose.tsx +6 -7
  74. package/src/components/sections/river/river.stories.tsx +8 -8
  75. package/src/components/sections/river/river.test.tsx +4 -4
  76. package/src/components/sections/river/river.tsx +8 -16
  77. package/src/components/sections/tout/tout.stories.tsx +7 -31
  78. package/src/components/sections/tout/tout.test.tsx +1 -1
  79. package/src/components/sections/tout/tout.tsx +11 -11
  80. package/src/components/sections/two-column-section/two-column-section.tsx +7 -9
  81. package/src/components/shared/floating-arrow.tsx +78 -0
  82. package/src/components/shared/index.ts +5 -0
  83. package/src/index.ts +98 -0
  84. package/src/lib/form-control.ts +71 -0
  85. package/src/stories/grid-system.stories.tsx +309 -0
  86. package/src/stories/{Introduction.mdx → introduction.mdx} +29 -15
  87. package/src/stories/{ThemeProvider.stories.tsx → theme-provider.stories.tsx} +8 -22
  88. package/src/stories/{TokenShowcase.stories.tsx → token-showcase.stories.tsx} +1 -20
  89. package/src/stories/token-showcase.tsx +777 -0
  90. package/src/styles.css +3 -0
  91. package/src/tests/token-resolution.test.tsx +298 -0
  92. package/src/theme/hooks.ts +1 -1
  93. package/src/theme/index.ts +1 -1
  94. package/src/theme/theme-provider.test.tsx +270 -0
  95. package/src/theme/{ThemeProvider.tsx → theme-provider.tsx} +18 -2
  96. package/src/stories/GridSystem.stories.tsx +0 -84
  97. package/src/stories/TokenShowcase.tsx +0 -1429
@@ -3,6 +3,28 @@ import { page, userEvent } from "vitest/browser";
3
3
  import { render } from "vitest-browser-react";
4
4
  import { Button } from "./button";
5
5
 
6
+ /**
7
+ * Helper to get computed styles of an element
8
+ */
9
+ function getStyles(element: HTMLElement) {
10
+ return window.getComputedStyle(element);
11
+ }
12
+
13
+ /**
14
+ * Helper to convert rgb/rgba to hex for easier comparison
15
+ */
16
+ function _rgbToHex(rgb: string): string {
17
+ // Handle rgba format
18
+ const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
19
+ if (!match) return rgb;
20
+
21
+ const r = parseInt(match[1], 10);
22
+ const g = parseInt(match[2], 10);
23
+ const b = parseInt(match[3], 10);
24
+
25
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
26
+ }
27
+
6
28
  describe("Button", () => {
7
29
  describe("Accessibility", () => {
8
30
  test("has correct button role", async () => {
@@ -124,12 +146,274 @@ describe("Button", () => {
124
146
  });
125
147
  });
126
148
 
127
- describe("Variants", () => {
128
- test("applies primary variant classes by default", async () => {
129
- render(<Button>Default</Button>);
149
+ describe("Variant Styles", () => {
150
+ test("default variant has dark background", async () => {
151
+ render(<Button variant="default">Default</Button>);
130
152
  const button = page.getByRole("button", { name: "Default" });
131
- // Button uses semantic token classes
132
- await expect.element(button).toHaveClass(/bg-button-primary-bg/);
153
+ await expect.element(button).toBeInTheDocument();
154
+
155
+ const element = button.element();
156
+ const styles = getStyles(element);
157
+
158
+ // Default variant should have a dark background (gray-1200)
159
+ // The actual color depends on CSS variables, but it should not be transparent
160
+ expect(styles.backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
161
+ expect(styles.backgroundColor).not.toBe("transparent");
162
+ });
163
+
164
+ test("primary variant has blue background", async () => {
165
+ render(<Button variant="primary">Primary</Button>);
166
+ const button = page.getByRole("button", { name: "Primary" });
167
+ await expect.element(button).toBeInTheDocument();
168
+
169
+ const element = button.element();
170
+ const styles = getStyles(element);
171
+
172
+ // Primary variant should have a visible background
173
+ expect(styles.backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
174
+ expect(styles.backgroundColor).not.toBe("transparent");
175
+ });
176
+
177
+ test("destructive variant has red-tinted background", async () => {
178
+ render(<Button variant="destructive">Delete</Button>);
179
+ const button = page.getByRole("button", { name: "Delete" });
180
+ await expect.element(button).toBeInTheDocument();
181
+
182
+ const element = button.element();
183
+ const styles = getStyles(element);
184
+
185
+ // Destructive variant should have a visible background
186
+ expect(styles.backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
187
+ expect(styles.backgroundColor).not.toBe("transparent");
188
+ });
189
+
190
+ test("ghost variant has transparent background", async () => {
191
+ render(<Button variant="ghost">Ghost</Button>);
192
+ const button = page.getByRole("button", { name: "Ghost" });
193
+ await expect.element(button).toBeInTheDocument();
194
+
195
+ const element = button.element();
196
+ const styles = getStyles(element);
197
+
198
+ // Ghost variant should have transparent background
199
+ expect(
200
+ styles.backgroundColor === "rgba(0, 0, 0, 0)" ||
201
+ styles.backgroundColor === "transparent",
202
+ ).toBe(true);
203
+ });
204
+
205
+ test("link variant has no background and underline on hover behavior", async () => {
206
+ render(<Button variant="link">Link</Button>);
207
+ const button = page.getByRole("button", { name: "Link" });
208
+ await expect.element(button).toBeInTheDocument();
209
+
210
+ const element = button.element();
211
+ const styles = getStyles(element);
212
+
213
+ // Link variant should have transparent background
214
+ expect(
215
+ styles.backgroundColor === "rgba(0, 0, 0, 0)" ||
216
+ styles.backgroundColor === "transparent",
217
+ ).toBe(true);
218
+ });
219
+
220
+ test("outline variant has visible border", async () => {
221
+ render(<Button variant="outline">Outline</Button>);
222
+ const button = page.getByRole("button", { name: "Outline" });
223
+ await expect.element(button).toBeInTheDocument();
224
+
225
+ const element = button.element();
226
+ const styles = getStyles(element);
227
+
228
+ // Outline variant should have a visible border
229
+ const borderWidth = parseFloat(styles.borderWidth) || 0;
230
+ expect(borderWidth).toBeGreaterThan(0);
231
+ });
232
+
233
+ test("secondary variant has light gray background with border", async () => {
234
+ render(<Button variant="secondary">Secondary</Button>);
235
+ const button = page.getByRole("button", { name: "Secondary" });
236
+ await expect.element(button).toBeInTheDocument();
237
+
238
+ const element = button.element();
239
+ const styles = getStyles(element);
240
+
241
+ // Secondary variant should have a visible background
242
+ expect(styles.backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
243
+ expect(styles.backgroundColor).not.toBe("transparent");
244
+
245
+ // Secondary should also have a border
246
+ const borderWidth = parseFloat(styles.borderWidth) || 0;
247
+ expect(borderWidth).toBeGreaterThan(0);
248
+ });
249
+ });
250
+
251
+ describe("Size Styles", () => {
252
+ test("small size has 32px height", async () => {
253
+ render(<Button size="sm">Small</Button>);
254
+ const button = page.getByRole("button", { name: "Small" });
255
+ await expect.element(button).toBeInTheDocument();
256
+
257
+ const element = button.element();
258
+ const styles = getStyles(element);
259
+
260
+ // Small button should be 32px height
261
+ expect(styles.height).toBe("32px");
262
+ });
263
+
264
+ test("default size has 36px height", async () => {
265
+ render(<Button size="default">Default</Button>);
266
+ const button = page.getByRole("button", { name: "Default" });
267
+ await expect.element(button).toBeInTheDocument();
268
+
269
+ const element = button.element();
270
+ const styles = getStyles(element);
271
+
272
+ // Default button should be 36px height
273
+ expect(styles.height).toBe("36px");
274
+ });
275
+
276
+ test("large size has 40px height", async () => {
277
+ render(<Button size="lg">Large</Button>);
278
+ const button = page.getByRole("button", { name: "Large" });
279
+ await expect.element(button).toBeInTheDocument();
280
+
281
+ const element = button.element();
282
+ const styles = getStyles(element);
283
+
284
+ // Large button should be 40px height
285
+ expect(styles.height).toBe("40px");
286
+ });
287
+
288
+ test("size affects padding appropriately", async () => {
289
+ render(
290
+ <>
291
+ <Button size="sm">Small</Button>
292
+ <Button size="lg">Large</Button>
293
+ </>,
294
+ );
295
+ const smallButton = page.getByRole("button", { name: "Small" });
296
+ const largeButton = page.getByRole("button", { name: "Large" });
297
+
298
+ const smallStyles = getStyles(smallButton.element());
299
+ const largeStyles = getStyles(largeButton.element());
300
+
301
+ const smallPaddingLeft = parseFloat(smallStyles.paddingLeft);
302
+ const largePaddingLeft = parseFloat(largeStyles.paddingLeft);
303
+
304
+ // Large should have more padding than small
305
+ expect(largePaddingLeft).toBeGreaterThan(smallPaddingLeft);
306
+ });
307
+
308
+ test("size affects border radius appropriately", async () => {
309
+ render(
310
+ <>
311
+ <Button size="sm">Small</Button>
312
+ <Button size="lg">Large</Button>
313
+ </>,
314
+ );
315
+ const smallButton = page.getByRole("button", { name: "Small" });
316
+ const largeButton = page.getByRole("button", { name: "Large" });
317
+
318
+ const smallStyles = getStyles(smallButton.element());
319
+ const largeStyles = getStyles(largeButton.element());
320
+
321
+ const smallRadius = parseFloat(smallStyles.borderRadius);
322
+ const largeRadius = parseFloat(largeStyles.borderRadius);
323
+
324
+ // Large should have more border radius than small
325
+ // Figma: sm=4px, default=6px, lg=10px
326
+ expect(largeRadius).toBeGreaterThan(smallRadius);
327
+ });
328
+ });
329
+
330
+ describe("Disabled State Styles", () => {
331
+ test("disabled button has reduced opacity", async () => {
332
+ render(<Button disabled>Disabled</Button>);
333
+ const button = page.getByRole("button", { name: "Disabled" });
334
+ await expect.element(button).toBeInTheDocument();
335
+
336
+ const element = button.element();
337
+ const styles = getStyles(element);
338
+
339
+ // Disabled button should have opacity of 0.5
340
+ expect(parseFloat(styles.opacity)).toBe(0.5);
341
+ });
342
+
343
+ test("disabled button has pointer-events none", async () => {
344
+ render(<Button disabled>Disabled</Button>);
345
+ const button = page.getByRole("button", { name: "Disabled" });
346
+ await expect.element(button).toBeInTheDocument();
347
+
348
+ const element = button.element();
349
+ const styles = getStyles(element);
350
+
351
+ expect(styles.pointerEvents).toBe("none");
352
+ });
353
+ });
354
+
355
+ describe("Data Attributes", () => {
356
+ test("button has correct data-variant attribute", async () => {
357
+ render(<Button variant="destructive">Test</Button>);
358
+ const button = page.getByRole("button", { name: "Test" });
359
+
360
+ await expect
361
+ .element(button)
362
+ .toHaveAttribute("data-variant", "destructive");
363
+ });
364
+
365
+ test("button has correct data-size attribute", async () => {
366
+ render(<Button size="lg">Test</Button>);
367
+ const button = page.getByRole("button", { name: "Test" });
368
+
369
+ await expect.element(button).toHaveAttribute("data-size", "lg");
370
+ });
371
+
372
+ test("default variant and size are reflected in data attributes", async () => {
373
+ render(<Button>Test</Button>);
374
+ const button = page.getByRole("button", { name: "Test" });
375
+
376
+ await expect.element(button).toHaveAttribute("data-variant", "default");
377
+ await expect.element(button).toHaveAttribute("data-size", "default");
378
+ });
379
+ });
380
+
381
+ describe("Layout Styles", () => {
382
+ test("button uses flexbox for content alignment", async () => {
383
+ render(<Button>Test</Button>);
384
+ const button = page.getByRole("button", { name: "Test" });
385
+ await expect.element(button).toBeInTheDocument();
386
+
387
+ const element = button.element();
388
+ const styles = getStyles(element);
389
+
390
+ expect(styles.display).toBe("inline-flex");
391
+ expect(styles.alignItems).toBe("center");
392
+ expect(styles.justifyContent).toBe("center");
393
+ });
394
+
395
+ test("button has cursor pointer", async () => {
396
+ render(<Button>Test</Button>);
397
+ const button = page.getByRole("button", { name: "Test" });
398
+ await expect.element(button).toBeInTheDocument();
399
+
400
+ const element = button.element();
401
+ const styles = getStyles(element);
402
+
403
+ expect(styles.cursor).toBe("pointer");
404
+ });
405
+
406
+ test("button has gap for icon-text spacing", async () => {
407
+ render(<Button>Test</Button>);
408
+ const button = page.getByRole("button", { name: "Test" });
409
+ await expect.element(button).toBeInTheDocument();
410
+
411
+ const element = button.element();
412
+ const styles = getStyles(element);
413
+
414
+ // Gap should be set (6px from Figma)
415
+ const gap = parseFloat(styles.gap);
416
+ expect(gap).toBeGreaterThan(0);
133
417
  });
134
418
  });
135
419
  });
@@ -9,22 +9,21 @@ import { tv, type VariantProps } from "tailwind-variants";
9
9
  import { type ButtonTheme, buttonThemeToStyleVars } from "../../../lib/theme";
10
10
 
11
11
  /**
12
- * Button component based on Figma BaseKit / Interface / Buttons
12
+ * Button component based on Figma Button component
13
13
  *
14
- * Variants:
15
- * - solid: Filled button
16
- * - outline: Outlined button
17
- * - ghost: No background/border, just text
18
- * - subtle: Light background with subtle styling
14
+ * Variants (matches Figma):
15
+ * - primary: Blue filled button for primary actions
16
+ * - default: Dark filled button for secondary prominence
17
+ * - secondary: Light gray filled button with subtle border
18
+ * - destructive: Red filled button for destructive actions
19
+ * - outline: Bordered button with transparent background
20
+ * - ghost: Transparent button with subtle hover
21
+ * - link: Text-only button with underline on hover
19
22
  *
20
- * Color Schemes:
21
- * - dark: Dark colors for use on light backgrounds (default)
22
- * - light: Light colors for use on dark backgrounds
23
- *
24
- * Sizes:
25
- * - lg: Large buttons
26
- * - default: Medium buttons
27
- * - sm: Small buttons
23
+ * Sizes (matches Figma):
24
+ * - sm: Small buttons (32px height)
25
+ * - default: Default buttons (36px height)
26
+ * - lg: Large buttons (40px height)
28
27
  *
29
28
  * For icon-only buttons, use the IconButton component instead.
30
29
  *
@@ -32,89 +31,42 @@ import { type ButtonTheme, buttonThemeToStyleVars } from "../../../lib/theme";
32
31
  * Pass a `theme` prop to override default colors via CSS custom properties.
33
32
  */
34
33
  const buttonVariants = tv({
35
- base: "inline-flex items-center justify-center gap-spacing-8 whitespace-nowrap transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 rounded-surface-button stroke-surface-button border-solid",
34
+ base: "inline-flex items-center justify-center gap-spatial-ui-button-gap-icon-text whitespace-nowrap transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50",
36
35
  variants: {
37
36
  variant: {
38
- solid: "",
39
- outline: "bg-transparent border",
40
- ghost: "bg-transparent border-transparent",
41
- subtle: "border",
37
+ // Primary - blue filled button
38
+ primary:
39
+ "bg-button-primary-bg text-button-primary-text hover:bg-button-primary-bg-hover active:bg-button-primary-bg-active border-transparent",
40
+ // Default - dark filled button
41
+ default:
42
+ "bg-button-default-bg text-button-default-text hover:bg-button-default-bg-hover active:bg-button-default-bg-active border-transparent",
43
+ // Secondary - light gray filled with subtle border
44
+ secondary:
45
+ "bg-button-secondary-bg text-button-secondary-text hover:bg-button-secondary-bg-hover active:bg-button-secondary-bg-active border border-button-secondary-border",
46
+ // Destructive - red filled button
47
+ destructive:
48
+ "bg-button-destructive-bg text-button-destructive-text hover:bg-button-destructive-bg-hover active:bg-button-destructive-bg-active border-transparent",
49
+ // Outline - bordered with transparent background
50
+ outline:
51
+ "bg-button-outline-bg text-button-outline-text hover:bg-button-outline-bg-hover active:bg-button-outline-bg-active border border-button-outline-border hover:border-button-outline-border-hover",
52
+ // Ghost - transparent with subtle hover
53
+ ghost:
54
+ "bg-button-ghost-bg text-button-ghost-text hover:bg-button-ghost-bg-hover active:bg-button-ghost-bg-active border-transparent",
55
+ // Link - text only with underline on hover
56
+ link: "bg-transparent text-button-link-text hover:text-button-link-text-hover hover:underline active:text-button-link-text-hover border-transparent underline-offset-4",
42
57
  // Themed - uses CSS custom properties for styling
43
58
  themed:
44
59
  "[background:var(--btn-bg)] [color:var(--btn-text)] [border-color:var(--btn-border-color,transparent)] hover:[background:var(--btn-bg-hover,var(--btn-bg))] active:[background:var(--btn-bg-active,var(--btn-bg-hover,var(--btn-bg)))]",
45
60
  },
46
- colorScheme: {
47
- dark: "",
48
- light: "",
49
- },
50
61
  size: {
51
- lg: "px-spacing-24 py-spacing-12 typography-large-button-large h-spacing-48",
62
+ sm: "typography-ui-text-xs h-spatial-ui-button-height-small px-spatial-ui-button-padding-x-small py-spatial-ui-button-padding-y-small rounded-surface-button-small",
52
63
  default:
53
- "px-spacing-20 py-spacing-10 typography-medium-button-medium h-spacing-40",
54
- sm: "px-spacing-16 py-spacing-8 typography-small-button-small h-spacing-32",
64
+ "typography-ui-text-sm h-spatial-ui-button-height-medium px-spatial-ui-button-padding-x-medium py-spatial-ui-button-padding-y-medium rounded-surface-button-medium",
65
+ lg: "typography-ui-text-md h-spatial-ui-button-height-large px-spatial-ui-button-padding-x-large py-spatial-ui-button-padding-y-large rounded-surface-button-large",
55
66
  },
56
67
  },
57
- compoundVariants: [
58
- // Solid + Dark (for light backgrounds) - uses semantic button tokens
59
- {
60
- variant: "solid",
61
- colorScheme: "dark",
62
- class:
63
- "bg-button-primary-bg text-text-inverted hover:bg-button-primary-bg-hover active:bg-button-primary-bg-hover border-transparent focus-visible:ring-button-primary-bg",
64
- },
65
- // Solid + Light (for dark backgrounds)
66
- {
67
- variant: "solid",
68
- colorScheme: "light",
69
- class:
70
- "bg-button-secondary-bg text-text-primary hover:bg-button-secondary-bg-hover active:bg-gray-200 border-transparent focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
71
- },
72
- // Outline + Dark (for light backgrounds)
73
- {
74
- variant: "outline",
75
- colorScheme: "dark",
76
- class:
77
- "border-border-subtle text-gray-1000 hover:bg-alpha-black-5 active:bg-alpha-black-10 focus-visible:ring-gray-1000",
78
- },
79
- // Outline + Light (for dark backgrounds)
80
- {
81
- variant: "outline",
82
- colorScheme: "light",
83
- class:
84
- "border-gray-50 text-gray-50 hover:bg-alpha-white-10 active:bg-alpha-white-20 focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
85
- },
86
- // Ghost + Dark (for light backgrounds)
87
- {
88
- variant: "ghost",
89
- colorScheme: "dark",
90
- class:
91
- "text-gray-700 hover:text-gray-900 hover:bg-alpha-black-5 active:bg-alpha-black-10 focus-visible:ring-gray-1000",
92
- },
93
- // Ghost + Light (for dark backgrounds)
94
- {
95
- variant: "ghost",
96
- colorScheme: "light",
97
- class:
98
- "text-gray-300 hover:text-gray-100 hover:bg-alpha-white-10 active:bg-alpha-white-20 focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
99
- },
100
- // Subtle + Dark (for light backgrounds)
101
- {
102
- variant: "subtle",
103
- colorScheme: "dark",
104
- class:
105
- "border-border-subtle text-alpha-black-60 hover:border-border-strong hover:text-alpha-black-80 active:bg-alpha-black-5 focus-visible:ring-gray-1000",
106
- },
107
- // Subtle + Light (for dark backgrounds)
108
- {
109
- variant: "subtle",
110
- colorScheme: "light",
111
- class:
112
- "border-alpha-white-20 text-alpha-white-60 hover:border-alpha-white-30 hover:text-alpha-white-80 active:bg-alpha-white-5 focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
113
- },
114
- ],
115
68
  defaultVariants: {
116
- variant: "solid",
117
- colorScheme: "dark",
69
+ variant: "default",
118
70
  size: "default",
119
71
  },
120
72
  });
@@ -139,17 +91,7 @@ function hasThemeValues(theme: ButtonTheme | undefined): boolean {
139
91
 
140
92
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
141
93
  (
142
- {
143
- className,
144
- variant,
145
- colorScheme,
146
- size,
147
- render,
148
- nativeButton,
149
- theme,
150
- style,
151
- ...props
152
- },
94
+ { className, variant, size, render, nativeButton, theme, style, ...props },
153
95
  ref,
154
96
  ) => {
155
97
  // When render prop is provided, default nativeButton to false to suppress warnings
@@ -162,15 +104,13 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
162
104
  const combinedStyles = hasTheme ? { ...themeStyles, ...style } : style;
163
105
 
164
106
  // Resolve actual values for data attributes
165
- const resolvedVariant = effectiveVariant ?? "solid";
166
- const resolvedColorScheme = colorScheme ?? "dark";
107
+ const resolvedVariant = effectiveVariant ?? "default";
167
108
  const resolvedSize = size ?? "default";
168
109
 
169
110
  return (
170
111
  <BaseButton
171
112
  className={buttonVariants({
172
113
  variant: effectiveVariant,
173
- colorScheme,
174
114
  size,
175
115
  class: className,
176
116
  })}
@@ -179,7 +119,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
179
119
  nativeButton={isNativeButton}
180
120
  style={combinedStyles}
181
121
  data-variant={resolvedVariant}
182
- data-color-scheme={resolvedColorScheme}
183
122
  data-size={resolvedSize}
184
123
  {...props}
185
124
  />
@@ -4,69 +4,66 @@ import { page } from "vitest/browser";
4
4
  import { Button } from "./button";
5
5
 
6
6
  describe("Button Visual Regression", () => {
7
- test("charcoal variant renders correctly", async () => {
8
- render(<Button variant="charcoal">Charcoal Button</Button>);
7
+ test("primary variant renders correctly", async () => {
8
+ render(<Button variant="primary">Primary Button</Button>);
9
9
 
10
10
  await expect(
11
- page.getByRole("button", { name: "Charcoal Button" }),
12
- ).toMatchScreenshot("button-charcoal");
11
+ page.getByRole("button", { name: "Primary Button" }),
12
+ ).toMatchScreenshot("button-primary");
13
13
  });
14
14
 
15
- test("charcoalOutline variant renders correctly", async () => {
16
- render(<Button variant="charcoalOutline">Charcoal Outline Button</Button>);
15
+ test("primary-outline variant renders correctly", async () => {
16
+ render(<Button variant="primary-outline">Primary Outline Button</Button>);
17
17
 
18
18
  await expect(
19
- page.getByRole("button", { name: "Charcoal Outline Button" }),
20
- ).toMatchScreenshot("button-charcoal-outline");
19
+ page.getByRole("button", { name: "Primary Outline Button" }),
20
+ ).toMatchScreenshot("button-primary-outline");
21
21
  });
22
22
 
23
- test("charcoalOutlineQuiet variant renders correctly", async () => {
23
+ test("secondary variant renders correctly", async () => {
24
24
  render(
25
- <Button variant="charcoalOutlineQuiet">Charcoal Outline Quiet</Button>,
25
+ <div style={{ background: "#1a1a1a", padding: "20px" }}>
26
+ <Button variant="secondary">Secondary Button</Button>
27
+ </div>,
26
28
  );
27
29
 
28
30
  await expect(
29
- page.getByRole("button", { name: "Charcoal Outline Quiet" }),
30
- ).toMatchScreenshot("button-charcoal-outline-quiet");
31
+ page.getByRole("button", { name: "Secondary Button" }),
32
+ ).toMatchScreenshot("button-secondary");
31
33
  });
32
34
 
33
- test("ivory variant renders correctly", async () => {
35
+ test("secondary-outline variant renders correctly", async () => {
34
36
  render(
35
37
  <div style={{ background: "#1a1a1a", padding: "20px" }}>
36
- <Button variant="ivory">Ivory Button</Button>
38
+ <Button variant="secondary-outline">Secondary Outline Button</Button>
37
39
  </div>,
38
40
  );
39
41
 
40
42
  await expect(
41
- page.getByRole("button", { name: "Ivory Button" }),
42
- ).toMatchScreenshot("button-ivory");
43
+ page.getByRole("button", { name: "Secondary Outline Button" }),
44
+ ).toMatchScreenshot("button-secondary-outline");
43
45
  });
44
46
 
45
- test("ivoryOutline variant renders correctly", async () => {
46
- render(
47
- <div style={{ background: "#1a1a1a", padding: "20px" }}>
48
- <Button variant="ivoryOutline">Ivory Outline Button</Button>
49
- </div>,
50
- );
47
+ test("ghost variant renders correctly", async () => {
48
+ render(<Button variant="ghost">Ghost Button</Button>);
51
49
 
52
50
  await expect(
53
- page.getByRole("button", { name: "Ivory Outline Button" }),
54
- ).toMatchScreenshot("button-ivory-outline");
51
+ page.getByRole("button", { name: "Ghost Button" }),
52
+ ).toMatchScreenshot("button-ghost");
55
53
  });
56
54
 
57
- test("ivoryOutlineQuiet variant renders correctly", async () => {
55
+ test("ghost-inverse variant renders correctly", async () => {
58
56
  render(
59
57
  <div style={{ background: "#1a1a1a", padding: "20px" }}>
60
- <Button variant="ivoryOutlineQuiet">Ivory Outline Quiet</Button>
58
+ <Button variant="ghost-inverse">Ghost Inverse Button</Button>
61
59
  </div>,
62
60
  );
63
61
 
64
62
  await expect(
65
- page.getByRole("button", { name: "Ivory Outline Quiet" }),
66
- ).toMatchScreenshot("button-ivory-outline-quiet");
63
+ page.getByRole("button", { name: "Ghost Inverse Button" }),
64
+ ).toMatchScreenshot("button-ghost-inverse");
67
65
  });
68
66
 
69
- // Size variants
70
67
  test("small size renders correctly", async () => {
71
68
  render(<Button size="sm">Small Button</Button>);
72
69
 
@@ -75,8 +72,8 @@ describe("Button Visual Regression", () => {
75
72
  ).toMatchScreenshot("button-size-small");
76
73
  });
77
74
 
78
- test("medium (default) size renders correctly", async () => {
79
- render(<Button size="default">Medium Button</Button>);
75
+ test("medium size renders correctly", async () => {
76
+ render(<Button size="md">Medium Button</Button>);
80
77
 
81
78
  await expect(
82
79
  page.getByRole("button", { name: "Medium Button" }),
@@ -91,7 +88,6 @@ describe("Button Visual Regression", () => {
91
88
  ).toMatchScreenshot("button-size-large");
92
89
  });
93
90
 
94
- // Disabled state
95
91
  test("disabled state renders correctly", async () => {
96
92
  render(<Button disabled>Disabled Button</Button>);
97
93