@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.
- package/dist/components/atoms/background/background.d.ts +13 -27
- package/dist/components/atoms/button/button.d.ts +55 -71
- package/dist/components/atoms/button/icon-button.d.ts +62 -110
- package/dist/components/atoms/input/input-group.d.ts +278 -0
- package/dist/components/atoms/input/input.d.ts +121 -0
- package/dist/components/atoms/select/select.d.ts +131 -0
- package/dist/components/organisms/card/card.d.ts +2 -2
- package/dist/components/sections/prose/prose.d.ts +3 -3
- package/dist/components/sections/river/river.d.ts +1 -1
- package/dist/components/sections/tout/tout.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11034 -7824
- package/dist/index.js.map +1 -1
- package/dist/lib/form-control.d.ts +105 -0
- package/dist/tokens.css +2132 -17329
- package/package.json +1 -1
- package/src/components/atoms/background/background.tsx +71 -109
- package/src/components/atoms/button/button.stories.tsx +42 -0
- package/src/components/atoms/button/button.test.tsx +1 -1
- package/src/components/atoms/button/button.tsx +38 -103
- package/src/components/atoms/button/button.visual.test.tsx +70 -24
- package/src/components/atoms/button/icon-button.tsx +81 -224
- package/src/components/atoms/input/index.ts +17 -0
- package/src/components/atoms/input/input-group.stories.tsx +650 -0
- package/src/components/atoms/input/input-group.test.tsx +376 -0
- package/src/components/atoms/input/input-group.tsx +384 -0
- package/src/components/atoms/input/input.stories.tsx +232 -0
- package/src/components/atoms/input/input.test.tsx +183 -0
- package/src/components/atoms/input/input.tsx +97 -0
- package/src/components/atoms/select/index.ts +18 -0
- package/src/components/atoms/select/select.stories.tsx +455 -0
- package/src/components/atoms/select/select.tsx +320 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +2 -6
- package/src/components/foundation/typography/typography.stories.tsx +401 -0
- package/src/components/organisms/card/card.stories.tsx +11 -11
- package/src/components/organisms/card/card.test.tsx +1 -1
- package/src/components/organisms/card/card.tsx +2 -2
- package/src/components/organisms/card/card.visual.test.tsx +6 -6
- package/src/components/organisms/navbar/navbar.tsx +2 -2
- package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
- package/src/components/sections/card-grid/card-grid.tsx +1 -1
- package/src/components/sections/faq-section/faq-section.tsx +2 -2
- package/src/components/sections/hero/hero.test.tsx +5 -5
- package/src/components/sections/prose/prose.test.tsx +2 -2
- package/src/components/sections/prose/prose.tsx +4 -5
- package/src/components/sections/river/river.stories.tsx +8 -8
- package/src/components/sections/river/river.test.tsx +1 -1
- package/src/components/sections/river/river.tsx +2 -4
- package/src/components/sections/tout/tout.test.tsx +1 -1
- package/src/components/sections/tout/tout.tsx +2 -2
- package/src/index.ts +41 -0
- package/src/lib/form-control.ts +69 -0
- package/src/stories/Introduction.mdx +29 -15
- package/src/stories/ThemeProvider.stories.tsx +1 -3
- package/src/stories/TokenShowcase.stories.tsx +0 -19
- package/src/stories/TokenShowcase.tsx +714 -1366
- package/src/styles.css +3 -0
- package/src/tests/token-resolution.test.tsx +301 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import * as React from "react";
|
|
3
|
+
import { useRender } from "@base-ui-components/react/use-render";
|
|
5
4
|
import { tv, type VariantProps } from "tailwind-variants";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
* IconButton component based on Figma
|
|
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,254 +20,112 @@ import { tv, type VariantProps } from "tailwind-variants";
|
|
|
21
20
|
* </IconButton>
|
|
22
21
|
* ```
|
|
23
22
|
*
|
|
24
|
-
* Variants:
|
|
25
|
-
* -
|
|
26
|
-
* - outline: Outlined button
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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 (
|
|
36
|
-
* -
|
|
37
|
-
* - lg: Large (
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
dark
|
|
55
|
-
|
|
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-
|
|
59
|
-
|
|
60
|
-
lg: "size-
|
|
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: "
|
|
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: "
|
|
128
|
-
|
|
129
|
-
size: "default",
|
|
74
|
+
variant: "primary",
|
|
75
|
+
size: "md",
|
|
130
76
|
rounded: "default",
|
|
131
77
|
},
|
|
132
78
|
});
|
|
133
79
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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]);
|
|
206
|
-
|
|
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";
|
|
212
|
-
|
|
213
|
-
const buttonClassName = iconButtonVariants({
|
|
214
|
-
variant,
|
|
215
|
-
colorScheme,
|
|
216
|
-
size,
|
|
217
|
-
rounded,
|
|
218
|
-
class: className,
|
|
219
|
-
});
|
|
92
|
+
export interface IconButtonProps
|
|
93
|
+
extends useRender.ComponentProps<"button", IconButtonState>,
|
|
94
|
+
VariantProps<typeof iconButtonVariants> {}
|
|
220
95
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
96
|
+
function IconButton(props: IconButtonProps) {
|
|
97
|
+
const {
|
|
98
|
+
className,
|
|
99
|
+
variant = "primary",
|
|
100
|
+
size = "md",
|
|
101
|
+
rounded = "default",
|
|
102
|
+
render,
|
|
103
|
+
...otherProps
|
|
104
|
+
} = props;
|
|
227
105
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
className: buttonClassName,
|
|
234
|
-
...dataAttributes,
|
|
235
|
-
...props,
|
|
236
|
-
} as React.ButtonHTMLAttributes<HTMLButtonElement>);
|
|
237
|
-
}
|
|
238
|
-
// Clone the render element with merged props
|
|
239
|
-
return React.cloneElement(render, {
|
|
240
|
-
ref,
|
|
241
|
-
className: buttonClassName,
|
|
242
|
-
...dataAttributes,
|
|
243
|
-
...props,
|
|
244
|
-
...(render.props as Record<string, unknown>),
|
|
245
|
-
});
|
|
246
|
-
}
|
|
106
|
+
const state: IconButtonState = {
|
|
107
|
+
variant,
|
|
108
|
+
size,
|
|
109
|
+
rounded,
|
|
110
|
+
};
|
|
247
111
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
{...dataAttributes}
|
|
255
|
-
{...props}
|
|
256
|
-
/>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
112
|
+
const buttonClassName = iconButtonVariants({
|
|
113
|
+
variant,
|
|
114
|
+
size,
|
|
115
|
+
rounded,
|
|
116
|
+
class: className,
|
|
117
|
+
});
|
|
259
118
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
272
|
-
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
|
+
}
|
|
273
130
|
|
|
274
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";
|