@neynar/ui 0.1.1 → 0.1.2
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/ui/accordion.d.ts +1 -25
- package/dist/components/ui/accordion.d.ts.map +1 -1
- package/dist/components/ui/alert-dialog.d.ts +240 -46
- package/dist/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/components/ui/alert.d.ts +73 -11
- package/dist/components/ui/alert.d.ts.map +1 -1
- package/dist/components/ui/aspect-ratio.d.ts +44 -10
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -1
- package/dist/components/ui/avatar.d.ts +117 -33
- package/dist/components/ui/avatar.d.ts.map +1 -1
- package/dist/components/ui/badge.d.ts +50 -71
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts +231 -49
- package/dist/components/ui/breadcrumb.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +189 -71
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/calendar.d.ts +197 -40
- package/dist/components/ui/calendar.d.ts.map +1 -1
- package/dist/components/ui/card.d.ts +7 -22
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/carousel.d.ts +369 -99
- package/dist/components/ui/carousel.d.ts.map +1 -1
- package/dist/components/ui/chart.d.ts.map +1 -1
- package/dist/components/ui/checkbox.d.ts +110 -38
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/collapsible.d.ts +246 -61
- package/dist/components/ui/collapsible.d.ts.map +1 -1
- package/dist/components/ui/combobox.d.ts +207 -159
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/command.d.ts +336 -67
- package/dist/components/ui/command.d.ts.map +1 -1
- package/dist/components/ui/container.d.ts +159 -64
- package/dist/components/ui/container.d.ts.map +1 -1
- package/dist/components/ui/context-menu.d.ts +321 -39
- package/dist/components/ui/context-menu.d.ts.map +1 -1
- package/dist/components/ui/date-picker.d.ts +113 -86
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/dialog.d.ts +106 -25
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/drawer.d.ts +388 -59
- package/dist/components/ui/drawer.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts +521 -74
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/empty-state.d.ts +148 -76
- package/dist/components/ui/empty-state.d.ts.map +1 -1
- package/dist/components/ui/hover-card.d.ts +253 -34
- package/dist/components/ui/hover-card.d.ts.map +1 -1
- package/dist/components/ui/input.d.ts +143 -44
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/label.d.ts +0 -8
- package/dist/components/ui/label.d.ts.map +1 -1
- package/dist/components/ui/menubar.d.ts +288 -46
- package/dist/components/ui/menubar.d.ts.map +1 -1
- package/dist/components/ui/navigation-menu.d.ts +444 -127
- package/dist/components/ui/navigation-menu.d.ts.map +1 -1
- package/dist/components/ui/pagination.d.ts +342 -66
- package/dist/components/ui/pagination.d.ts.map +1 -1
- package/dist/components/ui/popover.d.ts +0 -8
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/progress.d.ts +88 -30
- package/dist/components/ui/progress.d.ts.map +1 -1
- package/dist/components/ui/radio-group.d.ts +189 -45
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/resizable.d.ts +178 -62
- package/dist/components/ui/resizable.d.ts.map +1 -1
- package/dist/components/ui/scroll-area.d.ts +180 -21
- package/dist/components/ui/scroll-area.d.ts.map +1 -1
- package/dist/components/ui/select.d.ts +382 -60
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/separator.d.ts +52 -39
- package/dist/components/ui/separator.d.ts.map +1 -1
- package/dist/components/ui/sheet.d.ts +144 -27
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sidebar.d.ts +81 -31
- package/dist/components/ui/sidebar.d.ts.map +1 -1
- package/dist/components/ui/skeleton.d.ts +94 -32
- package/dist/components/ui/skeleton.d.ts.map +1 -1
- package/dist/components/ui/slider.d.ts +37 -31
- package/dist/components/ui/slider.d.ts.map +1 -1
- package/dist/components/ui/sonner.d.ts +280 -46
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/stack.d.ts +289 -148
- package/dist/components/ui/stack.d.ts.map +1 -1
- package/dist/components/ui/stories/aspect-ratio.stories.d.ts +1 -2
- package/dist/components/ui/stories/aspect-ratio.stories.d.ts.map +1 -1
- package/dist/components/ui/stories/container.stories.d.ts +2 -3
- package/dist/components/ui/stories/container.stories.d.ts.map +1 -1
- package/dist/components/ui/stories/empty-state.stories.d.ts +2 -2
- package/dist/components/ui/stories/scroll-area.stories.d.ts +1 -2
- package/dist/components/ui/stories/scroll-area.stories.d.ts.map +1 -1
- package/dist/components/ui/stories/stack.stories.d.ts +1 -1
- package/dist/components/ui/stories/text-field.stories.d.ts +7 -1
- package/dist/components/ui/stories/text-field.stories.d.ts.map +1 -1
- package/dist/components/ui/switch.d.ts +44 -38
- package/dist/components/ui/switch.d.ts.map +1 -1
- package/dist/components/ui/table.d.ts +33 -0
- package/dist/components/ui/table.d.ts.map +1 -1
- package/dist/components/ui/tabs.d.ts +4 -22
- package/dist/components/ui/tabs.d.ts.map +1 -1
- package/dist/components/ui/text-field.d.ts +170 -84
- package/dist/components/ui/text-field.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts +106 -29
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/components/ui/theme-toggle.d.ts +190 -65
- package/dist/components/ui/theme-toggle.d.ts.map +1 -1
- package/dist/components/ui/theme.d.ts +107 -23
- package/dist/components/ui/theme.d.ts.map +1 -1
- package/dist/components/ui/toggle-group.d.ts +143 -67
- package/dist/components/ui/toggle-group.d.ts.map +1 -1
- package/dist/components/ui/toggle.d.ts +118 -30
- package/dist/components/ui/toggle.d.ts.map +1 -1
- package/dist/components/ui/tooltip.d.ts +152 -28
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/typography.d.ts +452 -134
- package/dist/components/ui/typography.d.ts.map +1 -1
- package/dist/index.js +9388 -8281
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/llms.txt +173 -3
- package/package.json +5 -2
- package/src/components/ui/accordion.tsx +112 -27
- package/src/components/ui/alert-dialog.tsx +401 -46
- package/src/components/ui/alert.tsx +114 -11
- package/src/components/ui/aspect-ratio.tsx +69 -14
- package/src/components/ui/avatar.tsx +179 -33
- package/src/components/ui/badge.tsx +74 -75
- package/src/components/ui/breadcrumb.tsx +335 -50
- package/src/components/ui/button.tsx +198 -90
- package/src/components/ui/calendar.tsx +867 -43
- package/src/components/ui/card.tsx +140 -33
- package/src/components/ui/carousel.tsx +529 -98
- package/src/components/ui/chart.tsx +222 -1
- package/src/components/ui/checkbox.tsx +176 -38
- package/src/components/ui/collapsible.tsx +321 -67
- package/src/components/ui/combobox.tsx +284 -83
- package/src/components/ui/command.tsx +527 -67
- package/src/components/ui/container.tsx +217 -65
- package/src/components/ui/context-menu.tsx +716 -51
- package/src/components/ui/date-picker.tsx +228 -38
- package/src/components/ui/dialog.tsx +270 -33
- package/src/components/ui/drawer.tsx +546 -67
- package/src/components/ui/dropdown-menu.tsx +657 -74
- package/src/components/ui/empty-state.tsx +241 -82
- package/src/components/ui/hover-card.tsx +328 -39
- package/src/components/ui/input.tsx +207 -44
- package/src/components/ui/label.tsx +98 -8
- package/src/components/ui/menubar.tsx +587 -54
- package/src/components/ui/navigation-menu.tsx +557 -128
- package/src/components/ui/pagination.tsx +561 -79
- package/src/components/ui/popover.tsx +119 -8
- package/src/components/ui/progress.tsx +131 -29
- package/src/components/ui/radio-group.tsx +260 -51
- package/src/components/ui/resizable.tsx +289 -63
- package/src/components/ui/scroll-area.tsx +377 -66
- package/src/components/ui/select.tsx +545 -60
- package/src/components/ui/separator.tsx +146 -40
- package/src/components/ui/sheet.tsx +348 -31
- package/src/components/ui/sidebar.tsx +471 -29
- package/src/components/ui/skeleton.tsx +114 -32
- package/src/components/ui/slider.tsx +77 -31
- package/src/components/ui/sonner.tsx +574 -46
- package/src/components/ui/stack.tsx +423 -101
- package/src/components/ui/switch.tsx +78 -39
- package/src/components/ui/table.tsx +170 -4
- package/src/components/ui/tabs.tsx +108 -22
- package/src/components/ui/text-field.tsx +226 -81
- package/src/components/ui/textarea.tsx +180 -29
- package/src/components/ui/theme-toggle.tsx +313 -65
- package/src/components/ui/theme.tsx +117 -23
- package/src/components/ui/toggle-group.tsx +280 -69
- package/src/components/ui/toggle.tsx +124 -35
- package/src/components/ui/tooltip.tsx +239 -29
- package/src/components/ui/typography.tsx +1115 -165
|
@@ -4,102 +4,136 @@ import { Input } from "./input";
|
|
|
4
4
|
import { Label } from "./label";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Props for
|
|
8
|
-
*
|
|
9
|
-
* Extends standard HTML input properties while omitting 'id' to allow
|
|
10
|
-
* automatic ID generation for accessibility.
|
|
7
|
+
* Props for TextField component (Documentation only - NOT used in component implementation)
|
|
8
|
+
* These types are for documentation generation and should not replace inferred types
|
|
11
9
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* When provided, creates a semantic label-input relationship
|
|
17
|
-
* for improved accessibility and user experience.
|
|
18
|
-
*/
|
|
10
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
11
|
+
type TextFieldDocsProps = {
|
|
12
|
+
/** Label text displayed above the input field @example "Email Address" */
|
|
19
13
|
label?: string;
|
|
20
|
-
/**
|
|
21
|
-
* Helper text displayed below the input
|
|
22
|
-
*
|
|
23
|
-
* Provides additional context or instructions to help users
|
|
24
|
-
* understand the expected input format or purpose.
|
|
25
|
-
*/
|
|
14
|
+
/** Helper text displayed below the input field for additional context */
|
|
26
15
|
helperText?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Error message displayed below the input
|
|
29
|
-
*
|
|
30
|
-
* When provided, sets the input to an error state with
|
|
31
|
-
* destructive styling and proper ARIA attributes.
|
|
32
|
-
* Takes precedence over helperText when both are present.
|
|
33
|
-
*/
|
|
16
|
+
/** Error message displayed below the input field, takes precedence over helperText */
|
|
34
17
|
error?: string;
|
|
35
|
-
/**
|
|
36
|
-
* Whether the field is required
|
|
37
|
-
*
|
|
38
|
-
* Adds a visual asterisk (*) indicator to the label
|
|
39
|
-
* and semantic meaning for form validation.
|
|
40
|
-
*
|
|
41
|
-
* @default false
|
|
42
|
-
*/
|
|
18
|
+
/** Whether the form field is required for validation @default false */
|
|
43
19
|
required?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* Custom ID for the input field
|
|
46
|
-
*
|
|
47
|
-
* If not provided, an automatic ID will be generated
|
|
48
|
-
* using React.useId() for accessibility compliance.
|
|
49
|
-
*/
|
|
20
|
+
/** Custom identifier for the input element (auto-generated if not provided) */
|
|
50
21
|
id?: string;
|
|
51
|
-
|
|
22
|
+
/** The type of input field to render @default "text" */
|
|
23
|
+
type?: React.HTMLInputTypeAttribute;
|
|
24
|
+
/** Placeholder text displayed when the input is empty */
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
/** Current value of the input field (controlled component) */
|
|
27
|
+
value?: string | ReadonlyArray<string> | number;
|
|
28
|
+
/** Default value for uncontrolled input usage */
|
|
29
|
+
defaultValue?: string | ReadonlyArray<string> | number;
|
|
30
|
+
/** Whether the input field is disabled and non-interactive @default false */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Whether the input field is read-only @default false */
|
|
33
|
+
readOnly?: boolean;
|
|
34
|
+
/** Name attribute for form submission and validation */
|
|
35
|
+
name?: string;
|
|
36
|
+
/** Auto-complete hint for browsers */
|
|
37
|
+
autoComplete?: string;
|
|
38
|
+
/** Whether to automatically focus this input when mounted @default false */
|
|
39
|
+
autoFocus?: boolean;
|
|
40
|
+
/** Maximum number of characters allowed in text inputs */
|
|
41
|
+
maxLength?: number;
|
|
42
|
+
/** Minimum number of characters required for text inputs */
|
|
43
|
+
minLength?: number;
|
|
44
|
+
/** Regular expression pattern for client-side validation */
|
|
45
|
+
pattern?: string;
|
|
46
|
+
/** Minimum value for numeric input types */
|
|
47
|
+
min?: string | number;
|
|
48
|
+
/** Maximum value for numeric input types */
|
|
49
|
+
max?: string | number;
|
|
50
|
+
/** Step value for numeric inputs */
|
|
51
|
+
step?: string | number;
|
|
52
|
+
/** Callback fired when the input value changes */
|
|
53
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
54
|
+
/** Callback fired when the input receives focus */
|
|
55
|
+
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
56
|
+
/** Callback fired when the input loses focus */
|
|
57
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
58
|
+
/** Callback fired when a key is pressed down */
|
|
59
|
+
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
60
|
+
/** Callback fired when a key is released */
|
|
61
|
+
onKeyUp?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
62
|
+
/** Additional CSS classes for custom styling */
|
|
63
|
+
className?: string;
|
|
64
|
+
} & React.InputHTMLAttributes<HTMLInputElement>;
|
|
65
|
+
|
|
66
|
+
type TextFieldProps = {
|
|
67
|
+
label?: string;
|
|
68
|
+
helperText?: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
required?: boolean;
|
|
71
|
+
className?: string;
|
|
72
|
+
} & Omit<React.ComponentProps<typeof Input>, "className">;
|
|
52
73
|
|
|
53
74
|
/**
|
|
54
|
-
*
|
|
75
|
+
* TextField - Complete form field with label, input, and descriptive text
|
|
55
76
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
77
|
+
* A comprehensive form field component that combines Input and Label components with
|
|
78
|
+
* additional helper text and error handling capabilities. Automatically manages
|
|
79
|
+
* accessibility attributes, ID generation, and ARIA relationships to ensure forms
|
|
80
|
+
* are usable by all users including those using assistive technologies.
|
|
60
81
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
82
|
+
* Built on top of the Input and Label primitives, TextField provides a complete
|
|
83
|
+
* form field solution with consistent styling, proper semantic markup, and enhanced
|
|
84
|
+
* UX features. Supports all HTML input types while adding form validation states,
|
|
85
|
+
* descriptive text, and automatic accessibility compliance.
|
|
64
86
|
*
|
|
65
|
-
* @example
|
|
87
|
+
* @example
|
|
66
88
|
* ```tsx
|
|
89
|
+
* // Basic text input with label
|
|
67
90
|
* <TextField
|
|
68
91
|
* label="Email Address"
|
|
69
92
|
* type="email"
|
|
70
93
|
* placeholder="Enter your email"
|
|
94
|
+
* name="email"
|
|
71
95
|
* />
|
|
72
96
|
* ```
|
|
73
97
|
*
|
|
74
|
-
* @example
|
|
98
|
+
* @example
|
|
75
99
|
* ```tsx
|
|
100
|
+
* // Required field with helper text
|
|
76
101
|
* <TextField
|
|
77
102
|
* label="Username"
|
|
78
103
|
* placeholder="@username"
|
|
79
|
-
* helperText="Choose a unique username (3-20 characters)"
|
|
104
|
+
* helperText="Choose a unique username (3-20 characters, letters and numbers only)"
|
|
105
|
+
* required
|
|
106
|
+
* minLength={3}
|
|
107
|
+
* maxLength={20}
|
|
108
|
+
* pattern="[a-zA-Z0-9]+"
|
|
80
109
|
* />
|
|
81
110
|
* ```
|
|
82
111
|
*
|
|
83
|
-
* @example
|
|
112
|
+
* @example
|
|
84
113
|
* ```tsx
|
|
114
|
+
* // Password field with validation error
|
|
85
115
|
* <TextField
|
|
86
116
|
* label="Password"
|
|
87
117
|
* type="password"
|
|
88
118
|
* required
|
|
89
|
-
* error="Password must be at least 8 characters"
|
|
119
|
+
* error="Password must be at least 8 characters long"
|
|
90
120
|
* value={password}
|
|
91
121
|
* onChange={(e) => setPassword(e.target.value)}
|
|
122
|
+
* minLength={8}
|
|
123
|
+
* autoComplete="new-password"
|
|
92
124
|
* />
|
|
93
125
|
* ```
|
|
94
126
|
*
|
|
95
|
-
* @example
|
|
127
|
+
* @example
|
|
96
128
|
* ```tsx
|
|
129
|
+
* // Controlled component with real-time validation
|
|
97
130
|
* const [email, setEmail] = useState("");
|
|
98
131
|
* const [error, setError] = useState("");
|
|
99
132
|
*
|
|
100
133
|
* const validateEmail = (value: string) => {
|
|
101
|
-
*
|
|
102
|
-
*
|
|
134
|
+
* const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
135
|
+
* if (!emailRegex.test(value)) {
|
|
136
|
+
* setError("Please enter a valid email address");
|
|
103
137
|
* } else {
|
|
104
138
|
* setError("");
|
|
105
139
|
* }
|
|
@@ -113,50 +147,161 @@ export type TextFieldProps = Omit<React.ComponentProps<"input">, "id"> & {
|
|
|
113
147
|
* setEmail(e.target.value);
|
|
114
148
|
* validateEmail(e.target.value);
|
|
115
149
|
* }}
|
|
150
|
+
* onBlur={(e) => validateEmail(e.target.value)}
|
|
116
151
|
* error={error}
|
|
117
152
|
* required
|
|
153
|
+
* autoComplete="email"
|
|
118
154
|
* />
|
|
119
155
|
* ```
|
|
120
156
|
*
|
|
121
|
-
* @example
|
|
157
|
+
* @example
|
|
122
158
|
* ```tsx
|
|
123
|
-
*
|
|
159
|
+
* // Number input with constraints
|
|
160
|
+
* <TextField
|
|
161
|
+
* label="Age"
|
|
162
|
+
* type="number"
|
|
163
|
+
* min={0}
|
|
164
|
+
* max={120}
|
|
165
|
+
* step={1}
|
|
166
|
+
* helperText="Enter your age in years"
|
|
167
|
+
* placeholder="25"
|
|
168
|
+
* />
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* // Search input with custom styling
|
|
174
|
+
* <TextField
|
|
175
|
+
* label="Search"
|
|
176
|
+
* type="search"
|
|
177
|
+
* placeholder="Search products..."
|
|
178
|
+
* className="max-w-md"
|
|
179
|
+
* onKeyDown={(e) => {
|
|
180
|
+
* if (e.key === 'Enter') {
|
|
181
|
+
* handleSearch(e.currentTarget.value);
|
|
182
|
+
* }
|
|
183
|
+
* }}
|
|
184
|
+
* />
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* // Form integration with React Hook Form
|
|
190
|
+
* const { register, formState: { errors } } = useForm();
|
|
191
|
+
*
|
|
192
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
124
193
|
* <TextField
|
|
125
194
|
* label="Full Name"
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* helperText="
|
|
195
|
+
* {...register("fullName", { required: "Full name is required" })}
|
|
196
|
+
* error={errors.fullName?.message}
|
|
197
|
+
* helperText="Enter your first and last name"
|
|
129
198
|
* />
|
|
199
|
+
*
|
|
130
200
|
* <TextField
|
|
131
|
-
* label="
|
|
132
|
-
* type="
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
201
|
+
* label="Phone Number"
|
|
202
|
+
* type="tel"
|
|
203
|
+
* {...register("phone", {
|
|
204
|
+
* pattern: {
|
|
205
|
+
* value: /^[\+]?[1-9][\d]{0,15}$/,
|
|
206
|
+
* message: "Please enter a valid phone number"
|
|
207
|
+
* }
|
|
208
|
+
* })}
|
|
209
|
+
* error={errors.phone?.message}
|
|
210
|
+
* autoComplete="tel"
|
|
136
211
|
* />
|
|
137
212
|
* </form>
|
|
138
213
|
* ```
|
|
139
214
|
*
|
|
140
|
-
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```tsx
|
|
217
|
+
* // File input with custom validation
|
|
218
|
+
* <TextField
|
|
219
|
+
* label="Profile Picture"
|
|
220
|
+
* type="file"
|
|
221
|
+
* accept="image/*"
|
|
222
|
+
* helperText="Upload a PNG, JPG or GIF (max 5MB)"
|
|
223
|
+
* onChange={(e) => {
|
|
224
|
+
* const file = e.target.files?.[0];
|
|
225
|
+
* if (file && file.size > 5 * 1024 * 1024) {
|
|
226
|
+
* setError("File size must be less than 5MB");
|
|
227
|
+
* } else {
|
|
228
|
+
* setError("");
|
|
229
|
+
* handleFileUpload(file);
|
|
230
|
+
* }
|
|
231
|
+
* }}
|
|
232
|
+
* error={fileError}
|
|
233
|
+
* />
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```tsx
|
|
238
|
+
* // Date input with range constraints
|
|
239
|
+
* <TextField
|
|
240
|
+
* label="Birth Date"
|
|
241
|
+
* type="date"
|
|
242
|
+
* min="1900-01-01"
|
|
243
|
+
* max={new Date().toISOString().split('T')[0]}
|
|
244
|
+
* helperText="You must be 18 or older to register"
|
|
245
|
+
* required
|
|
246
|
+
* />
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```tsx
|
|
251
|
+
* // Disabled field with explanation
|
|
252
|
+
* <TextField
|
|
253
|
+
* label="Account ID"
|
|
254
|
+
* value="ACC-12345"
|
|
255
|
+
* disabled
|
|
256
|
+
* helperText="This value is automatically generated and cannot be changed"
|
|
257
|
+
* />
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* @accessibility
|
|
261
|
+
* - **Semantic Association**: Proper label-input relationship via `htmlFor` and `id` attributes
|
|
262
|
+
* - **ARIA Support**: Implements `aria-invalid`, `aria-describedby`, and `aria-required` attributes
|
|
263
|
+
* - **Focus Management**: Keyboard navigation with visible focus indicators and proper tab order
|
|
264
|
+
* - **Screen Reader Support**: Helper text and error messages are properly announced via `aria-describedby`
|
|
265
|
+
* - **Required Fields**: Visual asterisk indicator and semantic `aria-required` for screen readers
|
|
266
|
+
* - **Error States**: Clear visual styling and programmatic error indication with `aria-invalid`
|
|
267
|
+
* - **Automatic ID Generation**: Uses `React.useId()` to prevent conflicts and ensure accessibility
|
|
268
|
+
* - **Click-to-Focus**: Clicking the label automatically focuses the input field
|
|
269
|
+
* - **Disabled State**: Proper disabled styling and screen reader announcements
|
|
270
|
+
* - **Validation Support**: Works with native HTML5 validation and custom validation libraries
|
|
271
|
+
*
|
|
272
|
+
* @remarks
|
|
273
|
+
* The TextField component automatically handles:
|
|
274
|
+
* - ID generation and label-input association for accessibility compliance
|
|
275
|
+
* - ARIA attribute management based on component state (error, required, helper text)
|
|
276
|
+
* - Error state precedence over helper text display
|
|
277
|
+
* - Focus management and keyboard navigation patterns
|
|
278
|
+
* - Consistent styling inheritance from Input and Label components
|
|
279
|
+
* - Screen reader compatibility with proper announcements
|
|
141
280
|
*
|
|
142
|
-
*
|
|
143
|
-
* - **ARIA Support**: Implements `aria-invalid`, `aria-describedby`, and `aria-required`
|
|
144
|
-
* - **Focus Management**: Keyboard navigation and focus indicators work correctly
|
|
145
|
-
* - **Screen Readers**: Helper text and error messages are properly announced
|
|
146
|
-
* - **Required Fields**: Visual and semantic indication with asterisk and ARIA attributes
|
|
147
|
-
* - **Error States**: Clear visual and programmatic error indication
|
|
281
|
+
* ## Component Composition
|
|
148
282
|
*
|
|
149
|
-
*
|
|
283
|
+
* TextField is composed of:
|
|
284
|
+
* - **Root Container**: `div` with `space-y-2` for consistent spacing
|
|
285
|
+
* - **Label**: Radix UI Label primitive with proper association and styling
|
|
286
|
+
* - **Input**: Enhanced HTML input with design system styling and validation states
|
|
287
|
+
* - **Helper/Error Text**: Conditional paragraph elements with proper ARIA linking
|
|
150
288
|
*
|
|
151
|
-
*
|
|
152
|
-
* - Error state styling with destructive colors
|
|
153
|
-
* - Consistent spacing with `space-y-2` utility
|
|
154
|
-
* - Muted foreground color for helper text
|
|
155
|
-
* - Required field indicator styling
|
|
289
|
+
* ## Input Type Support
|
|
156
290
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
291
|
+
* Supports all HTML input types including:
|
|
292
|
+
* - **Text Types**: `text`, `email`, `password`, `tel`, `url`, `search`
|
|
293
|
+
* - **Numeric Types**: `number`, `range` (with min/max/step support)
|
|
294
|
+
* - **Date/Time Types**: `date`, `datetime-local`, `month`, `time`, `week`
|
|
295
|
+
* - **Other Types**: `file`, `hidden`, `color`, `checkbox`, `radio`
|
|
296
|
+
*
|
|
297
|
+
* @see {@link Input} The underlying input component with styling and validation
|
|
298
|
+
* @see {@link Label} The label component used for accessible field labels
|
|
299
|
+
* @see {@link https://ui.shadcn.com/docs/components/input} shadcn/ui Input documentation
|
|
300
|
+
* @see {@link https://www.radix-ui.com/primitives/docs/components/label} Radix UI Label primitive
|
|
301
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input} MDN Input element reference
|
|
302
|
+
* @see {@link https://www.w3.org/WAI/tutorials/forms/} W3C Forms accessibility tutorial
|
|
303
|
+
* @since 1.0.0
|
|
304
|
+
*/
|
|
160
305
|
const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
|
|
161
306
|
({ label, helperText, error, required, className, id, ...props }, ref) => {
|
|
162
307
|
const idIfNeeded = React.useId();
|
|
@@ -3,20 +3,95 @@ import * as React from "react";
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Props for Textarea (Documentation only - NOT used in component implementation)
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
* Multi-line text input for extended content entry. Built on the native HTML
|
|
9
|
+
* textarea element with enhanced styling and accessibility features.
|
|
10
|
+
*/
|
|
11
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
12
|
+
type TextareaDocsProps = {
|
|
13
|
+
/** Additional CSS classes for styling customization */
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Placeholder text displayed when the textarea is empty */
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
/** Whether the textarea is disabled and non-interactive @default false */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Whether the textarea is read-only (non-editable) @default false */
|
|
20
|
+
readOnly?: boolean;
|
|
21
|
+
/** Number of visible text lines for initial height @default 2 */
|
|
22
|
+
rows?: number;
|
|
23
|
+
/** Number of visible character columns for initial width */
|
|
24
|
+
cols?: number;
|
|
25
|
+
/** Maximum number of characters allowed (UTF-16 code units) */
|
|
26
|
+
maxLength?: number;
|
|
27
|
+
/** Minimum number of characters required */
|
|
28
|
+
minLength?: number;
|
|
29
|
+
/** Whether the textarea is required for form submission @default false */
|
|
30
|
+
required?: boolean;
|
|
31
|
+
/** Form element ID that this textarea belongs to */
|
|
32
|
+
form?: string;
|
|
33
|
+
/** Name attribute for form submission */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Unique identifier for this textarea element */
|
|
36
|
+
id?: string;
|
|
37
|
+
/** Current value of the textarea (for controlled components) */
|
|
38
|
+
value?: string;
|
|
39
|
+
/** Initial value for uncontrolled components */
|
|
40
|
+
defaultValue?: string;
|
|
41
|
+
/** How text wrapping should be handled @default "soft" */
|
|
42
|
+
wrap?: "soft" | "hard" | "off";
|
|
43
|
+
/** Enable automatic focusing when component mounts @default false */
|
|
44
|
+
autoFocus?: boolean;
|
|
45
|
+
/** Hint for form autofill feature */
|
|
46
|
+
autoComplete?: string;
|
|
47
|
+
/** Control automatic capitalization behavior */
|
|
48
|
+
autoCapitalize?: "none" | "sentences" | "words" | "characters";
|
|
49
|
+
/** Enable spell checking @default true */
|
|
50
|
+
spellCheck?: boolean;
|
|
51
|
+
/** Callback fired when the value changes @param event - The change event */
|
|
52
|
+
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
53
|
+
/** Callback fired when the textarea gains focus @param event - The focus event */
|
|
54
|
+
onFocus?: (event: React.FocusEvent<HTMLTextAreaElement>) => void;
|
|
55
|
+
/** Callback fired when the textarea loses focus @param event - The blur event */
|
|
56
|
+
onBlur?: (event: React.FocusEvent<HTMLTextAreaElement>) => void;
|
|
57
|
+
/** Callback fired on keydown events @param event - The keyboard event */
|
|
58
|
+
onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
59
|
+
/** Callback fired on keyup events @param event - The keyboard event */
|
|
60
|
+
onKeyUp?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
61
|
+
/** Callback fired on keypress events @param event - The keyboard event */
|
|
62
|
+
onKeyPress?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
63
|
+
/** Indicates if the input fails validation @default false */
|
|
64
|
+
"aria-invalid"?: boolean | "false" | "true" | "grammar" | "spelling";
|
|
65
|
+
/** References element(s) that describe this textarea */
|
|
66
|
+
"aria-describedby"?: string;
|
|
67
|
+
/** References element that labels this textarea */
|
|
68
|
+
"aria-labelledby"?: string;
|
|
69
|
+
/** Accessible label when no visible label is present */
|
|
70
|
+
"aria-label"?: string;
|
|
71
|
+
/** Tab index for keyboard navigation order */
|
|
72
|
+
tabIndex?: number;
|
|
73
|
+
/** CSS resize behavior control */
|
|
74
|
+
style?: React.CSSProperties & {
|
|
75
|
+
resize?: "none" | "both" | "horizontal" | "vertical";
|
|
76
|
+
};
|
|
77
|
+
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Textarea - Multi-line text input control for extended content entry
|
|
81
|
+
*
|
|
82
|
+
* A styled textarea component built on the native HTML textarea element for entering
|
|
83
|
+
* longer pieces of text that span multiple lines, such as comments, descriptions,
|
|
84
|
+
* messages, or any content that requires multiple lines of input. Features automatic
|
|
85
|
+
* content-based resizing using CSS field-sizing when supported by the browser.
|
|
12
86
|
*
|
|
13
87
|
* Built following shadcn/ui design patterns with consistent styling and behavior
|
|
14
|
-
* across form elements.
|
|
88
|
+
* across form elements. Inherits all native HTML textarea functionality while
|
|
89
|
+
* providing enhanced visual design and better accessibility patterns.
|
|
15
90
|
*
|
|
16
91
|
* @example
|
|
17
92
|
* ```tsx
|
|
18
93
|
* // Basic usage
|
|
19
|
-
* <Textarea placeholder="Type your message here
|
|
94
|
+
* <Textarea placeholder="Type your message here..." />
|
|
20
95
|
* ```
|
|
21
96
|
*
|
|
22
97
|
* @example
|
|
@@ -28,14 +103,16 @@ import { cn } from "@/lib/utils";
|
|
|
28
103
|
* id="description"
|
|
29
104
|
* placeholder="Enter a description..."
|
|
30
105
|
* rows={4}
|
|
106
|
+
* required
|
|
31
107
|
* />
|
|
32
108
|
* </div>
|
|
33
109
|
* ```
|
|
34
110
|
*
|
|
35
111
|
* @example
|
|
36
112
|
* ```tsx
|
|
37
|
-
* // Controlled with character limit
|
|
113
|
+
* // Controlled with character limit and validation
|
|
38
114
|
* const [bio, setBio] = useState("");
|
|
115
|
+
* const [error, setError] = useState("");
|
|
39
116
|
* const maxLength = 160;
|
|
40
117
|
*
|
|
41
118
|
* <div className="space-y-2">
|
|
@@ -43,43 +120,117 @@ import { cn } from "@/lib/utils";
|
|
|
43
120
|
* <Textarea
|
|
44
121
|
* id="bio"
|
|
45
122
|
* value={bio}
|
|
46
|
-
* onChange={(e) =>
|
|
123
|
+
* onChange={(e) => {
|
|
124
|
+
* setBio(e.target.value);
|
|
125
|
+
* if (e.target.value.length > maxLength) {
|
|
126
|
+
* setError("Bio is too long");
|
|
127
|
+
* } else {
|
|
128
|
+
* setError("");
|
|
129
|
+
* }
|
|
130
|
+
* }}
|
|
47
131
|
* placeholder="Tell us about yourself..."
|
|
48
132
|
* maxLength={maxLength}
|
|
133
|
+
* aria-invalid={!!error}
|
|
134
|
+
* aria-describedby={error ? "bio-error" : "bio-count"}
|
|
49
135
|
* />
|
|
50
|
-
* <
|
|
51
|
-
*
|
|
52
|
-
*
|
|
136
|
+
* <div className="flex justify-between items-center">
|
|
137
|
+
* <p id="bio-count" className="text-sm text-muted-foreground">
|
|
138
|
+
* {bio.length}/{maxLength} characters
|
|
139
|
+
* </p>
|
|
140
|
+
* {error && (
|
|
141
|
+
* <p id="bio-error" className="text-sm text-destructive">
|
|
142
|
+
* {error}
|
|
143
|
+
* </p>
|
|
144
|
+
* )}
|
|
145
|
+
* </div>
|
|
53
146
|
* </div>
|
|
54
147
|
* ```
|
|
55
148
|
*
|
|
56
149
|
* @example
|
|
57
150
|
* ```tsx
|
|
58
|
-
* //
|
|
151
|
+
* // Auto-resizing with minimum height
|
|
59
152
|
* <Textarea
|
|
60
|
-
* placeholder="
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* className="border-destructive"
|
|
153
|
+
* placeholder="This textarea grows with content"
|
|
154
|
+
* className="min-h-[100px] max-h-[300px]"
|
|
155
|
+
* style={{ resize: "vertical" }}
|
|
64
156
|
* />
|
|
65
|
-
* <p id="feedback-error" className="text-sm text-destructive">
|
|
66
|
-
* This field is required
|
|
67
|
-
* </p>
|
|
68
157
|
* ```
|
|
69
158
|
*
|
|
70
|
-
* @
|
|
71
|
-
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```tsx
|
|
161
|
+
* // Form integration with validation
|
|
162
|
+
* <form onSubmit={handleSubmit}>
|
|
163
|
+
* <div className="space-y-2">
|
|
164
|
+
* <Label htmlFor="feedback">Feedback</Label>
|
|
165
|
+
* <Textarea
|
|
166
|
+
* id="feedback"
|
|
167
|
+
* name="feedback"
|
|
168
|
+
* placeholder="Share your thoughts..."
|
|
169
|
+
* required
|
|
170
|
+
* minLength={10}
|
|
171
|
+
* maxLength={500}
|
|
172
|
+
* rows={3}
|
|
173
|
+
* />
|
|
174
|
+
* </div>
|
|
175
|
+
* <Button type="submit">Submit Feedback</Button>
|
|
176
|
+
* </form>
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* // Different wrap behaviors for code/text
|
|
182
|
+
* <div className="space-y-4">
|
|
183
|
+
* <div>
|
|
184
|
+
* <Label>Code (no wrapping)</Label>
|
|
185
|
+
* <Textarea
|
|
186
|
+
* wrap="off"
|
|
187
|
+
* placeholder="Enter code here..."
|
|
188
|
+
* className="font-mono text-sm"
|
|
189
|
+
* style={{ resize: "both" }}
|
|
190
|
+
* />
|
|
191
|
+
* </div>
|
|
192
|
+
* <div>
|
|
193
|
+
* <Label>Text (soft wrapping)</Label>
|
|
194
|
+
* <Textarea
|
|
195
|
+
* wrap="soft"
|
|
196
|
+
* placeholder="Enter text content..."
|
|
197
|
+
* />
|
|
198
|
+
* </div>
|
|
199
|
+
* </div>
|
|
200
|
+
* ```
|
|
72
201
|
*
|
|
73
202
|
* @accessibility
|
|
74
|
-
* -
|
|
75
|
-
* -
|
|
76
|
-
* -
|
|
77
|
-
* -
|
|
78
|
-
* -
|
|
79
|
-
* - Respects disabled and
|
|
80
|
-
* -
|
|
203
|
+
* - Full keyboard navigation with Tab to focus and Shift+Tab to unfocus
|
|
204
|
+
* - Screen reader compatible with proper labeling support
|
|
205
|
+
* - ARIA state management for validation (aria-invalid, aria-describedby)
|
|
206
|
+
* - Supports assistive technology form field recognition
|
|
207
|
+
* - Visible focus indicators with ring outline for keyboard users
|
|
208
|
+
* - Respects disabled and read-only states for interaction prevention
|
|
209
|
+
* - Automatic association with Label components via htmlFor/id pattern
|
|
210
|
+
* - Auto-resizing maintains proper focus and scroll position
|
|
211
|
+
* - Character limits announced to screen readers when using aria-describedby
|
|
212
|
+
* - Spell checking integration with assistive technologies
|
|
213
|
+
*
|
|
214
|
+
* @behavior
|
|
215
|
+
* - Auto-resizing based on content using CSS field-sizing property
|
|
216
|
+
* - Smooth transitions for focus states and validation changes
|
|
217
|
+
* - Native browser resize handle (unless overridden with CSS resize property)
|
|
218
|
+
* - Form validation integration with HTML5 constraint validation API
|
|
219
|
+
* - Clipboard operations (cut, copy, paste) work as expected
|
|
220
|
+
* - Text selection and manipulation using standard keyboard shortcuts
|
|
221
|
+
*
|
|
222
|
+
* @styling
|
|
223
|
+
* - Base styles provide consistent appearance across form elements
|
|
224
|
+
* - Focus-visible styles with ring and border color changes
|
|
225
|
+
* - Error state styling via aria-invalid attribute with destructive colors
|
|
226
|
+
* - Dark mode support with appropriate background and text colors
|
|
227
|
+
* - Shadow and border transitions for interactive feedback
|
|
228
|
+
* - Disabled state with reduced opacity and pointer-events disabled
|
|
81
229
|
*
|
|
230
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea} MDN textarea documentation
|
|
82
231
|
* @see {@link https://ui.shadcn.com/docs/components/textarea} shadcn/ui Textarea documentation
|
|
232
|
+
* @see {@link Label} For proper labeling and accessibility association
|
|
233
|
+
* @see {@link Input} Single-line text input alternative
|
|
83
234
|
* @since 1.0.0
|
|
84
235
|
*/
|
|
85
236
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|