@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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Select as BaseSelect } from "@base-ui-components/react/select";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
6
|
+
import {
|
|
7
|
+
formControlBase,
|
|
8
|
+
formControlError,
|
|
9
|
+
formControlSizes,
|
|
10
|
+
} from "@/lib/form-control";
|
|
11
|
+
import { cn } from "@/lib/utils";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Select trigger variants based on Figma BaseKit / Interface / Dropdown
|
|
15
|
+
*
|
|
16
|
+
* States:
|
|
17
|
+
* - Default: White background, subtle border
|
|
18
|
+
* - Hover: Light gray background
|
|
19
|
+
* - Focus/Open: Accent border with focus ring
|
|
20
|
+
* - Selected: Has a value selected (darker text)
|
|
21
|
+
* - Disabled: Reduced opacity, not interactive
|
|
22
|
+
*/
|
|
23
|
+
const selectTriggerVariants = tv({
|
|
24
|
+
base: [
|
|
25
|
+
...formControlBase,
|
|
26
|
+
// Select-specific styles
|
|
27
|
+
"justify-between cursor-pointer",
|
|
28
|
+
// Override disabled to use data attribute (Base UI pattern)
|
|
29
|
+
"data-[disabled]:bg-ui-control-background-disabled data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
30
|
+
// Open state styling
|
|
31
|
+
"data-[popup-open]:border-ui-accent-base data-[popup-open]:ring-4 data-[popup-open]:ring-ui-color-focus data-[popup-open]:bg-ui-control-background",
|
|
32
|
+
],
|
|
33
|
+
variants: {
|
|
34
|
+
size: formControlSizes,
|
|
35
|
+
error: formControlError,
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
size: "default",
|
|
39
|
+
error: false,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Select popup/menu variants
|
|
45
|
+
*/
|
|
46
|
+
const selectPopupVariants = tv({
|
|
47
|
+
base: [
|
|
48
|
+
// Layout - match trigger width using CSS custom property from Base UI
|
|
49
|
+
"flex flex-col gap-2 p-10",
|
|
50
|
+
"w-[var(--anchor-width)]",
|
|
51
|
+
// Background and border
|
|
52
|
+
"bg-ui-control-background border border-solid border-ui-color-border-active rounded-radius-6",
|
|
53
|
+
// Focus ring shadow (ui-focus-state from Figma)
|
|
54
|
+
"ring-4 ring-ui-color-focus",
|
|
55
|
+
// Animation
|
|
56
|
+
"origin-[var(--transform-origin)]",
|
|
57
|
+
"transition-[transform,scale,opacity] duration-150",
|
|
58
|
+
"data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
|
|
59
|
+
"data-[ending-style]:scale-95 data-[ending-style]:opacity-0",
|
|
60
|
+
// Ensure it's above other content
|
|
61
|
+
"z-50",
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Select option/item variants based on Figma Menu Items
|
|
67
|
+
*
|
|
68
|
+
* States:
|
|
69
|
+
* - Default: White background
|
|
70
|
+
* - Hover/Highlighted: Light indigo tint background
|
|
71
|
+
* - Selected: Stronger indigo tint with blue text and checkmark
|
|
72
|
+
* - Disabled: Reduced opacity
|
|
73
|
+
*/
|
|
74
|
+
const selectOptionVariants = tv({
|
|
75
|
+
base: [
|
|
76
|
+
// Layout
|
|
77
|
+
"flex items-center justify-between px-12 py-8 h-36",
|
|
78
|
+
// Typography - use semantic tokens
|
|
79
|
+
"text-16 font-medium leading-14 text-ui-menu-item-text",
|
|
80
|
+
// Background - default
|
|
81
|
+
"bg-ui-menu-item-bg",
|
|
82
|
+
// Border radius
|
|
83
|
+
"rounded-radius-6",
|
|
84
|
+
// Cursor
|
|
85
|
+
"cursor-pointer outline-none",
|
|
86
|
+
// Transitions
|
|
87
|
+
"transition-colors duration-150",
|
|
88
|
+
// Hover/highlighted state - use semantic token
|
|
89
|
+
"data-[highlighted]:bg-ui-menu-item-bg-hover",
|
|
90
|
+
// Selected state - use semantic tokens
|
|
91
|
+
"data-[selected]:bg-ui-menu-item-bg-selected data-[selected]:text-ui-menu-item-text-selected",
|
|
92
|
+
// Disabled state
|
|
93
|
+
"data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed",
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Chevron icon for the select trigger
|
|
99
|
+
*/
|
|
100
|
+
const SelectChevronIcon = ({ className }: { className?: string }) => (
|
|
101
|
+
<svg
|
|
102
|
+
className={cn("size-16 text-gray-500 shrink-0", className)}
|
|
103
|
+
viewBox="0 0 16 16"
|
|
104
|
+
fill="none"
|
|
105
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
106
|
+
aria-hidden="true"
|
|
107
|
+
>
|
|
108
|
+
<path
|
|
109
|
+
d="M4 6L8 10L12 6"
|
|
110
|
+
stroke="currentColor"
|
|
111
|
+
strokeWidth="1.5"
|
|
112
|
+
strokeLinecap="round"
|
|
113
|
+
strokeLinejoin="round"
|
|
114
|
+
/>
|
|
115
|
+
</svg>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checkmark icon for selected options
|
|
120
|
+
*/
|
|
121
|
+
const CheckIcon = ({ className }: { className?: string }) => (
|
|
122
|
+
<svg
|
|
123
|
+
className={cn("size-14 shrink-0", className)}
|
|
124
|
+
viewBox="0 0 14 14"
|
|
125
|
+
fill="none"
|
|
126
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
>
|
|
129
|
+
<path
|
|
130
|
+
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
|
|
131
|
+
stroke="currentColor"
|
|
132
|
+
strokeWidth="1.5"
|
|
133
|
+
strokeLinecap="round"
|
|
134
|
+
strokeLinejoin="round"
|
|
135
|
+
/>
|
|
136
|
+
</svg>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Select Root
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
export interface SelectProps extends BaseSelect.Root.Props {
|
|
144
|
+
children: React.ReactNode;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const SelectRoot = ({ children, ...props }: SelectProps) => {
|
|
148
|
+
return <BaseSelect.Root {...props}>{children}</BaseSelect.Root>;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Select Trigger
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
export interface SelectTriggerProps
|
|
156
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Trigger>, "className">,
|
|
157
|
+
VariantProps<typeof selectTriggerVariants> {
|
|
158
|
+
className?: string;
|
|
159
|
+
placeholder?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
163
|
+
(
|
|
164
|
+
{ className, size, error, placeholder = "Select option...", ...props },
|
|
165
|
+
ref,
|
|
166
|
+
) => {
|
|
167
|
+
return (
|
|
168
|
+
<BaseSelect.Trigger
|
|
169
|
+
ref={ref}
|
|
170
|
+
className={cn(selectTriggerVariants({ size, error }), className)}
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
173
|
+
<BaseSelect.Value>
|
|
174
|
+
{(value) =>
|
|
175
|
+
value ? (
|
|
176
|
+
value
|
|
177
|
+
) : (
|
|
178
|
+
<span className="text-text-muted">{placeholder}</span>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
</BaseSelect.Value>
|
|
182
|
+
<BaseSelect.Icon>
|
|
183
|
+
<SelectChevronIcon />
|
|
184
|
+
</BaseSelect.Icon>
|
|
185
|
+
</BaseSelect.Trigger>
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
SelectTrigger.displayName = "SelectTrigger";
|
|
190
|
+
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// Select Portal & Popup
|
|
193
|
+
// ============================================================================
|
|
194
|
+
|
|
195
|
+
export interface SelectPopupProps
|
|
196
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Popup>, "className"> {
|
|
197
|
+
className?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const SelectPopup = React.forwardRef<HTMLDivElement, SelectPopupProps>(
|
|
201
|
+
({ className, children, ...props }, ref) => {
|
|
202
|
+
return (
|
|
203
|
+
<BaseSelect.Portal>
|
|
204
|
+
<BaseSelect.Positioner side="bottom" sideOffset={4} align="start">
|
|
205
|
+
<BaseSelect.Popup
|
|
206
|
+
ref={ref}
|
|
207
|
+
className={cn(selectPopupVariants(), className)}
|
|
208
|
+
{...props}
|
|
209
|
+
>
|
|
210
|
+
{children}
|
|
211
|
+
</BaseSelect.Popup>
|
|
212
|
+
</BaseSelect.Positioner>
|
|
213
|
+
</BaseSelect.Portal>
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
SelectPopup.displayName = "SelectPopup";
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Select Option (wraps Base UI's Select.Item)
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
export interface SelectOptionProps
|
|
224
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Item>, "className"> {
|
|
225
|
+
className?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const SelectOption = React.forwardRef<HTMLDivElement, SelectOptionProps>(
|
|
229
|
+
({ className, children, ...props }, ref) => {
|
|
230
|
+
return (
|
|
231
|
+
<BaseSelect.Item
|
|
232
|
+
ref={ref}
|
|
233
|
+
className={cn(selectOptionVariants(), className)}
|
|
234
|
+
{...props}
|
|
235
|
+
>
|
|
236
|
+
<BaseSelect.ItemText>{children}</BaseSelect.ItemText>
|
|
237
|
+
<BaseSelect.ItemIndicator>
|
|
238
|
+
<CheckIcon />
|
|
239
|
+
</BaseSelect.ItemIndicator>
|
|
240
|
+
</BaseSelect.Item>
|
|
241
|
+
);
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
SelectOption.displayName = "SelectOption";
|
|
245
|
+
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// Select Group
|
|
248
|
+
// ============================================================================
|
|
249
|
+
|
|
250
|
+
export interface SelectGroupProps
|
|
251
|
+
extends Omit<React.ComponentProps<typeof BaseSelect.Group>, "className"> {
|
|
252
|
+
className?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const SelectGroup = React.forwardRef<HTMLDivElement, SelectGroupProps>(
|
|
256
|
+
({ className, children, ...props }, ref) => {
|
|
257
|
+
return (
|
|
258
|
+
<BaseSelect.Group ref={ref} className={className} {...props}>
|
|
259
|
+
{children}
|
|
260
|
+
</BaseSelect.Group>
|
|
261
|
+
);
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
SelectGroup.displayName = "SelectGroup";
|
|
265
|
+
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// Select Group Label
|
|
268
|
+
// ============================================================================
|
|
269
|
+
|
|
270
|
+
export interface SelectGroupLabelProps
|
|
271
|
+
extends Omit<
|
|
272
|
+
React.ComponentProps<typeof BaseSelect.GroupLabel>,
|
|
273
|
+
"className"
|
|
274
|
+
> {
|
|
275
|
+
className?: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const SelectGroupLabel = React.forwardRef<
|
|
279
|
+
HTMLDivElement,
|
|
280
|
+
SelectGroupLabelProps
|
|
281
|
+
>(({ className, children, ...props }, ref) => {
|
|
282
|
+
return (
|
|
283
|
+
<BaseSelect.GroupLabel
|
|
284
|
+
ref={ref}
|
|
285
|
+
className={cn(
|
|
286
|
+
"px-12 py-6 text-12 font-medium text-text-muted uppercase tracking-wide",
|
|
287
|
+
className,
|
|
288
|
+
)}
|
|
289
|
+
{...props}
|
|
290
|
+
>
|
|
291
|
+
{children}
|
|
292
|
+
</BaseSelect.GroupLabel>
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
SelectGroupLabel.displayName = "SelectGroupLabel";
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Compound Component Export
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
export const Select = Object.assign(SelectRoot, {
|
|
302
|
+
Trigger: SelectTrigger,
|
|
303
|
+
Popup: SelectPopup,
|
|
304
|
+
Option: SelectOption,
|
|
305
|
+
Group: SelectGroup,
|
|
306
|
+
GroupLabel: SelectGroupLabel,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Also export individual components for flexibility
|
|
310
|
+
export {
|
|
311
|
+
SelectRoot,
|
|
312
|
+
SelectTrigger,
|
|
313
|
+
SelectPopup,
|
|
314
|
+
SelectOption,
|
|
315
|
+
SelectGroup,
|
|
316
|
+
SelectGroupLabel,
|
|
317
|
+
selectTriggerVariants,
|
|
318
|
+
selectPopupVariants,
|
|
319
|
+
selectOptionVariants,
|
|
320
|
+
};
|
|
@@ -20,9 +20,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
20
20
|
const DemoContent = () => (
|
|
21
21
|
<div className="min-h-screen bg-gray-100 py-spacing-64">
|
|
22
22
|
<div className="w-full max-w-[90rem] mx-auto px-[var(--spatial-grid-small-margin)] md:px-[var(--spatial-grid-medium-margin)] lg:px-[var(--spatial-grid-large-margin)]">
|
|
23
|
-
<h1 className="typography-
|
|
24
|
-
Dev Toolbar Demo
|
|
25
|
-
</h1>
|
|
23
|
+
<h1 className="typography-h2 mb-spacing-16">Dev Toolbar Demo</h1>
|
|
26
24
|
<p className="typography-body-medium text-gray-600 mb-spacing-8">
|
|
27
25
|
Click the bar at the bottom to expand, then toggle the Grid overlay.
|
|
28
26
|
</p>
|
|
@@ -43,9 +41,7 @@ const DemoContent = () => (
|
|
|
43
41
|
key={id}
|
|
44
42
|
className="col-span-4 md:col-span-4 lg:col-span-8 bg-white p-spacing-16 rounded-radius-12 shadow"
|
|
45
43
|
>
|
|
46
|
-
<h3 className="typography-
|
|
47
|
-
Card {id}
|
|
48
|
-
</h3>
|
|
44
|
+
<h3 className="typography-h4 mb-spacing-8">Card {id}</h3>
|
|
49
45
|
<p className="typography-body-small text-gray-500">
|
|
50
46
|
Sample content to visualize how the grid overlay aligns with your
|
|
51
47
|
layout.
|