@ceed/ads 1.23.3 → 1.23.4
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/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +368 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
The Select component is a form input that allows users to choose one or multiple options from a predefined list. Built on Joy UI's Select, it provides a dropdown interface for
|
|
5
|
+
The Select component is a form input that allows users to choose one or multiple options from a predefined list. Built on Joy UI's Select, it provides a dropdown interface for picking values within forms, filters, and configuration settings.
|
|
6
|
+
|
|
7
|
+
Select accepts options as an array of objects (with `value`, `label`, and optional `secondaryText` / `disabled` fields) or as simple string/number arrays. It wraps the underlying select element with built-in `FormControl`, `FormLabel`, and `FormHelperText` so you can compose complete form fields with a single component.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Select options={options} />
|
|
@@ -24,20 +26,20 @@ The Select component is a form input that allows users to choose one or multiple
|
|
|
24
26
|
| onChange | — | — |
|
|
25
27
|
| options | — | options |
|
|
26
28
|
|
|
27
|
-
>
|
|
29
|
+
> **Don't confuse Select with Dropdown**
|
|
28
30
|
>
|
|
29
|
-
> - **Select**: For form value selection
|
|
30
|
-
> - **Dropdown**: For menus and action lists
|
|
31
|
+
> - **Select**: For form value selection -- the selected value becomes form data.
|
|
32
|
+
> - **Dropdown**: For menus and action lists -- triggers actions or navigation.
|
|
31
33
|
>
|
|
32
|
-
>
|
|
34
|
+
> Choosing the right component:
|
|
33
35
|
>
|
|
34
|
-
> -
|
|
35
|
-
> - More than 20 options
|
|
36
|
-
> - Actions/navigation
|
|
36
|
+
> - Fewer than 5 options -- consider RadioButton or RadioGroup
|
|
37
|
+
> - More than 20 options -- consider Autocomplete with search
|
|
38
|
+
> - Actions / navigation -- use Dropdown with Menu
|
|
37
39
|
|
|
38
|
-
>
|
|
40
|
+
> **Use built-in form props**
|
|
39
41
|
>
|
|
40
|
-
> This component natively supports
|
|
42
|
+
> This component natively supports `label` and `helperText` props.
|
|
41
43
|
> When building forms, use these built-in props instead of manually composing labels and helper text with Typography.
|
|
42
44
|
|
|
43
45
|
## Usage
|
|
@@ -62,19 +64,9 @@ function MyComponent() {
|
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
### Basic Select
|
|
68
|
-
|
|
69
|
-
The default Select with a list of options.
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
<Select options={options} />
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Variants
|
|
67
|
+
## Variants
|
|
76
68
|
|
|
77
|
-
Select supports
|
|
69
|
+
Select supports four visual styles: `outlined` (default), `plain`, `soft`, and `solid`.
|
|
78
70
|
|
|
79
71
|
```tsx
|
|
80
72
|
<Stack spacing={4}>
|
|
@@ -85,9 +77,16 @@ Select supports different visual styles.
|
|
|
85
77
|
</Stack>
|
|
86
78
|
```
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
```tsx
|
|
81
|
+
<Select options={options} defaultValue="dog" />
|
|
82
|
+
<Select options={options} defaultValue="dog" variant="plain" />
|
|
83
|
+
<Select options={options} defaultValue="dog" variant="soft" />
|
|
84
|
+
<Select options={options} defaultValue="dog" variant="solid" />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Sizes
|
|
89
88
|
|
|
90
|
-
|
|
89
|
+
Three sizes are available: `sm`, `md` (default), and `lg`.
|
|
91
90
|
|
|
92
91
|
```tsx
|
|
93
92
|
<Stack spacing={4}>
|
|
@@ -97,9 +96,15 @@ Select comes in different sizes.
|
|
|
97
96
|
</Stack>
|
|
98
97
|
```
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
```tsx
|
|
100
|
+
<Select options={options} defaultValue="dog" size="sm" />
|
|
101
|
+
<Select options={options} defaultValue="dog" size="md" />
|
|
102
|
+
<Select options={options} defaultValue="dog" size="lg" />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Colors
|
|
101
106
|
|
|
102
|
-
Apply semantic colors to
|
|
107
|
+
Apply semantic colors to communicate intent or state.
|
|
103
108
|
|
|
104
109
|
```tsx
|
|
105
110
|
<Stack spacing={4}>
|
|
@@ -111,9 +116,17 @@ Apply semantic colors to Select.
|
|
|
111
116
|
</Stack>
|
|
112
117
|
```
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
```tsx
|
|
120
|
+
<Select options={options} defaultValue="dog" color="primary" />
|
|
121
|
+
<Select options={options} defaultValue="dog" color="neutral" />
|
|
122
|
+
<Select options={options} defaultValue="dog" color="success" />
|
|
123
|
+
<Select options={options} defaultValue="dog" color="danger" />
|
|
124
|
+
<Select options={options} defaultValue="dog" color="warning" />
|
|
125
|
+
```
|
|
115
126
|
|
|
116
|
-
|
|
127
|
+
## Decorators
|
|
128
|
+
|
|
129
|
+
Use `startDecorator` and `endDecorator` to add icons, badges, or other elements beside the select trigger.
|
|
117
130
|
|
|
118
131
|
```tsx
|
|
119
132
|
<Select placeholder="Select a pet…" startDecorator={<FavoriteBorder />} endDecorator={<Chip size="sm" color="danger" variant="soft">
|
|
@@ -123,9 +136,24 @@ width: 240
|
|
|
123
136
|
}} options={options} />
|
|
124
137
|
```
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
```tsx
|
|
140
|
+
import FavoriteBorder from '@mui/icons-material/FavoriteBorder';
|
|
141
|
+
import { Chip } from '@ceed/ads';
|
|
127
142
|
|
|
128
|
-
Select
|
|
143
|
+
<Select
|
|
144
|
+
placeholder="Select a pet..."
|
|
145
|
+
startDecorator={<FavoriteBorder />}
|
|
146
|
+
endDecorator={
|
|
147
|
+
<Chip size="sm" color="danger" variant="soft">+5</Chip>
|
|
148
|
+
}
|
|
149
|
+
options={options}
|
|
150
|
+
sx={{ width: 240 }}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Label and Helper Text
|
|
155
|
+
|
|
156
|
+
The `label` prop renders a form label above the select. The `helperText` prop renders descriptive text below it. Both are built into the component so you do not need to compose them separately.
|
|
129
157
|
|
|
130
158
|
```tsx
|
|
131
159
|
<>
|
|
@@ -133,19 +161,26 @@ Select with an associated label.
|
|
|
133
161
|
</>
|
|
134
162
|
```
|
|
135
163
|
|
|
136
|
-
### With Helper Text
|
|
137
|
-
|
|
138
|
-
Add helper text for additional context.
|
|
139
|
-
|
|
140
164
|
```tsx
|
|
141
165
|
<>
|
|
142
166
|
<Select label="Select" helperText="I'm helper text" defaultValue="dog" options={options} />
|
|
143
167
|
</>
|
|
144
168
|
```
|
|
145
169
|
|
|
146
|
-
|
|
170
|
+
```tsx
|
|
171
|
+
<Select label="Pet" options={options} defaultValue="dog" />
|
|
147
172
|
|
|
148
|
-
|
|
173
|
+
<Select
|
|
174
|
+
label="Pet"
|
|
175
|
+
helperText="Choose your favorite animal"
|
|
176
|
+
options={options}
|
|
177
|
+
defaultValue="dog"
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Error State
|
|
182
|
+
|
|
183
|
+
Set `error` to `true` to visually indicate a validation error. Combine with `helperText` to display an error message.
|
|
149
184
|
|
|
150
185
|
```tsx
|
|
151
186
|
<>
|
|
@@ -153,9 +188,18 @@ Show validation errors.
|
|
|
153
188
|
</>
|
|
154
189
|
```
|
|
155
190
|
|
|
156
|
-
|
|
191
|
+
```tsx
|
|
192
|
+
<Select
|
|
193
|
+
label="Pet"
|
|
194
|
+
error
|
|
195
|
+
helperText="This field is required"
|
|
196
|
+
options={options}
|
|
197
|
+
/>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Form Control
|
|
157
201
|
|
|
158
|
-
|
|
202
|
+
Combining `label`, `helperText`, and `error` produces a complete form field. Here is a normal field next to one with an error.
|
|
159
203
|
|
|
160
204
|
```tsx
|
|
161
205
|
<>
|
|
@@ -164,9 +208,9 @@ Complete form integration with label and helper text.
|
|
|
164
208
|
</>
|
|
165
209
|
```
|
|
166
210
|
|
|
167
|
-
|
|
211
|
+
## Required Field
|
|
168
212
|
|
|
169
|
-
|
|
213
|
+
Set `required` to mark the field as required. An asterisk is added to the label automatically.
|
|
170
214
|
|
|
171
215
|
```tsx
|
|
172
216
|
<Select
|
|
@@ -177,9 +221,18 @@ Mark the Select as required.
|
|
|
177
221
|
/>
|
|
178
222
|
```
|
|
179
223
|
|
|
180
|
-
|
|
224
|
+
```tsx
|
|
225
|
+
<Select
|
|
226
|
+
label="Pet"
|
|
227
|
+
helperText="This field is required"
|
|
228
|
+
required
|
|
229
|
+
options={options}
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Multiple Selection
|
|
181
234
|
|
|
182
|
-
|
|
235
|
+
Set `multiple` to allow selecting more than one option. The value becomes an array.
|
|
183
236
|
|
|
184
237
|
```tsx
|
|
185
238
|
<Select
|
|
@@ -191,9 +244,18 @@ Allow selecting multiple options.
|
|
|
191
244
|
/>
|
|
192
245
|
```
|
|
193
246
|
|
|
194
|
-
|
|
247
|
+
```tsx
|
|
248
|
+
<Select
|
|
249
|
+
label="Pets"
|
|
250
|
+
multiple
|
|
251
|
+
defaultValue={['dog']}
|
|
252
|
+
options={options}
|
|
253
|
+
/>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Controlled Select
|
|
195
257
|
|
|
196
|
-
|
|
258
|
+
Use the `value` and `onChange` props for controlled behavior when you need to synchronize the selected value with other state.
|
|
197
259
|
|
|
198
260
|
```tsx
|
|
199
261
|
<div>
|
|
@@ -212,9 +274,20 @@ Programmatically control the selected value.
|
|
|
212
274
|
</div>
|
|
213
275
|
```
|
|
214
276
|
|
|
215
|
-
|
|
277
|
+
```tsx
|
|
278
|
+
const [value, setValue] = useState('dog');
|
|
216
279
|
|
|
217
|
-
|
|
280
|
+
<Select
|
|
281
|
+
label="Pet"
|
|
282
|
+
value={value}
|
|
283
|
+
onChange={(event, newValue) => setValue(newValue)}
|
|
284
|
+
options={options}
|
|
285
|
+
/>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Disabled Options
|
|
289
|
+
|
|
290
|
+
Individual options can be disabled by setting `disabled: true` on the option object.
|
|
218
291
|
|
|
219
292
|
```tsx
|
|
220
293
|
<Select
|
|
@@ -227,9 +300,20 @@ Individual options can be disabled.
|
|
|
227
300
|
/>
|
|
228
301
|
```
|
|
229
302
|
|
|
230
|
-
|
|
303
|
+
```tsx
|
|
304
|
+
const options = [
|
|
305
|
+
{ value: 'dog', label: 'Dog', disabled: true },
|
|
306
|
+
{ value: 'cat', label: 'Cat' },
|
|
307
|
+
{ value: 'fish', label: 'Fish', disabled: true },
|
|
308
|
+
{ value: 'bird', label: 'Bird' },
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
<Select label="Pet" options={options} defaultValue="dog" />
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Options with Secondary Text
|
|
231
315
|
|
|
232
|
-
|
|
316
|
+
Add a `secondaryText` field to display supplementary information below each option label. The secondary text size scales proportionally with the component `size`.
|
|
233
317
|
|
|
234
318
|
```tsx
|
|
235
319
|
<Stack spacing={4} direction="row" alignItems="flex-start">
|
|
@@ -245,58 +329,76 @@ Display additional information for each option.
|
|
|
245
329
|
</Stack>
|
|
246
330
|
```
|
|
247
331
|
|
|
248
|
-
|
|
332
|
+
```tsx
|
|
333
|
+
const userOptions = [
|
|
334
|
+
{ value: 'emily', label: 'Emily Carter', secondaryText: '(415) 555-0198' },
|
|
335
|
+
{ value: 'daniel', label: 'Daniel Kim', secondaryText: '(212) 555-0421' },
|
|
336
|
+
{ value: 'sophia', label: 'Sophia Martinez', secondaryText: '(646) 555-0734' },
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
<Select placeholder="Select a contact" options={userOptions} />
|
|
340
|
+
```
|
|
249
341
|
|
|
250
|
-
|
|
342
|
+
## Numeric Values
|
|
251
343
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
344
|
+
Select supports both string and numeric option values. When using numeric values, TypeScript infers the correct type automatically.
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
<Stack spacing={4}>
|
|
348
|
+
<Select defaultValue={1} options={numericOptions} />
|
|
349
|
+
<Select defaultValue={1} variant="plain" options={numericOptions} />
|
|
350
|
+
<Select defaultValue={1} variant="soft" options={numericOptions} />
|
|
351
|
+
<Select defaultValue={1} variant="solid" options={numericOptions} />
|
|
352
|
+
</Stack>
|
|
353
|
+
```
|
|
258
354
|
|
|
259
|
-
|
|
355
|
+
```tsx
|
|
356
|
+
const numericOptions = [
|
|
357
|
+
{ value: 1, label: 'Option 1' },
|
|
358
|
+
{ value: 2, label: 'Option 2' },
|
|
359
|
+
{ value: 3, label: 'Option 3' },
|
|
360
|
+
];
|
|
260
361
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
- **20+ options**: Use Autocomplete with search functionality
|
|
264
|
-
- **Complex selections**: Consider a different pattern if selections involve complex logic
|
|
265
|
-
- **Toggle between states**: Use Switch or Checkbox instead
|
|
362
|
+
<Select options={numericOptions} defaultValue={1} />
|
|
363
|
+
```
|
|
266
364
|
|
|
267
365
|
## Common Use Cases
|
|
268
366
|
|
|
269
|
-
### Form with
|
|
367
|
+
### Form with Validation
|
|
270
368
|
|
|
271
369
|
```tsx
|
|
370
|
+
import { useState } from 'react';
|
|
371
|
+
import { Select, Button, Stack } from '@ceed/ads';
|
|
372
|
+
|
|
272
373
|
function UserForm() {
|
|
273
|
-
const [
|
|
274
|
-
const [
|
|
374
|
+
const [role, setRole] = useState<string | null>(null);
|
|
375
|
+
const [submitted, setSubmitted] = useState(false);
|
|
376
|
+
|
|
377
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
378
|
+
e.preventDefault();
|
|
379
|
+
setSubmitted(true);
|
|
380
|
+
if (role) {
|
|
381
|
+
// submit form
|
|
382
|
+
}
|
|
383
|
+
};
|
|
275
384
|
|
|
276
385
|
return (
|
|
277
386
|
<form onSubmit={handleSubmit}>
|
|
278
387
|
<Stack spacing={2}>
|
|
279
|
-
<Select
|
|
280
|
-
label="Country"
|
|
281
|
-
required
|
|
282
|
-
value={country}
|
|
283
|
-
onChange={(e, val) => setCountry(val)}
|
|
284
|
-
options={countries}
|
|
285
|
-
placeholder="Select your country"
|
|
286
|
-
/>
|
|
287
|
-
|
|
288
388
|
<Select
|
|
289
389
|
label="Role"
|
|
290
390
|
required
|
|
291
391
|
value={role}
|
|
292
392
|
onChange={(e, val) => setRole(val)}
|
|
393
|
+
error={submitted && !role}
|
|
394
|
+
helperText={submitted && !role ? 'Please select a role' : undefined}
|
|
293
395
|
options={[
|
|
294
396
|
{ value: 'admin', label: 'Administrator' },
|
|
295
397
|
{ value: 'editor', label: 'Editor' },
|
|
296
398
|
{ value: 'viewer', label: 'Viewer' },
|
|
297
399
|
]}
|
|
400
|
+
placeholder="Select a role..."
|
|
298
401
|
/>
|
|
299
|
-
|
|
300
402
|
<Button type="submit">Submit</Button>
|
|
301
403
|
</Stack>
|
|
302
404
|
</form>
|
|
@@ -304,9 +406,11 @@ function UserForm() {
|
|
|
304
406
|
}
|
|
305
407
|
```
|
|
306
408
|
|
|
307
|
-
### Filter
|
|
409
|
+
### Filter Bar
|
|
308
410
|
|
|
309
411
|
```tsx
|
|
412
|
+
import { Select, Stack } from '@ceed/ads';
|
|
413
|
+
|
|
310
414
|
function ProductFilter({ onFilterChange }) {
|
|
311
415
|
const [category, setCategory] = useState('all');
|
|
312
416
|
const [sortBy, setSortBy] = useState('newest');
|
|
@@ -326,10 +430,8 @@ function ProductFilter({ onFilterChange }) {
|
|
|
326
430
|
{ value: 'all', label: 'All Categories' },
|
|
327
431
|
{ value: 'electronics', label: 'Electronics' },
|
|
328
432
|
{ value: 'clothing', label: 'Clothing' },
|
|
329
|
-
{ value: 'home', label: 'Home & Garden' },
|
|
330
433
|
]}
|
|
331
434
|
/>
|
|
332
|
-
|
|
333
435
|
<Select
|
|
334
436
|
label="Sort By"
|
|
335
437
|
size="sm"
|
|
@@ -339,7 +441,6 @@ function ProductFilter({ onFilterChange }) {
|
|
|
339
441
|
{ value: 'newest', label: 'Newest First' },
|
|
340
442
|
{ value: 'price-low', label: 'Price: Low to High' },
|
|
341
443
|
{ value: 'price-high', label: 'Price: High to Low' },
|
|
342
|
-
{ value: 'popular', label: 'Most Popular' },
|
|
343
444
|
]}
|
|
344
445
|
/>
|
|
345
446
|
</Stack>
|
|
@@ -347,39 +448,15 @@ function ProductFilter({ onFilterChange }) {
|
|
|
347
448
|
}
|
|
348
449
|
```
|
|
349
450
|
|
|
350
|
-
### Multiple Select with Tags
|
|
351
|
-
|
|
352
|
-
```tsx
|
|
353
|
-
function TagSelect() {
|
|
354
|
-
const [selectedTags, setSelectedTags] = useState([]);
|
|
355
|
-
|
|
356
|
-
const tagOptions = [
|
|
357
|
-
{ value: 'urgent', label: 'Urgent' },
|
|
358
|
-
{ value: 'feature', label: 'Feature' },
|
|
359
|
-
{ value: 'bug', label: 'Bug' },
|
|
360
|
-
{ value: 'enhancement', label: 'Enhancement' },
|
|
361
|
-
{ value: 'documentation', label: 'Documentation' },
|
|
362
|
-
];
|
|
363
|
-
|
|
364
|
-
return (
|
|
365
|
-
<Select
|
|
366
|
-
label="Tags"
|
|
367
|
-
multiple
|
|
368
|
-
value={selectedTags}
|
|
369
|
-
onChange={(e, val) => setSelectedTags(val)}
|
|
370
|
-
options={tagOptions}
|
|
371
|
-
placeholder="Select tags..."
|
|
372
|
-
/>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
451
|
### Dependent Selects
|
|
378
452
|
|
|
379
453
|
```tsx
|
|
454
|
+
import { useState, useMemo } from 'react';
|
|
455
|
+
import { Select, Stack } from '@ceed/ads';
|
|
456
|
+
|
|
380
457
|
function LocationSelect() {
|
|
381
|
-
const [country, setCountry] = useState(
|
|
382
|
-
const [city, setCity] = useState(
|
|
458
|
+
const [country, setCountry] = useState<string | null>(null);
|
|
459
|
+
const [city, setCity] = useState<string | null>(null);
|
|
383
460
|
|
|
384
461
|
const cities = useMemo(() => {
|
|
385
462
|
if (!country) return [];
|
|
@@ -393,269 +470,83 @@ function LocationSelect() {
|
|
|
393
470
|
value={country}
|
|
394
471
|
onChange={(e, val) => {
|
|
395
472
|
setCountry(val);
|
|
396
|
-
setCity(
|
|
473
|
+
setCity(null);
|
|
397
474
|
}}
|
|
398
475
|
options={countries}
|
|
476
|
+
placeholder="Select a country"
|
|
399
477
|
/>
|
|
400
|
-
|
|
401
478
|
<Select
|
|
402
479
|
label="City"
|
|
403
480
|
value={city}
|
|
404
481
|
onChange={(e, val) => setCity(val)}
|
|
405
482
|
options={cities}
|
|
406
483
|
disabled={!country}
|
|
407
|
-
placeholder={country ? 'Select city' : 'Select country first'}
|
|
484
|
+
placeholder={country ? 'Select a city' : 'Select a country first'}
|
|
408
485
|
/>
|
|
409
486
|
</Stack>
|
|
410
487
|
);
|
|
411
488
|
}
|
|
412
489
|
```
|
|
413
490
|
|
|
414
|
-
### With Custom Option Rendering
|
|
415
|
-
|
|
416
|
-
```tsx
|
|
417
|
-
function UserSelect() {
|
|
418
|
-
const userOptions = [
|
|
419
|
-
{ value: 'user1', label: 'John Doe', secondaryText: 'john@example.com' },
|
|
420
|
-
{ value: 'user2', label: 'Jane Smith', secondaryText: 'jane@example.com' },
|
|
421
|
-
{ value: 'user3', label: 'Bob Johnson', secondaryText: 'bob@example.com' },
|
|
422
|
-
];
|
|
423
|
-
|
|
424
|
-
return (
|
|
425
|
-
<Select
|
|
426
|
-
label="Assign to"
|
|
427
|
-
options={userOptions}
|
|
428
|
-
placeholder="Select a user..."
|
|
429
|
-
/>
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
## Props and Customization
|
|
435
|
-
|
|
436
|
-
### Key Props
|
|
437
|
-
|
|
438
|
-
| Prop | Type | Default | Description |
|
|
439
|
-
| -------------- | -------------------------------------------------------------- | ------------ | ------------------------------- |
|
|
440
|
-
| `options` | `Option[]` | `[]` | Array of options to display |
|
|
441
|
-
| `value` | `T \| T[]` | - | Selected value(s) (controlled) |
|
|
442
|
-
| `defaultValue` | `T \| T[]` | - | Default value (uncontrolled) |
|
|
443
|
-
| `onChange` | `function` | - | Callback when selection changes |
|
|
444
|
-
| `multiple` | `boolean` | `false` | Allow multiple selections |
|
|
445
|
-
| `label` | `string` | - | Label text above the Select |
|
|
446
|
-
| `helperText` | `string` | - | Helper text below the Select |
|
|
447
|
-
| `error` | `boolean` | `false` | Show error state |
|
|
448
|
-
| `required` | `boolean` | `false` | Mark as required |
|
|
449
|
-
| `disabled` | `boolean` | `false` | Disable the Select |
|
|
450
|
-
| `placeholder` | `string` | - | Placeholder text |
|
|
451
|
-
| `variant` | `'plain' \| 'outlined' \| 'soft' \| 'solid'` | `'outlined'` | Visual style |
|
|
452
|
-
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | - | Color scheme |
|
|
453
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
|
|
454
|
-
|
|
455
|
-
### Option Type
|
|
456
|
-
|
|
457
|
-
```tsx
|
|
458
|
-
interface Option {
|
|
459
|
-
value: string | number;
|
|
460
|
-
label: string;
|
|
461
|
-
secondaryText?: string;
|
|
462
|
-
disabled?: boolean;
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Numeric Values
|
|
467
|
-
|
|
468
|
-
Select supports both string and numeric values:
|
|
469
|
-
|
|
470
|
-
```tsx
|
|
471
|
-
<Stack spacing={4}>
|
|
472
|
-
<Select defaultValue={1} options={numericOptions} />
|
|
473
|
-
<Select defaultValue={1} variant="plain" options={numericOptions} />
|
|
474
|
-
<Select defaultValue={1} variant="soft" options={numericOptions} />
|
|
475
|
-
<Select defaultValue={1} variant="solid" options={numericOptions} />
|
|
476
|
-
</Stack>
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
```tsx
|
|
480
|
-
const numericOptions = [
|
|
481
|
-
{ value: 1, label: 'Option 1' },
|
|
482
|
-
{ value: 2, label: 'Option 2' },
|
|
483
|
-
{ value: 3, label: 'Option 3' },
|
|
484
|
-
];
|
|
485
|
-
|
|
486
|
-
<Select options={numericOptions} defaultValue={1} />
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### Custom Styling
|
|
490
|
-
|
|
491
|
-
```tsx
|
|
492
|
-
<Select
|
|
493
|
-
options={options}
|
|
494
|
-
sx={{
|
|
495
|
-
minWidth: 200,
|
|
496
|
-
'& .MuiSelect-button': {
|
|
497
|
-
borderRadius: 'xl',
|
|
498
|
-
},
|
|
499
|
-
}}
|
|
500
|
-
/>
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
## Accessibility
|
|
504
|
-
|
|
505
|
-
Select components follow accessibility best practices:
|
|
506
|
-
|
|
507
|
-
### ARIA Attributes
|
|
508
|
-
|
|
509
|
-
- Uses native listbox role with proper ARIA attributes
|
|
510
|
-
- `aria-labelledby` connects to the label
|
|
511
|
-
- `aria-describedby` connects to helper text
|
|
512
|
-
- `aria-required` when required prop is true
|
|
513
|
-
- `aria-invalid` when error prop is true
|
|
514
|
-
|
|
515
|
-
### Keyboard Navigation
|
|
516
|
-
|
|
517
|
-
- **Tab**: Focus the Select
|
|
518
|
-
- **Enter/Space**: Open the dropdown
|
|
519
|
-
- **Arrow Up/Down**: Navigate through options
|
|
520
|
-
- **Home/End**: Jump to first/last option
|
|
521
|
-
- **Enter**: Select the focused option
|
|
522
|
-
- **Escape**: Close the dropdown
|
|
523
|
-
|
|
524
|
-
### Screen Reader Support
|
|
525
|
-
|
|
526
|
-
```tsx
|
|
527
|
-
<Select
|
|
528
|
-
label="Country"
|
|
529
|
-
helperText="Select your country of residence"
|
|
530
|
-
options={countries}
|
|
531
|
-
aria-label="Country selection"
|
|
532
|
-
/>
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### Form Association
|
|
536
|
-
|
|
537
|
-
```tsx
|
|
538
|
-
<FormControl required error={hasError}>
|
|
539
|
-
<FormLabel>Country</FormLabel>
|
|
540
|
-
<Select options={countries} />
|
|
541
|
-
<FormHelperText>{hasError ? 'Please select a country' : 'Required field'}</FormHelperText>
|
|
542
|
-
</FormControl>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
491
|
## Best Practices
|
|
546
492
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
1. **Use clear labels**: Labels should clearly describe what the user is selecting
|
|
493
|
+
1. **Use clear, specific labels.** The label should describe exactly what the user is choosing.
|
|
550
494
|
|
|
551
495
|
```tsx
|
|
552
|
-
// ✅ Good
|
|
496
|
+
// ✅ Good
|
|
553
497
|
<Select label="Preferred contact method" options={contactMethods} />
|
|
554
498
|
|
|
555
|
-
// ❌ Bad
|
|
499
|
+
// ❌ Bad
|
|
556
500
|
<Select label="Select" options={contactMethods} />
|
|
557
501
|
```
|
|
558
502
|
|
|
559
|
-
2. **Provide meaningful option
|
|
503
|
+
2. **Provide meaningful option text.** Each option label should be immediately understandable.
|
|
560
504
|
|
|
561
505
|
```tsx
|
|
562
|
-
// ✅ Good
|
|
506
|
+
// ✅ Good
|
|
563
507
|
{ value: 'express', label: 'Express Shipping (1-2 days)' }
|
|
564
508
|
|
|
565
|
-
// ❌ Bad
|
|
509
|
+
// ❌ Bad
|
|
566
510
|
{ value: 'exp', label: 'EXP' }
|
|
567
511
|
```
|
|
568
512
|
|
|
569
|
-
3. **
|
|
570
|
-
|
|
571
|
-
4. **Include a placeholder**: Help users understand what to select
|
|
572
|
-
|
|
573
|
-
```tsx
|
|
574
|
-
<Select placeholder="Select a country..." options={countries} />
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
5. **Use helper text**: Provide additional context when needed
|
|
513
|
+
3. **Do not use Select for actions.** Select is for data input. For action menus (Edit, Delete, etc.), use Dropdown with Menu.
|
|
578
514
|
|
|
579
515
|
```tsx
|
|
516
|
+
// ✅ Good: Select for data
|
|
580
517
|
<Select
|
|
581
|
-
label="
|
|
582
|
-
|
|
583
|
-
options={timezones}
|
|
518
|
+
label="Country"
|
|
519
|
+
options={countries}
|
|
584
520
|
/>
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
### ❌ Don't
|
|
588
|
-
|
|
589
|
-
1. **Don't use for actions**: Select is for data, not navigation or actions
|
|
590
|
-
|
|
591
|
-
```tsx
|
|
592
|
-
// ❌ Bad: Using Select for actions
|
|
593
|
-
<Select options={[
|
|
594
|
-
{ value: 'edit', label: 'Edit' },
|
|
595
|
-
{ value: 'delete', label: 'Delete' },
|
|
596
|
-
]} />
|
|
597
|
-
|
|
598
|
-
// ✅ Good: Use Dropdown for actions
|
|
599
|
-
<Dropdown>
|
|
600
|
-
<MenuButton>Actions</MenuButton>
|
|
601
|
-
<Menu>
|
|
602
|
-
<MenuItem onClick={handleEdit}>Edit</MenuItem>
|
|
603
|
-
<MenuItem onClick={handleDelete}>Delete</MenuItem>
|
|
604
|
-
</Menu>
|
|
605
|
-
</Dropdown>
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
2. **Don't disable without explanation**: If options are unavailable, explain why
|
|
609
|
-
|
|
610
|
-
3. **Don't use with very few options**: Consider RadioGroup for 2-4 options
|
|
611
|
-
|
|
612
|
-
4. **Don't use with many options without search**: Use Autocomplete for 20+ options
|
|
613
|
-
|
|
614
|
-
## Performance Considerations
|
|
615
521
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
3. **Lazy loading**: Load options on demand
|
|
623
|
-
|
|
624
|
-
```tsx
|
|
625
|
-
// For many options, use Autocomplete instead
|
|
626
|
-
<Autocomplete
|
|
627
|
-
options={largeOptionList}
|
|
628
|
-
getOptionLabel={(option) => option.label}
|
|
629
|
-
renderInput={(params) => <Input {...params} label="Search..." />}
|
|
522
|
+
// ❌ Bad: Select for actions
|
|
523
|
+
<Select
|
|
524
|
+
options={[
|
|
525
|
+
{ value: 'edit', label: 'Edit' },
|
|
526
|
+
{ value: 'delete', label: 'Delete' },
|
|
527
|
+
]}
|
|
630
528
|
/>
|
|
631
529
|
```
|
|
632
530
|
|
|
633
|
-
|
|
531
|
+
4. **Choose the right component for the option count.** For 2--4 visible choices, use RadioGroup. For 20+ options that need searching, use Autocomplete. Select works best with roughly 5--20 options.
|
|
634
532
|
|
|
635
|
-
Use
|
|
533
|
+
5. **Use the built-in `label` and `helperText` props** instead of manually wrapping Select with FormControl, FormLabel, and FormHelperText. The component handles the composition and accessibility wiring internally.
|
|
636
534
|
|
|
637
535
|
```tsx
|
|
638
|
-
//
|
|
639
|
-
<Select
|
|
536
|
+
// ✅ Good
|
|
537
|
+
<Select label="Timezone" helperText="Used for report scheduling" options={timezones} />
|
|
640
538
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
<
|
|
539
|
+
// ❌ Bad
|
|
540
|
+
<FormControl>
|
|
541
|
+
<FormLabel>Timezone</FormLabel>
|
|
542
|
+
<Select options={timezones} />
|
|
543
|
+
<FormHelperText>Used for report scheduling</FormHelperText>
|
|
544
|
+
</FormControl>
|
|
644
545
|
```
|
|
645
546
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
When options are computed, memoize them:
|
|
649
|
-
|
|
650
|
-
```tsx
|
|
651
|
-
const options = useMemo(() =>
|
|
652
|
-
data.map(item => ({
|
|
653
|
-
value: item.id,
|
|
654
|
-
label: item.name,
|
|
655
|
-
})),
|
|
656
|
-
[data]);
|
|
657
|
-
|
|
658
|
-
<Select options={options} />
|
|
659
|
-
```
|
|
547
|
+
## Accessibility
|
|
660
548
|
|
|
661
|
-
|
|
549
|
+
- **Label association**: When you use the `label` prop, the component automatically connects the label to the select element via `aria-labelledby`. Always provide a label for screen reader users.
|
|
550
|
+
- **Keyboard navigation**: The select can be opened with Enter or Space, navigated with Arrow Up/Down and Home/End, confirmed with Enter, and dismissed with Escape.
|
|
551
|
+
- **Error announcement**: When `error` is set, `aria-invalid` is applied. Pair it with a descriptive `helperText` so assistive technology can announce the error reason.
|
|
552
|
+
- **Required state**: The `required` prop adds `aria-required` and a visible asterisk to the label, communicating the requirement both visually and programmatically.
|