@nationaldesignstudio/react 0.2.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 (58) hide show
  1. package/dist/components/atoms/background/background.d.ts +13 -27
  2. package/dist/components/atoms/button/button.d.ts +55 -71
  3. package/dist/components/atoms/button/icon-button.d.ts +62 -110
  4. package/dist/components/atoms/input/input-group.d.ts +278 -0
  5. package/dist/components/atoms/input/input.d.ts +121 -0
  6. package/dist/components/atoms/select/select.d.ts +131 -0
  7. package/dist/components/organisms/card/card.d.ts +2 -2
  8. package/dist/components/sections/prose/prose.d.ts +3 -3
  9. package/dist/components/sections/river/river.d.ts +1 -1
  10. package/dist/components/sections/tout/tout.d.ts +1 -1
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +11034 -7824
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/form-control.d.ts +105 -0
  15. package/dist/tokens.css +2132 -17329
  16. package/package.json +1 -1
  17. package/src/components/atoms/background/background.tsx +71 -109
  18. package/src/components/atoms/button/button.stories.tsx +42 -0
  19. package/src/components/atoms/button/button.test.tsx +1 -1
  20. package/src/components/atoms/button/button.tsx +38 -103
  21. package/src/components/atoms/button/button.visual.test.tsx +70 -24
  22. package/src/components/atoms/button/icon-button.tsx +81 -224
  23. package/src/components/atoms/input/index.ts +17 -0
  24. package/src/components/atoms/input/input-group.stories.tsx +650 -0
  25. package/src/components/atoms/input/input-group.test.tsx +376 -0
  26. package/src/components/atoms/input/input-group.tsx +384 -0
  27. package/src/components/atoms/input/input.stories.tsx +232 -0
  28. package/src/components/atoms/input/input.test.tsx +183 -0
  29. package/src/components/atoms/input/input.tsx +97 -0
  30. package/src/components/atoms/select/index.ts +18 -0
  31. package/src/components/atoms/select/select.stories.tsx +455 -0
  32. package/src/components/atoms/select/select.tsx +320 -0
  33. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +2 -6
  34. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  35. package/src/components/organisms/card/card.stories.tsx +11 -11
  36. package/src/components/organisms/card/card.test.tsx +1 -1
  37. package/src/components/organisms/card/card.tsx +2 -2
  38. package/src/components/organisms/card/card.visual.test.tsx +6 -6
  39. package/src/components/organisms/navbar/navbar.tsx +2 -2
  40. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  41. package/src/components/sections/card-grid/card-grid.tsx +1 -1
  42. package/src/components/sections/faq-section/faq-section.tsx +2 -2
  43. package/src/components/sections/hero/hero.test.tsx +5 -5
  44. package/src/components/sections/prose/prose.test.tsx +2 -2
  45. package/src/components/sections/prose/prose.tsx +4 -5
  46. package/src/components/sections/river/river.stories.tsx +8 -8
  47. package/src/components/sections/river/river.test.tsx +1 -1
  48. package/src/components/sections/river/river.tsx +2 -4
  49. package/src/components/sections/tout/tout.test.tsx +1 -1
  50. package/src/components/sections/tout/tout.tsx +2 -2
  51. package/src/index.ts +41 -0
  52. package/src/lib/form-control.ts +69 -0
  53. package/src/stories/Introduction.mdx +29 -15
  54. package/src/stories/ThemeProvider.stories.tsx +1 -3
  55. package/src/stories/TokenShowcase.stories.tsx +0 -19
  56. package/src/stories/TokenShowcase.tsx +714 -1366
  57. package/src/styles.css +3 -0
  58. package/src/tests/token-resolution.test.tsx +301 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nationaldesignstudio/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useRender } from "@base-ui-components/react/use-render";
3
4
  import * as React from "react";
4
5
  import { tv } from "tailwind-variants";
5
6
 
@@ -49,7 +50,8 @@ const backgroundImageVariants = tv({
49
50
  });
50
51
 
51
52
  export interface BackgroundImageProps
52
- extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src"> {
53
+ extends useRender.ComponentProps<"img">,
54
+ Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "render"> {
53
55
  /**
54
56
  * URL for the background image
55
57
  */
@@ -58,23 +60,6 @@ export interface BackgroundImageProps
58
60
  * Object position (default: "center")
59
61
  */
60
62
  position?: string;
61
- /**
62
- * Custom render prop for element composition.
63
- * Accepts a React element or render function.
64
- * @example
65
- * ```tsx
66
- * // Element pattern
67
- * <BackgroundImage render={<img className="custom" />} src="/bg.jpg" />
68
- *
69
- * // Callback pattern
70
- * <BackgroundImage render={(props) => <img {...props} />} src="/bg.jpg" />
71
- * ```
72
- */
73
- render?:
74
- | React.ReactElement
75
- | ((
76
- props: React.ImgHTMLAttributes<HTMLImageElement>,
77
- ) => React.ReactElement);
78
63
  }
79
64
 
80
65
  /**
@@ -82,45 +67,32 @@ export interface BackgroundImageProps
82
67
  * Supports native lazy loading, srcset, and better accessibility.
83
68
  * Supports render prop for element composition.
84
69
  */
85
- const BackgroundImage = React.forwardRef<
86
- HTMLImageElement,
87
- BackgroundImageProps
88
- >(
89
- (
90
- { className, src, position = "center", alt = "", style, render, ...props },
91
- ref,
92
- ) => {
93
- const imgClassName = backgroundImageVariants({ class: className });
94
- const imgStyle = { objectPosition: position, ...style };
95
- const imgProps = {
70
+ function BackgroundImage(props: BackgroundImageProps) {
71
+ const {
72
+ className,
73
+ src,
74
+ position = "center",
75
+ alt = "",
76
+ style,
77
+ render,
78
+ ...otherProps
79
+ } = props;
80
+
81
+ const imgClassName = backgroundImageVariants({ class: className });
82
+ const imgStyle = { objectPosition: position, ...style };
83
+
84
+ return useRender({
85
+ render,
86
+ props: {
96
87
  src,
97
88
  alt,
98
89
  className: imgClassName,
99
90
  style: imgStyle,
100
- ...props,
101
- };
102
-
103
- // Handle render prop (element or function)
104
- if (render) {
105
- if (typeof render === "function") {
106
- return render({
107
- ref,
108
- ...imgProps,
109
- } as React.ImgHTMLAttributes<HTMLImageElement>);
110
- }
111
- // Clone the render element with merged props
112
- return React.cloneElement(render, {
113
- ref,
114
- ...imgProps,
115
- ...(render.props as Record<string, unknown>),
116
- });
117
- }
118
-
119
- // Default: render as img
120
- // biome-ignore lint/a11y/useAltText: alt is provided via imgProps spread
121
- return <img ref={ref} {...imgProps} />;
122
- },
123
- );
91
+ ...otherProps,
92
+ },
93
+ defaultTagName: "img",
94
+ });
95
+ }
124
96
  BackgroundImage.displayName = "Background.Image";
125
97
 
126
98
  // =============================================================================
@@ -132,7 +104,11 @@ const backgroundVideoVariants = tv({
132
104
  });
133
105
 
134
106
  export interface BackgroundVideoProps
135
- extends Omit<React.VideoHTMLAttributes<HTMLVideoElement>, "children"> {
107
+ extends useRender.ComponentProps<"video">,
108
+ Omit<
109
+ React.VideoHTMLAttributes<HTMLVideoElement>,
110
+ "children" | "render" | "src"
111
+ > {
136
112
  /**
137
113
  * URL for the video source
138
114
  */
@@ -145,77 +121,63 @@ export interface BackgroundVideoProps
145
121
  * Poster image URL shown before video loads
146
122
  */
147
123
  poster?: string;
148
- /**
149
- * Custom render prop for element composition.
150
- * @example
151
- * ```tsx
152
- * <BackgroundVideo render={<video className="custom" />} src="/bg.mp4" />
153
- * ```
154
- */
155
- render?:
156
- | React.ReactElement
157
- | ((
158
- props: React.VideoHTMLAttributes<HTMLVideoElement>,
159
- ) => React.ReactElement);
160
124
  }
161
125
 
162
126
  /**
163
127
  * Background video layer using HTML5 video element.
164
128
  * Supports render prop for element composition.
165
129
  */
166
- const BackgroundVideo = React.forwardRef<
167
- HTMLVideoElement,
168
- BackgroundVideoProps
169
- >(
170
- (
171
- {
172
- className,
173
- src,
174
- type,
175
- poster,
176
- autoPlay = true,
177
- loop = true,
178
- muted = true,
179
- playsInline = true,
180
- render,
181
- ...props
182
- },
183
- ref,
184
- ) => {
185
- const videoClassName = backgroundVideoVariants({ class: className });
186
- const videoProps = {
130
+ function BackgroundVideo(props: BackgroundVideoProps) {
131
+ const {
132
+ className,
133
+ src,
134
+ type,
135
+ poster,
136
+ autoPlay = true,
137
+ loop = true,
138
+ muted = true,
139
+ playsInline = true,
140
+ render,
141
+ children,
142
+ ...otherProps
143
+ } = props;
144
+
145
+ const videoClassName = backgroundVideoVariants({ class: className });
146
+
147
+ // useRender must be called unconditionally
148
+ const rendered = useRender({
149
+ render,
150
+ props: {
187
151
  autoPlay,
188
152
  loop,
189
153
  muted,
190
154
  playsInline,
191
155
  poster,
192
156
  className: videoClassName,
193
- ...props,
194
- };
195
-
196
- // Handle render prop (element or function)
197
- if (render) {
198
- if (typeof render === "function") {
199
- return render({
200
- ref,
201
- ...videoProps,
202
- } as React.VideoHTMLAttributes<HTMLVideoElement>);
203
- }
204
- // Clone the render element with merged props
205
- return React.cloneElement(render, {
206
- ...videoProps,
207
- ...(render.props as Record<string, unknown>),
208
- });
209
- }
210
-
211
- // Default: render as video with source
157
+ ...otherProps,
158
+ },
159
+ defaultTagName: "video",
160
+ });
161
+
162
+ // If no render prop, return video with source child
163
+ if (!render) {
212
164
  return (
213
- <video ref={ref} {...videoProps}>
165
+ <video
166
+ autoPlay={autoPlay}
167
+ loop={loop}
168
+ muted={muted}
169
+ playsInline={playsInline}
170
+ poster={poster}
171
+ className={videoClassName}
172
+ {...otherProps}
173
+ >
214
174
  <source src={src} type={type} />
215
175
  </video>
216
176
  );
217
- },
218
- );
177
+ }
178
+
179
+ return rendered;
180
+ }
219
181
  BackgroundVideo.displayName = "Background.Video";
220
182
 
221
183
  // =============================================================================
@@ -45,24 +45,51 @@ Playground.args = {
45
45
 
46
46
  // =============================================================================
47
47
  // Variants (Dark Color Scheme - for light backgrounds)
48
+ // Figma naming: Charcoal = solid+dark, Charcoal Outline = outline+dark
48
49
  // =============================================================================
49
50
 
51
+ /**
52
+ * Solid Dark (Figma: "Charcoal" / "Black")
53
+ * Primary filled button with ui-button-primary-bg background and inverted text.
54
+ * Use on light backgrounds for primary actions.
55
+ */
50
56
  export const Solid = () => <Button variant="solid">Solid</Button>;
51
57
 
58
+ /**
59
+ * Outline Dark (Figma: "Charcoal Outline")
60
+ * Primary-colored border and text on transparent background.
61
+ * Use on light backgrounds for secondary actions.
62
+ */
52
63
  export const Outline = () => <Button variant="outline">Outline</Button>;
53
64
 
65
+ /**
66
+ * Ghost Dark
67
+ * Primary-colored text with no border or background.
68
+ * Use for tertiary actions on light backgrounds.
69
+ */
54
70
  export const Ghost = () => <Button variant="ghost">Ghost</Button>;
55
71
 
72
+ /**
73
+ * Subtle Dark
74
+ * Subtle border with primary-colored text.
75
+ * Use for less prominent actions on light backgrounds.
76
+ */
56
77
  export const Subtle = () => <Button variant="subtle">Subtle</Button>;
57
78
 
58
79
  // =============================================================================
59
80
  // Variants (Light Color Scheme - for dark backgrounds)
81
+ // Figma naming: Ivory = solid+light, Ivory Outline = outline+light
60
82
  // =============================================================================
61
83
 
62
84
  const DarkBackground = ({ children }: { children: React.ReactNode }) => (
63
85
  <div className="rounded-radius-12 bg-gray-1200 p-spacing-32">{children}</div>
64
86
  );
65
87
 
88
+ /**
89
+ * Solid Light (Figma: "Ivory")
90
+ * White filled button with primary-colored text.
91
+ * Use on dark backgrounds for primary actions.
92
+ */
66
93
  export const SolidLight = () => (
67
94
  <DarkBackground>
68
95
  <Button variant="solid" colorScheme="light">
@@ -71,6 +98,11 @@ export const SolidLight = () => (
71
98
  </DarkBackground>
72
99
  );
73
100
 
101
+ /**
102
+ * Outline Light (Figma: "Ivory Outline")
103
+ * White border and text on transparent background.
104
+ * Use on dark backgrounds for secondary actions.
105
+ */
74
106
  export const OutlineLight = () => (
75
107
  <DarkBackground>
76
108
  <Button variant="outline" colorScheme="light">
@@ -79,6 +111,11 @@ export const OutlineLight = () => (
79
111
  </DarkBackground>
80
112
  );
81
113
 
114
+ /**
115
+ * Ghost Light
116
+ * White text with no border or background.
117
+ * Use for tertiary actions on dark backgrounds.
118
+ */
82
119
  export const GhostLight = () => (
83
120
  <DarkBackground>
84
121
  <Button variant="ghost" colorScheme="light">
@@ -87,6 +124,11 @@ export const GhostLight = () => (
87
124
  </DarkBackground>
88
125
  );
89
126
 
127
+ /**
128
+ * Subtle Light
129
+ * Alpha-white border with white text.
130
+ * Use for less prominent actions on dark backgrounds.
131
+ */
90
132
  export const SubtleLight = () => (
91
133
  <DarkBackground>
92
134
  <Button variant="subtle" colorScheme="light">
@@ -129,7 +129,7 @@ describe("Button", () => {
129
129
  render(<Button>Default</Button>);
130
130
  const button = page.getByRole("button", { name: "Default" });
131
131
  // Button uses semantic token classes
132
- await expect.element(button).toHaveClass(/bg-button-primary-bg/);
132
+ await expect.element(button).toHaveClass(/bg-ui-button-primary-bg/);
133
133
  });
134
134
  });
135
135
  });
@@ -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-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
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