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