@nationaldesignstudio/react 0.2.0 → 0.5.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 (97) hide show
  1. package/dist/component-registry.md +1310 -127
  2. package/dist/components/atoms/background/background.d.ts +13 -27
  3. package/dist/components/atoms/button/button.d.ts +64 -72
  4. package/dist/components/atoms/button/button.figma.d.ts +1 -0
  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/popover/popover.d.ts +195 -0
  9. package/dist/components/atoms/select/select.d.ts +131 -0
  10. package/dist/components/atoms/tooltip/tooltip.d.ts +161 -0
  11. package/dist/components/organisms/card/card.d.ts +3 -3
  12. package/dist/components/sections/hero/hero.d.ts +2 -2
  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 +4 -4
  16. package/dist/components/shared/floating-arrow.d.ts +34 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +13935 -7622
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/form-control.d.ts +106 -0
  21. package/dist/tokens.css +4725 -19065
  22. package/package.json +2 -1
  23. package/src/components/atoms/accordion/accordion.stories.tsx +1 -1
  24. package/src/components/atoms/accordion/accordion.tsx +2 -2
  25. package/src/components/atoms/background/background.tsx +71 -109
  26. package/src/components/atoms/button/button.figma.tsx +37 -0
  27. package/src/components/atoms/button/button.stories.tsx +253 -115
  28. package/src/components/atoms/button/button.test.tsx +289 -5
  29. package/src/components/atoms/button/button.tsx +40 -101
  30. package/src/components/atoms/button/button.visual.test.tsx +28 -32
  31. package/src/components/atoms/button/icon-button.stories.tsx +44 -101
  32. package/src/components/atoms/button/icon-button.test.tsx +26 -94
  33. package/src/components/atoms/button/icon-button.tsx +81 -224
  34. package/src/components/atoms/input/index.ts +17 -0
  35. package/src/components/atoms/input/input-group.stories.tsx +646 -0
  36. package/src/components/atoms/input/input-group.test.tsx +362 -0
  37. package/src/components/atoms/input/input-group.tsx +409 -0
  38. package/src/components/atoms/input/input.stories.tsx +228 -0
  39. package/src/components/atoms/input/input.test.tsx +167 -0
  40. package/src/components/atoms/input/input.tsx +104 -0
  41. package/src/components/atoms/pager-control/pager-control.stories.tsx +6 -8
  42. package/src/components/atoms/pager-control/pager-control.tsx +12 -12
  43. package/src/components/atoms/popover/index.ts +30 -0
  44. package/src/components/atoms/popover/popover.stories.tsx +531 -0
  45. package/src/components/atoms/popover/popover.test.tsx +486 -0
  46. package/src/components/atoms/popover/popover.tsx +488 -0
  47. package/src/components/atoms/select/index.ts +18 -0
  48. package/src/components/atoms/select/select.stories.tsx +455 -0
  49. package/src/components/atoms/select/select.tsx +324 -0
  50. package/src/components/atoms/tooltip/index.ts +24 -0
  51. package/src/components/atoms/tooltip/tooltip.stories.tsx +348 -0
  52. package/src/components/atoms/tooltip/tooltip.test.tsx +363 -0
  53. package/src/components/atoms/tooltip/tooltip.tsx +347 -0
  54. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +8 -17
  55. package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +3 -3
  56. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  57. package/src/components/organisms/card/card.stories.tsx +19 -19
  58. package/src/components/organisms/card/card.test.tsx +1 -1
  59. package/src/components/organisms/card/card.tsx +3 -3
  60. package/src/components/organisms/card/card.visual.test.tsx +11 -11
  61. package/src/components/organisms/navbar/navbar.tsx +2 -2
  62. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  63. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +2 -2
  64. package/src/components/sections/banner/banner.stories.tsx +1 -5
  65. package/src/components/sections/banner/banner.test.tsx +2 -2
  66. package/src/components/sections/banner/banner.tsx +6 -6
  67. package/src/components/sections/card-grid/card-grid.tsx +5 -5
  68. package/src/components/sections/faq-section/faq-section.tsx +2 -2
  69. package/src/components/sections/hero/hero.stories.tsx +7 -7
  70. package/src/components/sections/hero/hero.test.tsx +5 -5
  71. package/src/components/sections/hero/hero.tsx +10 -11
  72. package/src/components/sections/prose/prose.test.tsx +2 -2
  73. package/src/components/sections/prose/prose.tsx +6 -7
  74. package/src/components/sections/river/river.stories.tsx +8 -8
  75. package/src/components/sections/river/river.test.tsx +4 -4
  76. package/src/components/sections/river/river.tsx +8 -16
  77. package/src/components/sections/tout/tout.stories.tsx +7 -31
  78. package/src/components/sections/tout/tout.test.tsx +1 -1
  79. package/src/components/sections/tout/tout.tsx +11 -11
  80. package/src/components/sections/two-column-section/two-column-section.tsx +7 -9
  81. package/src/components/shared/floating-arrow.tsx +78 -0
  82. package/src/components/shared/index.ts +5 -0
  83. package/src/index.ts +98 -0
  84. package/src/lib/form-control.ts +71 -0
  85. package/src/stories/grid-system.stories.tsx +309 -0
  86. package/src/stories/{Introduction.mdx → introduction.mdx} +29 -15
  87. package/src/stories/{ThemeProvider.stories.tsx → theme-provider.stories.tsx} +8 -22
  88. package/src/stories/{TokenShowcase.stories.tsx → token-showcase.stories.tsx} +1 -20
  89. package/src/stories/token-showcase.tsx +777 -0
  90. package/src/styles.css +3 -0
  91. package/src/tests/token-resolution.test.tsx +298 -0
  92. package/src/theme/hooks.ts +1 -1
  93. package/src/theme/index.ts +1 -1
  94. package/src/theme/theme-provider.test.tsx +270 -0
  95. package/src/theme/{ThemeProvider.tsx → theme-provider.tsx} +18 -2
  96. package/src/stories/GridSystem.stories.tsx +0 -84
  97. package/src/stories/TokenShowcase.tsx +0 -1429
@@ -0,0 +1,409 @@
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+ import { tv, type VariantProps } from "tailwind-variants";
5
+ import { formControlBase } from "@/lib/form-control";
6
+ import { cn } from "@/lib/utils";
7
+ import { Button, type ButtonProps } from "../button";
8
+
9
+ // =============================================================================
10
+ // InputGroup Variants
11
+ // =============================================================================
12
+
13
+ const inputGroupVariants = tv({
14
+ base: [
15
+ ...formControlBase,
16
+ // InputGroup-specific: Container layout and styling
17
+ "group/input-group relative flex w-full items-center justify-start",
18
+ "shadow-xs",
19
+ // Override focus state to work with child focus detection
20
+ "focus-visible:border-ui-color-border focus-visible:ring-0",
21
+ // Override hover to work with InputGroup structure
22
+ "hover:bg-ui-control-background",
23
+ // Height and layout (overridden by size variants)
24
+ "min-w-0",
25
+ "has-[>textarea]:h-auto",
26
+ "has-[>[data-align=inline-start]]:[&>input]:pl-6",
27
+ "has-[>[data-align=inline-end]]:[&>input]:pr-6",
28
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pt-spatial-ui-input-group-gap-block has-[>[data-align=block-start]]:[&>input]:pb-spatial-ui-input-group-padding-y-medium",
29
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pb-spatial-ui-input-group-gap-block",
30
+ // Focus state detection on child input (overrides formControlBase focus)
31
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ui-accent-base has-[[data-slot=input-group-control]:focus-visible]:ring-4 has-[[data-slot=input-group-control]:focus-visible]:ring-ui-color-focus",
32
+ // Error state detection on child elements
33
+ "has-[[data-slot][aria-invalid=true]]:border-ui-error-color has-[[data-slot][aria-invalid=true]]:ring-ui-error-color/20",
34
+ // Disabled state (overrides formControlBase disabled)
35
+ "data-[disabled=true]:bg-ui-control-background-disabled data-[disabled=true]:opacity-50 data-[disabled=true]:cursor-not-allowed",
36
+ ],
37
+ variants: {
38
+ size: {
39
+ sm: "h-spatial-ui-input-group-height-small",
40
+ default: "h-spatial-ui-input-group-height-medium",
41
+ lg: "h-spatial-ui-input-group-height-large",
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ size: "default",
46
+ },
47
+ });
48
+
49
+ // =============================================================================
50
+ // InputGroupAddon Variants
51
+ // =============================================================================
52
+
53
+ const inputGroupAddonVariants = tv({
54
+ base: [
55
+ "flex items-center justify-center gap-6",
56
+ "typography-ui-text-sm text-text-muted",
57
+ "select-none cursor-text",
58
+ "[&>svg:not([class*='size-'])]:size-16",
59
+ "[&_button]:text-[unset] [&_button]:cursor-pointer",
60
+ "group-data-[disabled=true]/input-group:opacity-50",
61
+ ],
62
+ variants: {
63
+ align: {
64
+ "inline-start": [
65
+ "order-first h-full",
66
+ "pl-spatial-ui-input-group-padding-x-medium",
67
+ "has-[>button]:ml-[-6px]",
68
+ ],
69
+ "inline-end": [
70
+ "order-last h-full",
71
+ "pr-spatial-ui-input-group-padding-x-medium",
72
+ "has-[>button]:mr-[-6px]",
73
+ ],
74
+ "block-start": [
75
+ "order-first h-auto w-full justify-start",
76
+ "px-spatial-ui-input-group-padding-x-medium pt-spatial-ui-input-group-padding-y-medium",
77
+ "[.border-b]:pb-spatial-ui-input-group-padding-y-medium",
78
+ // Use shared text style for block addons (12px, Regular weight per Figma)
79
+ "typography-ui-text-xs",
80
+ ],
81
+ "block-end": [
82
+ "order-last h-auto w-full justify-start",
83
+ "px-spatial-ui-input-group-padding-x-medium pb-spatial-ui-input-group-padding-y-medium",
84
+ "[.border-t]:pt-spatial-ui-input-group-padding-y-medium",
85
+ // Use shared text style for block addons (12px, Regular weight per Figma)
86
+ "typography-ui-text-xs",
87
+ ],
88
+ },
89
+ },
90
+ defaultVariants: {
91
+ align: "inline-start",
92
+ },
93
+ });
94
+
95
+ // =============================================================================
96
+ // InputGroup Component
97
+ // =============================================================================
98
+
99
+ export interface InputGroupProps
100
+ extends React.FieldsetHTMLAttributes<HTMLFieldSetElement>,
101
+ VariantProps<typeof inputGroupVariants> {}
102
+
103
+ /**
104
+ * InputGroup component for combining inputs with addons, buttons, and text.
105
+ *
106
+ * A container that groups an input with prefix/suffix addons, icons, or buttons.
107
+ * Supports inline (left/right) and block (top/bottom) addon positioning.
108
+ *
109
+ * Uses semantic UI tokens for theming support.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * // With prefix icon
114
+ * <InputGroup>
115
+ * <InputGroupAddon>
116
+ * <SearchIcon />
117
+ * </InputGroupAddon>
118
+ * <InputGroupInput placeholder="Search..." />
119
+ * </InputGroup>
120
+ *
121
+ * // With suffix button
122
+ * <InputGroup>
123
+ * <InputGroupInput placeholder="Enter email" />
124
+ * <InputGroupAddon align="inline-end">
125
+ * <InputGroupButton>Subscribe</InputGroupButton>
126
+ * </InputGroupAddon>
127
+ * </InputGroup>
128
+ *
129
+ * // With text prefix
130
+ * <InputGroup>
131
+ * <InputGroupAddon>
132
+ * <InputGroupText>https://</InputGroupText>
133
+ * </InputGroupAddon>
134
+ * <InputGroupInput placeholder="example.com" />
135
+ * </InputGroup>
136
+ * ```
137
+ */
138
+ function InputGroup({ className, size, disabled, ...props }: InputGroupProps) {
139
+ return (
140
+ <fieldset
141
+ data-slot="input-group"
142
+ data-disabled={disabled || undefined}
143
+ disabled={disabled}
144
+ className={cn(
145
+ "p-0 m-0 min-w-0",
146
+ inputGroupVariants({ size, class: className }),
147
+ )}
148
+ {...props}
149
+ />
150
+ );
151
+ }
152
+
153
+ // =============================================================================
154
+ // InputGroupAddon Component
155
+ // =============================================================================
156
+
157
+ export interface InputGroupAddonProps
158
+ extends React.HTMLAttributes<HTMLDivElement>,
159
+ VariantProps<typeof inputGroupAddonVariants> {}
160
+
161
+ /**
162
+ * InputGroupAddon component for positioning addons within an InputGroup.
163
+ *
164
+ * Can contain icons, text, or buttons. Clicking the addon focuses the input.
165
+ *
166
+ * @example
167
+ * ```tsx
168
+ * // Inline start (default - left side)
169
+ * <InputGroupAddon>
170
+ * <SearchIcon />
171
+ * </InputGroupAddon>
172
+ *
173
+ * // Inline end (right side)
174
+ * <InputGroupAddon align="inline-end">
175
+ * <InputGroupButton>Submit</InputGroupButton>
176
+ * </InputGroupAddon>
177
+ *
178
+ * // Block positions (top/bottom)
179
+ * <InputGroupAddon align="block-start">
180
+ * <label>Email Address</label>
181
+ * </InputGroupAddon>
182
+ * ```
183
+ */
184
+ function InputGroupAddon({
185
+ className,
186
+ align = "inline-start",
187
+ onClick,
188
+ onKeyDown,
189
+ ...props
190
+ }: InputGroupAddonProps) {
191
+ const focusInput = (element: HTMLElement) => {
192
+ element.parentElement?.querySelector("input")?.focus();
193
+ };
194
+
195
+ const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
196
+ // Don't focus input if clicking a button inside the addon
197
+ if ((e.target as HTMLElement).closest("button")) {
198
+ onClick?.(e);
199
+ return;
200
+ }
201
+ // Focus the input when clicking the addon
202
+ focusInput(e.currentTarget);
203
+ onClick?.(e);
204
+ };
205
+
206
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
207
+ // Focus input on Enter or Space (unless inside a button)
208
+ if (
209
+ (e.key === "Enter" || e.key === " ") &&
210
+ !(e.target as HTMLElement).closest("button")
211
+ ) {
212
+ focusInput(e.currentTarget);
213
+ }
214
+ onKeyDown?.(e);
215
+ };
216
+
217
+ return (
218
+ // biome-ignore lint/a11y/noStaticElementInteractions: Click-to-focus is a convenience UX pattern; primary interaction is via the input itself
219
+ <div
220
+ data-slot="input-group-addon"
221
+ data-align={align}
222
+ className={cn(inputGroupAddonVariants({ align, class: className }))}
223
+ onClick={handleClick}
224
+ onKeyDown={handleKeyDown}
225
+ {...props}
226
+ />
227
+ );
228
+ }
229
+
230
+ // =============================================================================
231
+ // InputGroupButton Component
232
+ // =============================================================================
233
+
234
+ const inputGroupButtonVariants = tv({
235
+ base: [
236
+ "typography-ui-button-small shadow-none flex gap-6 items-center",
237
+ "focus-visible:ring-1 focus-visible:ring-offset-0 focus-visible:ring-ui-color-border",
238
+ "transition-opacity duration-150",
239
+ ],
240
+ variants: {
241
+ size: {
242
+ xs: "!h-24 gap-4 px-8 rounded-surface-ui-small [&>svg:not([class*='size-'])]:size-16 has-[>svg]:px-6",
243
+ sm: "!h-28 px-10 gap-6 rounded-surface-ui-medium has-[>svg]:px-8",
244
+ "icon-xs":
245
+ "!size-24 rounded-surface-ui-small p-0 [&>svg:not([class*='size-'])]:size-16 has-[>svg]:p-0",
246
+ "icon-sm":
247
+ "!size-28 rounded-surface-ui-medium p-0 [&>svg:not([class*='size-'])]:size-16 has-[>svg]:p-0",
248
+ },
249
+ },
250
+ defaultVariants: {
251
+ size: "xs",
252
+ },
253
+ });
254
+
255
+ export interface InputGroupButtonProps
256
+ extends Omit<ButtonProps, "size">,
257
+ VariantProps<typeof inputGroupButtonVariants> {}
258
+
259
+ /**
260
+ * InputGroupButton component for inline buttons within an InputGroup.
261
+ *
262
+ * A small button variant designed to fit inside input groups.
263
+ *
264
+ * @example
265
+ * ```tsx
266
+ * <InputGroupAddon align="inline-end">
267
+ * <InputGroupButton>Submit</InputGroupButton>
268
+ * </InputGroupAddon>
269
+ *
270
+ * // Icon button
271
+ * <InputGroupAddon align="inline-end">
272
+ * <InputGroupButton size="icon-xs">
273
+ * <ClearIcon />
274
+ * </InputGroupButton>
275
+ * </InputGroupAddon>
276
+ * ```
277
+ */
278
+ function InputGroupButton({
279
+ className,
280
+ type = "button",
281
+ variant = "ghost",
282
+ size = "xs",
283
+ ...props
284
+ }: InputGroupButtonProps) {
285
+ return (
286
+ <Button
287
+ type={type}
288
+ data-size={size}
289
+ variant={variant}
290
+ className={cn(inputGroupButtonVariants({ size, class: className }))}
291
+ {...props}
292
+ />
293
+ );
294
+ }
295
+
296
+ // =============================================================================
297
+ // InputGroupText Component
298
+ // =============================================================================
299
+
300
+ export interface InputGroupTextProps
301
+ extends React.HTMLAttributes<HTMLSpanElement> {}
302
+
303
+ /**
304
+ * InputGroupText component for static text within an InputGroup.
305
+ *
306
+ * Use for prefixes like "https://" or suffixes like ".com"
307
+ *
308
+ * @example
309
+ * ```tsx
310
+ * <InputGroupAddon>
311
+ * <InputGroupText>https://</InputGroupText>
312
+ * </InputGroupAddon>
313
+ * ```
314
+ */
315
+ function InputGroupText({ className, ...props }: InputGroupTextProps) {
316
+ return (
317
+ <span
318
+ className={cn(
319
+ "flex items-center gap-6 typography-ui-text-sm text-text-muted",
320
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-16",
321
+ className,
322
+ )}
323
+ {...props}
324
+ />
325
+ );
326
+ }
327
+
328
+ // =============================================================================
329
+ // InputGroupInput Component
330
+ // =============================================================================
331
+
332
+ export interface InputGroupInputProps
333
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
334
+
335
+ /**
336
+ * InputGroupInput component - the input element within an InputGroup.
337
+ *
338
+ * Styled to integrate seamlessly with the InputGroup container.
339
+ *
340
+ * @example
341
+ * ```tsx
342
+ * <InputGroup>
343
+ * <InputGroupInput placeholder="Enter text..." />
344
+ * </InputGroup>
345
+ * ```
346
+ */
347
+ function InputGroupInput({ className, ...props }: InputGroupInputProps) {
348
+ return (
349
+ <input
350
+ data-slot="input-group-control"
351
+ className={cn(
352
+ "flex-1 min-w-0 h-full w-full",
353
+ "border-0 bg-transparent shadow-none outline-none",
354
+ "typography-ui-text-sm text-left placeholder:text-text-muted",
355
+ "px-spatial-ui-input-group-padding-x-medium",
356
+ "focus-visible:ring-0",
357
+ className,
358
+ )}
359
+ {...props}
360
+ />
361
+ );
362
+ }
363
+
364
+ // =============================================================================
365
+ // InputGroupTextarea Component
366
+ // =============================================================================
367
+
368
+ export interface InputGroupTextareaProps
369
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
370
+
371
+ /**
372
+ * InputGroupTextarea component - a textarea element within an InputGroup.
373
+ *
374
+ * Styled to integrate seamlessly with the InputGroup container.
375
+ *
376
+ * @example
377
+ * ```tsx
378
+ * <InputGroup>
379
+ * <InputGroupTextarea placeholder="Enter long text..." rows={4} />
380
+ * </InputGroup>
381
+ * ```
382
+ */
383
+ function InputGroupTextarea({ className, ...props }: InputGroupTextareaProps) {
384
+ return (
385
+ <textarea
386
+ data-slot="input-group-control"
387
+ className={cn(
388
+ "flex-1 min-w-0 w-full resize-none",
389
+ "border-0 bg-transparent shadow-none outline-none",
390
+ "typography-ui-text-sm text-left placeholder:text-text-muted",
391
+ "px-spatial-ui-input-group-padding-x-medium py-spatial-ui-input-group-padding-y-medium",
392
+ "focus-visible:ring-0",
393
+ className,
394
+ )}
395
+ {...props}
396
+ />
397
+ );
398
+ }
399
+
400
+ export {
401
+ InputGroup,
402
+ InputGroupAddon,
403
+ InputGroupButton,
404
+ InputGroupText,
405
+ InputGroupInput,
406
+ InputGroupTextarea,
407
+ inputGroupVariants,
408
+ inputGroupAddonVariants,
409
+ };
@@ -0,0 +1,228 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Input } from ".";
3
+
4
+ const meta: Meta<typeof Input> = {
5
+ title: "Atoms/Input",
6
+ component: Input,
7
+ } as Meta<typeof Input>;
8
+
9
+ export default meta;
10
+ type Story = StoryObj<typeof Input>;
11
+
12
+ export const Playground: Story = {
13
+ render: (args) => <Input {...args} />,
14
+ };
15
+ Playground.argTypes = {
16
+ size: {
17
+ control: {
18
+ type: "radio",
19
+ },
20
+ options: ["sm", "default", "lg"],
21
+ },
22
+ disabled: {
23
+ control: {
24
+ type: "boolean",
25
+ },
26
+ },
27
+ error: {
28
+ control: {
29
+ type: "boolean",
30
+ },
31
+ },
32
+ placeholder: {
33
+ control: {
34
+ type: "text",
35
+ },
36
+ },
37
+ };
38
+ Playground.args = {
39
+ size: "default",
40
+ disabled: false,
41
+ error: false,
42
+ placeholder: "Enter text...",
43
+ };
44
+
45
+ // =============================================================================
46
+ // States
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Default state - white background with subtle border
51
+ */
52
+ export const Default = () => <Input placeholder="Enter text..." />;
53
+
54
+ /**
55
+ * Filled state - shows how a filled input looks
56
+ */
57
+ export const Filled = () => <Input defaultValue="Filled content" />;
58
+
59
+ /**
60
+ * Error state - red border and text
61
+ */
62
+ export const ErrorState = () => <Input error placeholder="Invalid input" />;
63
+
64
+ /**
65
+ * Disabled state - grayed out and not interactive
66
+ */
67
+ export const Disabled = () => <Input disabled placeholder="Disabled input" />;
68
+
69
+ /**
70
+ * All states comparison
71
+ */
72
+ export const AllStates = () => (
73
+ <div className="flex flex-col gap-16 max-w-[320px]">
74
+ <div>
75
+ <p className="mb-8 text-12 text-text-muted">Default</p>
76
+ <Input placeholder="Enter text..." />
77
+ </div>
78
+ <div>
79
+ <p className="mb-8 text-12 text-text-muted">Hover (hover the input)</p>
80
+ <Input placeholder="Hover me..." />
81
+ </div>
82
+ <div>
83
+ <p className="mb-8 text-12 text-text-muted">Focus (click the input)</p>
84
+ <Input placeholder="Click to focus..." />
85
+ </div>
86
+ <div>
87
+ <p className="mb-8 text-12 text-text-muted">Filled</p>
88
+ <Input defaultValue="Filled content" />
89
+ </div>
90
+ <div>
91
+ <p className="mb-8 text-12 text-text-muted">Error</p>
92
+ <Input error placeholder="Invalid input" />
93
+ </div>
94
+ <div>
95
+ <p className="mb-8 text-12 text-text-muted">Disabled</p>
96
+ <Input disabled placeholder="Disabled input" />
97
+ </div>
98
+ </div>
99
+ );
100
+
101
+ // =============================================================================
102
+ // Sizes
103
+ // =============================================================================
104
+
105
+ /**
106
+ * Small size - compact input for tight layouts
107
+ */
108
+ export const Small = () => <Input size="sm" placeholder="Small input" />;
109
+
110
+ /**
111
+ * Default size - standard 48px height
112
+ */
113
+ export const Medium = () => (
114
+ <Input size="default" placeholder="Default input" />
115
+ );
116
+
117
+ /**
118
+ * Large size - more prominent input
119
+ */
120
+ export const Large = () => <Input size="lg" placeholder="Large input" />;
121
+
122
+ /**
123
+ * All sizes comparison
124
+ */
125
+ export const AllSizes = () => (
126
+ <div className="flex flex-col gap-16 max-w-[320px]">
127
+ <div>
128
+ <p className="mb-8 text-12 text-text-muted">Small (36px)</p>
129
+ <Input size="sm" placeholder="Small input" />
130
+ </div>
131
+ <div>
132
+ <p className="mb-8 text-12 text-text-muted">Default (48px)</p>
133
+ <Input size="default" placeholder="Default input" />
134
+ </div>
135
+ <div>
136
+ <p className="mb-8 text-12 text-text-muted">Large (56px)</p>
137
+ <Input size="lg" placeholder="Large input" />
138
+ </div>
139
+ </div>
140
+ );
141
+
142
+ // =============================================================================
143
+ // Types
144
+ // =============================================================================
145
+
146
+ /**
147
+ * Email input type
148
+ */
149
+ export const EmailType = () => (
150
+ <Input type="email" placeholder="Enter your email" />
151
+ );
152
+
153
+ /**
154
+ * Password input type
155
+ */
156
+ export const PasswordType = () => (
157
+ <Input type="password" placeholder="Enter your password" />
158
+ );
159
+
160
+ /**
161
+ * Number input type
162
+ */
163
+ export const NumberType = () => (
164
+ <Input type="number" placeholder="Enter a number" />
165
+ );
166
+
167
+ /**
168
+ * Search input type
169
+ */
170
+ export const SearchType = () => <Input type="search" placeholder="Search..." />;
171
+
172
+ // =============================================================================
173
+ // Examples
174
+ // =============================================================================
175
+
176
+ /**
177
+ * Form field example with label
178
+ */
179
+ export const WithLabel = () => (
180
+ <div className="flex flex-col gap-8 max-w-[320px]">
181
+ <label
182
+ htmlFor="email-input"
183
+ className="text-14 font-medium text-text-primary"
184
+ >
185
+ Email Address
186
+ </label>
187
+ <Input id="email-input" type="email" placeholder="you@example.com" />
188
+ </div>
189
+ );
190
+
191
+ /**
192
+ * Form field with error message
193
+ */
194
+ export const WithErrorMessage = () => (
195
+ <div className="flex flex-col gap-8 max-w-[320px]">
196
+ <label
197
+ htmlFor="email-error"
198
+ className="text-14 font-medium text-text-primary"
199
+ >
200
+ Email Address
201
+ </label>
202
+ <Input
203
+ id="email-error"
204
+ type="email"
205
+ error
206
+ placeholder="you@example.com"
207
+ defaultValue="invalid-email"
208
+ />
209
+ <p className="text-12 text-ui-error-color">
210
+ Please enter a valid email address
211
+ </p>
212
+ </div>
213
+ );
214
+
215
+ /**
216
+ * Required field indicator
217
+ */
218
+ export const RequiredField = () => (
219
+ <div className="flex flex-col gap-8 max-w-[320px]">
220
+ <label
221
+ htmlFor="required-input"
222
+ className="text-14 font-medium text-text-primary"
223
+ >
224
+ Full Name <span className="text-ui-error-color">*</span>
225
+ </label>
226
+ <Input id="required-input" required placeholder="John Doe" />
227
+ </div>
228
+ );