@nationaldesignstudio/react 0.1.0 → 0.3.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 (74) hide show
  1. package/dist/component-registry.md +46 -19
  2. package/dist/components/atoms/accordion/accordion.d.ts +7 -7
  3. package/dist/components/atoms/background/background.d.ts +13 -27
  4. package/dist/components/atoms/button/button.d.ts +55 -71
  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/select/select.d.ts +131 -0
  9. package/dist/components/organisms/card/card.d.ts +2 -2
  10. package/dist/components/sections/banner/banner.d.ts +9 -9
  11. package/dist/components/sections/faq-section/faq-section.d.ts +1 -1
  12. package/dist/components/sections/hero/hero.d.ts +115 -18
  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 +9 -9
  16. package/dist/components/sections/two-column-section/two-column-section.d.ts +7 -21
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +11075 -7841
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/form-control.d.ts +105 -0
  21. package/dist/tokens.css +2144 -17341
  22. package/package.json +1 -1
  23. package/src/components/atoms/accordion/accordion.test.tsx +18 -20
  24. package/src/components/atoms/accordion/accordion.tsx +19 -17
  25. package/src/components/atoms/background/background.test.tsx +2 -2
  26. package/src/components/atoms/background/background.tsx +77 -96
  27. package/src/components/atoms/button/button.stories.tsx +42 -0
  28. package/src/components/atoms/button/button.test.tsx +1 -1
  29. package/src/components/atoms/button/button.tsx +38 -103
  30. package/src/components/atoms/button/button.visual.test.tsx +70 -24
  31. package/src/components/atoms/button/icon-button.tsx +80 -188
  32. package/src/components/atoms/input/index.ts +17 -0
  33. package/src/components/atoms/input/input-group.stories.tsx +650 -0
  34. package/src/components/atoms/input/input-group.test.tsx +376 -0
  35. package/src/components/atoms/input/input-group.tsx +384 -0
  36. package/src/components/atoms/input/input.stories.tsx +232 -0
  37. package/src/components/atoms/input/input.test.tsx +183 -0
  38. package/src/components/atoms/input/input.tsx +97 -0
  39. package/src/components/atoms/select/index.ts +18 -0
  40. package/src/components/atoms/select/select.stories.tsx +455 -0
  41. package/src/components/atoms/select/select.tsx +320 -0
  42. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +2 -6
  43. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  44. package/src/components/organisms/card/card.stories.tsx +11 -11
  45. package/src/components/organisms/card/card.test.tsx +5 -3
  46. package/src/components/organisms/card/card.tsx +2 -2
  47. package/src/components/organisms/card/card.visual.test.tsx +6 -6
  48. package/src/components/organisms/navbar/navbar.tsx +2 -2
  49. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  50. package/src/components/sections/banner/banner.stories.tsx +5 -1
  51. package/src/components/sections/banner/banner.tsx +10 -10
  52. package/src/components/sections/card-grid/card-grid.tsx +1 -1
  53. package/src/components/sections/faq-section/faq-section.stories.tsx +7 -7
  54. package/src/components/sections/faq-section/faq-section.tsx +5 -5
  55. package/src/components/sections/hero/hero.test.tsx +5 -5
  56. package/src/components/sections/hero/hero.tsx +33 -51
  57. package/src/components/sections/prose/prose.test.tsx +2 -2
  58. package/src/components/sections/prose/prose.tsx +4 -5
  59. package/src/components/sections/river/river.stories.tsx +8 -8
  60. package/src/components/sections/river/river.test.tsx +1 -1
  61. package/src/components/sections/river/river.tsx +2 -4
  62. package/src/components/sections/tout/tout.stories.tsx +31 -7
  63. package/src/components/sections/tout/tout.test.tsx +1 -1
  64. package/src/components/sections/tout/tout.tsx +8 -10
  65. package/src/components/sections/two-column-section/two-column-section.stories.tsx +11 -11
  66. package/src/components/sections/two-column-section/two-column-section.tsx +16 -10
  67. package/src/index.ts +41 -0
  68. package/src/lib/form-control.ts +69 -0
  69. package/src/stories/Introduction.mdx +29 -15
  70. package/src/stories/ThemeProvider.stories.tsx +1 -3
  71. package/src/stories/TokenShowcase.stories.tsx +0 -19
  72. package/src/stories/TokenShowcase.tsx +714 -1366
  73. package/src/styles.css +3 -0
  74. package/src/tests/token-resolution.test.tsx +301 -0
@@ -9,22 +9,20 @@ 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
19
- *
20
- * Color Schemes:
21
- * - dark: Dark colors for use on light backgrounds (default)
22
- * - light: Light colors for use on dark backgrounds
14
+ * Variants (matches Figma):
15
+ * - primary: Filled brand button (indigo background)
16
+ * - primary-outline: Outlined brand button (indigo border/text)
17
+ * - secondary: Filled neutral button (white background, for dark backgrounds)
18
+ * - secondary-outline: Outlined neutral button (white border/text, for dark backgrounds)
19
+ * - ghost: Transparent button with subtle hover (for light backgrounds)
20
+ * - ghost-inverse: Transparent button with subtle hover (for dark backgrounds)
23
21
  *
24
22
  * Sizes:
25
- * - lg: Large buttons
26
- * - default: Medium buttons
27
- * - sm: Small buttons
23
+ * - lg: Large buttons (56px height)
24
+ * - md: Medium buttons (40px height) - default
25
+ * - sm: Small buttons (28px height)
28
26
  *
29
27
  * For icon-only buttons, use the IconButton component instead.
30
28
  *
@@ -32,90 +30,40 @@ import { type ButtonTheme, buttonThemeToStyleVars } from "../../../lib/theme";
32
30
  * Pass a `theme` prop to override default colors via CSS custom properties.
33
31
  */
34
32
  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",
33
+ base: "inline-flex items-center justify-center gap-6 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 border-solid",
36
34
  variants: {
37
35
  variant: {
38
- solid: "",
39
- outline: "bg-transparent border",
40
- ghost: "bg-transparent border-transparent",
41
- subtle: "border",
36
+ // Primary - filled brand button
37
+ primary:
38
+ "bg-button-primary-bg text-button-primary-text hover:bg-button-primary-bg-hover hover:text-button-primary-text-hover border-transparent focus-visible:ring-button-primary-bg",
39
+ // Primary Outline - outlined brand button
40
+ "primary-outline":
41
+ "bg-button-primary-outline-bg text-button-primary-outline-text border border-button-primary-outline-border hover:bg-button-primary-outline-bg-hover hover:text-button-primary-outline-text-hover hover:border-button-primary-outline-border-hover focus-visible:ring-button-primary-outline-border",
42
+ // Secondary - filled neutral button (for dark backgrounds)
43
+ secondary:
44
+ "bg-button-secondary-bg text-button-secondary-text hover:bg-button-secondary-bg-hover hover:text-button-secondary-text-hover border-transparent focus-visible:ring-button-secondary-bg focus-visible:ring-offset-gray-1000",
45
+ // Secondary Outline - outlined neutral button (for dark backgrounds)
46
+ "secondary-outline":
47
+ "bg-button-secondary-outline-bg text-button-secondary-outline-text border border-button-secondary-outline-border hover:bg-button-secondary-outline-bg-hover hover:text-button-secondary-outline-text-hover hover:border-button-secondary-outline-border-hover focus-visible:ring-button-secondary-outline-border focus-visible:ring-offset-gray-1000",
48
+ // Ghost - transparent button (for light backgrounds)
49
+ ghost:
50
+ "bg-button-ghost-bg text-button-ghost-text hover:bg-button-ghost-bg-hover hover:text-button-ghost-text-hover border-transparent focus-visible:ring-gray-1000",
51
+ // Ghost Inverse - transparent button (for dark backgrounds)
52
+ "ghost-inverse":
53
+ "bg-button-ghost-inverse-bg text-button-ghost-inverse-text hover:bg-button-ghost-inverse-bg-hover hover:text-button-ghost-inverse-text-hover border-transparent focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
42
54
  // Themed - uses CSS custom properties for styling
43
55
  themed:
44
56
  "[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
57
  },
46
- colorScheme: {
47
- dark: "",
48
- light: "",
49
- },
50
58
  size: {
51
- lg: "px-spacing-24 py-spacing-12 typography-large-button-large h-spacing-48",
52
- 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",
59
+ lg: "px-32 py-20 h-56 rounded-10 typography-large-button-large",
60
+ md: "px-20 py-12 h-40 rounded-6 typography-medium-button-medium",
61
+ sm: "px-12 py-8 h-28 rounded-4 typography-small-button-small",
55
62
  },
56
63
  },
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-strong 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
64
  defaultVariants: {
116
- variant: "solid",
117
- colorScheme: "dark",
118
- size: "default",
65
+ variant: "primary",
66
+ size: "md",
119
67
  },
120
68
  });
121
69
 
@@ -139,17 +87,7 @@ function hasThemeValues(theme: ButtonTheme | undefined): boolean {
139
87
 
140
88
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
141
89
  (
142
- {
143
- className,
144
- variant,
145
- colorScheme,
146
- size,
147
- render,
148
- nativeButton,
149
- theme,
150
- style,
151
- ...props
152
- },
90
+ { className, variant, size, render, nativeButton, theme, style, ...props },
153
91
  ref,
154
92
  ) => {
155
93
  // When render prop is provided, default nativeButton to false to suppress warnings
@@ -162,15 +100,13 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
162
100
  const combinedStyles = hasTheme ? { ...themeStyles, ...style } : style;
163
101
 
164
102
  // Resolve actual values for data attributes
165
- const resolvedVariant = effectiveVariant ?? "solid";
166
- const resolvedColorScheme = colorScheme ?? "dark";
167
- const resolvedSize = size ?? "default";
103
+ const resolvedVariant = effectiveVariant ?? "primary";
104
+ const resolvedSize = size ?? "md";
168
105
 
169
106
  return (
170
107
  <BaseButton
171
108
  className={buttonVariants({
172
109
  variant: effectiveVariant,
173
- colorScheme,
174
110
  size,
175
111
  class: className,
176
112
  })}
@@ -179,7 +115,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
179
115
  nativeButton={isNativeButton}
180
116
  style={combinedStyles}
181
117
  data-variant={resolvedVariant}
182
- data-color-scheme={resolvedColorScheme}
183
118
  data-size={resolvedSize}
184
119
  {...props}
185
120
  />
@@ -4,66 +4,112 @@ 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
+ // Solid variants
8
+ test("solid dark variant renders correctly", async () => {
9
+ render(
10
+ <Button variant="solid" colorScheme="dark">
11
+ Solid Dark Button
12
+ </Button>,
13
+ );
9
14
 
10
15
  await expect(
11
- page.getByRole("button", { name: "Charcoal Button" }),
12
- ).toMatchScreenshot("button-charcoal");
16
+ page.getByRole("button", { name: "Solid Dark Button" }),
17
+ ).toMatchScreenshot("button-solid-dark");
13
18
  });
14
19
 
15
- test("charcoalOutline variant renders correctly", async () => {
16
- render(<Button variant="charcoalOutline">Charcoal Outline Button</Button>);
20
+ test("solid light variant renders correctly", async () => {
21
+ render(
22
+ <div style={{ background: "#1a1a1a", padding: "20px" }}>
23
+ <Button variant="solid" colorScheme="light">
24
+ Solid Light Button
25
+ </Button>
26
+ </div>,
27
+ );
17
28
 
18
29
  await expect(
19
- page.getByRole("button", { name: "Charcoal Outline Button" }),
20
- ).toMatchScreenshot("button-charcoal-outline");
30
+ page.getByRole("button", { name: "Solid Light Button" }),
31
+ ).toMatchScreenshot("button-solid-light");
21
32
  });
22
33
 
23
- test("charcoalOutlineQuiet variant renders correctly", async () => {
34
+ // Outline variants
35
+ test("outline dark variant renders correctly", async () => {
24
36
  render(
25
- <Button variant="charcoalOutlineQuiet">Charcoal Outline Quiet</Button>,
37
+ <Button variant="outline" colorScheme="dark">
38
+ Outline Dark Button
39
+ </Button>,
26
40
  );
27
41
 
28
42
  await expect(
29
- page.getByRole("button", { name: "Charcoal Outline Quiet" }),
30
- ).toMatchScreenshot("button-charcoal-outline-quiet");
43
+ page.getByRole("button", { name: "Outline Dark Button" }),
44
+ ).toMatchScreenshot("button-outline-dark");
31
45
  });
32
46
 
33
- test("ivory variant renders correctly", async () => {
47
+ test("outline light variant renders correctly", async () => {
34
48
  render(
35
49
  <div style={{ background: "#1a1a1a", padding: "20px" }}>
36
- <Button variant="ivory">Ivory Button</Button>
50
+ <Button variant="outline" colorScheme="light">
51
+ Outline Light Button
52
+ </Button>
37
53
  </div>,
38
54
  );
39
55
 
40
56
  await expect(
41
- page.getByRole("button", { name: "Ivory Button" }),
42
- ).toMatchScreenshot("button-ivory");
57
+ page.getByRole("button", { name: "Outline Light Button" }),
58
+ ).toMatchScreenshot("button-outline-light");
59
+ });
60
+
61
+ // Ghost variants
62
+ test("ghost dark variant renders correctly", async () => {
63
+ render(
64
+ <Button variant="ghost" colorScheme="dark">
65
+ Ghost Dark Button
66
+ </Button>,
67
+ );
68
+
69
+ await expect(
70
+ page.getByRole("button", { name: "Ghost Dark Button" }),
71
+ ).toMatchScreenshot("button-ghost-dark");
43
72
  });
44
73
 
45
- test("ivoryOutline variant renders correctly", async () => {
74
+ test("ghost light variant renders correctly", async () => {
46
75
  render(
47
76
  <div style={{ background: "#1a1a1a", padding: "20px" }}>
48
- <Button variant="ivoryOutline">Ivory Outline Button</Button>
77
+ <Button variant="ghost" colorScheme="light">
78
+ Ghost Light Button
79
+ </Button>
49
80
  </div>,
50
81
  );
51
82
 
52
83
  await expect(
53
- page.getByRole("button", { name: "Ivory Outline Button" }),
54
- ).toMatchScreenshot("button-ivory-outline");
84
+ page.getByRole("button", { name: "Ghost Light Button" }),
85
+ ).toMatchScreenshot("button-ghost-light");
86
+ });
87
+
88
+ // Subtle variants
89
+ test("subtle dark variant renders correctly", async () => {
90
+ render(
91
+ <Button variant="subtle" colorScheme="dark">
92
+ Subtle Dark Button
93
+ </Button>,
94
+ );
95
+
96
+ await expect(
97
+ page.getByRole("button", { name: "Subtle Dark Button" }),
98
+ ).toMatchScreenshot("button-subtle-dark");
55
99
  });
56
100
 
57
- test("ivoryOutlineQuiet variant renders correctly", async () => {
101
+ test("subtle light variant renders correctly", async () => {
58
102
  render(
59
103
  <div style={{ background: "#1a1a1a", padding: "20px" }}>
60
- <Button variant="ivoryOutlineQuiet">Ivory Outline Quiet</Button>
104
+ <Button variant="subtle" colorScheme="light">
105
+ Subtle Light Button
106
+ </Button>
61
107
  </div>,
62
108
  );
63
109
 
64
110
  await expect(
65
- page.getByRole("button", { name: "Ivory Outline Quiet" }),
66
- ).toMatchScreenshot("button-ivory-outline-quiet");
111
+ page.getByRole("button", { name: "Subtle Light Button" }),
112
+ ).toMatchScreenshot("button-subtle-light");
67
113
  });
68
114
 
69
115
  // Size variants
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
 
3
3
  import { useRender } from "@base-ui-components/react/use-render";
4
- import * as React from "react";
5
4
  import { tv, type VariantProps } from "tailwind-variants";
6
5
 
7
6
  /**
8
- * IconButton component based on Figma BaseKit / Interface / Icon Button
7
+ * IconButton component based on Figma Button component (icon-only variant)
9
8
  *
10
9
  * **IMPORTANT: Accessibility Requirement**
11
10
  * Icon-only buttons MUST have an accessible label. Provide one of:
@@ -21,219 +20,112 @@ import { tv, type VariantProps } from "tailwind-variants";
21
20
  * </IconButton>
22
21
  * ```
23
22
  *
24
- * Variants:
25
- * - solid: Filled button
26
- * - outline: Outlined button
27
- * - ghost: No background/border, just icon
28
- * - subtle: Subtle outlined button
29
- *
30
- * Color Schemes:
31
- * - dark: Dark colors for use on light backgrounds (default)
32
- * - light: Light colors for use on dark backgrounds
23
+ * Variants (matches Figma):
24
+ * - primary: Filled brand button (indigo background)
25
+ * - primary-outline: Outlined brand button (indigo border/text)
26
+ * - secondary: Filled neutral button (white background, for dark backgrounds)
27
+ * - secondary-outline: Outlined neutral button (white border/text, for dark backgrounds)
28
+ * - ghost: Transparent button with subtle hover (for light backgrounds)
29
+ * - ghost-inverse: Transparent button with subtle hover (for dark backgrounds)
33
30
  *
34
31
  * Sizes:
35
- * - sm: Small (32x32)
36
- * - default: Medium (40x40)
37
- * - lg: Large (48x48)
32
+ * - sm: Small (28x28)
33
+ * - md: Medium (40x40) - default
34
+ * - lg: Large (56x56)
38
35
  *
39
36
  * Rounded:
40
- * - default: Standard border radius
41
- * - sm: Smaller border radius
37
+ * - default: Standard border radius (matches size)
42
38
  * - full: Fully circular
43
39
  */
44
40
  const iconButtonVariants = tv({
45
41
  base: "inline-flex items-center justify-center 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",
46
42
  variants: {
47
43
  variant: {
48
- solid: "",
49
- outline: "border",
50
- ghost: "",
51
- subtle: "border",
52
- },
53
- colorScheme: {
54
- dark: "",
55
- light: "",
44
+ // Primary - filled brand button
45
+ primary:
46
+ "bg-button-primary-bg text-button-primary-text hover:bg-button-primary-bg-hover hover:text-button-primary-text-hover border-transparent focus-visible:ring-button-primary-bg",
47
+ // Primary Outline - outlined brand button
48
+ "primary-outline":
49
+ "bg-button-primary-outline-bg text-button-primary-outline-text border border-button-primary-outline-border hover:bg-button-primary-outline-bg-hover hover:text-button-primary-outline-text-hover hover:border-button-primary-outline-border-hover focus-visible:ring-button-primary-outline-border",
50
+ // Secondary - filled neutral button (for dark backgrounds)
51
+ secondary:
52
+ "bg-button-secondary-bg text-button-secondary-text hover:bg-button-secondary-bg-hover hover:text-button-secondary-text-hover border-transparent focus-visible:ring-button-secondary-bg focus-visible:ring-offset-gray-1000",
53
+ // Secondary Outline - outlined neutral button (for dark backgrounds)
54
+ "secondary-outline":
55
+ "bg-button-secondary-outline-bg text-button-secondary-outline-text border border-button-secondary-outline-border hover:bg-button-secondary-outline-bg-hover hover:text-button-secondary-outline-text-hover hover:border-button-secondary-outline-border-hover focus-visible:ring-button-secondary-outline-border focus-visible:ring-offset-gray-1000",
56
+ // Ghost - transparent button (for light backgrounds)
57
+ ghost:
58
+ "bg-button-ghost-bg text-button-ghost-text hover:bg-button-ghost-bg-hover hover:text-button-ghost-text-hover border-transparent focus-visible:ring-gray-1000",
59
+ // Ghost Inverse - transparent button (for dark backgrounds)
60
+ "ghost-inverse":
61
+ "bg-button-ghost-inverse-bg text-button-ghost-inverse-text hover:bg-button-ghost-inverse-bg-hover hover:text-button-ghost-inverse-text-hover border-transparent focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
56
62
  },
57
63
  size: {
58
- sm: "size-32",
59
- default: "size-40",
60
- lg: "size-48",
64
+ sm: "size-28 rounded-4",
65
+ md: "size-40 rounded-6",
66
+ lg: "size-56 rounded-10",
61
67
  },
62
68
  rounded: {
63
- default: "rounded-radius-12",
64
- sm: "rounded-radius-10",
69
+ default: "",
65
70
  full: "rounded-full",
66
71
  },
67
72
  },
68
- compoundVariants: [
69
- // Solid + Dark (for light backgrounds)
70
- {
71
- variant: "solid",
72
- colorScheme: "dark",
73
- class:
74
- "bg-gray-1200 text-gray-100 hover:bg-gray-1100 active:bg-gray-1000 focus-visible:ring-gray-1000",
75
- },
76
- // Solid + Light (for dark backgrounds)
77
- {
78
- variant: "solid",
79
- colorScheme: "light",
80
- class:
81
- "bg-gray-50 text-gray-1000 hover:bg-gray-100 active:bg-gray-200 focus-visible:ring-gray-50 focus-visible:ring-offset-gray-1000",
82
- },
83
- // Outline + Dark (for light backgrounds)
84
- {
85
- variant: "outline",
86
- colorScheme: "dark",
87
- class:
88
- "border-alpha-black-30 text-gray-1000 hover:bg-alpha-black-5 active:bg-alpha-black-10 focus-visible:ring-gray-1000",
89
- },
90
- // Outline + Light (for dark backgrounds)
91
- {
92
- variant: "outline",
93
- colorScheme: "light",
94
- class:
95
- "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",
96
- },
97
- // Ghost + Dark (for light backgrounds)
98
- {
99
- variant: "ghost",
100
- colorScheme: "dark",
101
- class:
102
- "text-gray-700 hover:text-gray-900 hover:bg-alpha-black-5 active:bg-alpha-black-10 focus-visible:ring-gray-1000",
103
- },
104
- // Ghost + Light (for dark backgrounds)
105
- {
106
- variant: "ghost",
107
- colorScheme: "light",
108
- class:
109
- "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",
110
- },
111
- // Subtle + Dark (for light backgrounds)
112
- {
113
- variant: "subtle",
114
- colorScheme: "dark",
115
- class:
116
- "border-alpha-black-20 text-alpha-black-60 hover:border-alpha-black-30 hover:text-alpha-black-80 active:bg-alpha-black-5 focus-visible:ring-gray-1000",
117
- },
118
- // Subtle + Light (for dark backgrounds)
119
- {
120
- variant: "subtle",
121
- colorScheme: "light",
122
- class:
123
- "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",
124
- },
125
- ],
126
73
  defaultVariants: {
127
- variant: "solid",
128
- colorScheme: "dark",
129
- size: "default",
74
+ variant: "primary",
75
+ size: "md",
130
76
  rounded: "default",
131
77
  },
132
78
  });
133
79
 
134
- export interface IconButtonProps
135
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
136
- VariantProps<typeof iconButtonVariants> {
137
- /**
138
- * Custom render prop for element composition.
139
- * Accepts a React element or render function.
140
- * @example
141
- * ```tsx
142
- * // Render as a link
143
- * <IconButton render={<a href="/contact" />} aria-label="Contact">
144
- * <LinkIcon />
145
- * </IconButton>
146
- *
147
- * // Render with custom element
148
- * <IconButton render={(props) => <Link {...props} to="/home" />} aria-label="Home">
149
- * <HomeIcon />
150
- * </IconButton>
151
- * ```
152
- */
153
- render?:
154
- | React.ReactElement
155
- | ((
156
- props: React.ButtonHTMLAttributes<HTMLButtonElement>,
157
- ) => React.ReactElement);
158
- /**
159
- * @deprecated Use `render` prop instead for element composition.
160
- * @example
161
- * ```tsx
162
- * // Old (deprecated)
163
- * <IconButton asChild><a href="/link">...</a></IconButton>
164
- *
165
- * // New (recommended)
166
- * <IconButton render={<a href="/link" />}>...</IconButton>
167
- * ```
168
- */
169
- asChild?: boolean;
80
+ interface IconButtonState extends Record<string, unknown> {
81
+ variant:
82
+ | "primary"
83
+ | "primary-outline"
84
+ | "secondary"
85
+ | "secondary-outline"
86
+ | "ghost"
87
+ | "ghost-inverse";
88
+ size: "sm" | "md" | "lg";
89
+ rounded: "default" | "full";
170
90
  }
171
91
 
172
- const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
173
- (
174
- {
175
- className,
176
- variant,
177
- colorScheme,
178
- size,
179
- rounded,
180
- render,
181
- asChild,
182
- ...props
183
- },
184
- ref,
185
- ) => {
186
- // Development warnings
187
- React.useEffect(() => {
188
- if (import.meta.env?.DEV) {
189
- // Warn about missing accessible label
190
- const hasAccessibleLabel =
191
- props["aria-label"] || props["aria-labelledby"] || props.title;
192
- if (!hasAccessibleLabel) {
193
- console.warn(
194
- "IconButton: Missing accessible label. Icon-only buttons must have an aria-label, aria-labelledby, or title attribute for screen reader users.",
195
- );
196
- }
197
- // Warn about deprecated asChild prop
198
- if (asChild !== undefined) {
199
- console.warn(
200
- 'IconButton: The "asChild" prop is deprecated. Use the "render" prop instead for element composition.\n' +
201
- 'Example: <IconButton render={<a href="/link" />}>...</IconButton>',
202
- );
203
- }
204
- }
205
- }, [props["aria-label"], props["aria-labelledby"], props.title, asChild]);
92
+ export interface IconButtonProps
93
+ extends useRender.ComponentProps<"button", IconButtonState>,
94
+ VariantProps<typeof iconButtonVariants> {}
206
95
 
207
- // Resolve actual values for data attributes
208
- const resolvedVariant = variant ?? "solid";
209
- const resolvedColorScheme = colorScheme ?? "dark";
210
- const resolvedSize = size ?? "default";
211
- const resolvedRounded = rounded ?? "default";
96
+ function IconButton(props: IconButtonProps) {
97
+ const {
98
+ className,
99
+ variant = "primary",
100
+ size = "md",
101
+ rounded = "default",
102
+ render,
103
+ ...otherProps
104
+ } = props;
212
105
 
213
- const mergedProps = {
214
- className: iconButtonVariants({
215
- variant,
216
- colorScheme,
217
- size,
218
- rounded,
219
- class: className,
220
- }),
221
- "data-variant": resolvedVariant,
222
- "data-color-scheme": resolvedColorScheme,
223
- "data-size": resolvedSize,
224
- "data-rounded": resolvedRounded,
225
- ...props,
226
- };
106
+ const state: IconButtonState = {
107
+ variant,
108
+ size,
109
+ rounded,
110
+ };
227
111
 
228
- const element = useRender({
229
- render: render ?? <button type="button" />,
230
- ref,
231
- props: mergedProps,
232
- });
112
+ const buttonClassName = iconButtonVariants({
113
+ variant,
114
+ size,
115
+ rounded,
116
+ class: className,
117
+ });
233
118
 
234
- return element;
235
- },
236
- );
237
- IconButton.displayName = "IconButton";
119
+ return useRender<IconButtonState, HTMLButtonElement>({
120
+ render,
121
+ state,
122
+ props: {
123
+ type: "button",
124
+ className: buttonClassName,
125
+ ...otherProps,
126
+ },
127
+ defaultTagName: "button",
128
+ });
129
+ }
238
130
 
239
131
  export { IconButton, iconButtonVariants };
@@ -0,0 +1,17 @@
1
+ export { Input, type InputProps, inputVariants } from "./input";
2
+ export {
3
+ InputGroup,
4
+ InputGroupAddon,
5
+ type InputGroupAddonProps,
6
+ InputGroupButton,
7
+ type InputGroupButtonProps,
8
+ InputGroupInput,
9
+ type InputGroupInputProps,
10
+ type InputGroupProps,
11
+ InputGroupText,
12
+ InputGroupTextarea,
13
+ type InputGroupTextareaProps,
14
+ type InputGroupTextProps,
15
+ inputGroupAddonVariants,
16
+ inputGroupVariants,
17
+ } from "./input-group";