@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
@@ -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-headline-large mb-spacing-16">
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-headline-small mb-spacing-8">
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.