@ceed/ads 1.20.0 → 1.20.1

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.
@@ -2,6 +2,8 @@
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 selecting values within forms. Select is designed for data input scenarios where users need to pick from a set of options, making it ideal for forms, filters, and configuration settings.
6
+
5
7
  ```tsx
6
8
  <Select options={options} />
7
9
  ```
@@ -22,8 +24,53 @@
22
24
  | onChange | — | — |
23
25
  | options | — | options |
24
26
 
27
+ > ⚠️ **Don't Confuse Select with Dropdown** ⚠️
28
+ >
29
+ > - **Select**: For form value selection - the selected value becomes form data
30
+ > - **Dropdown**: For menus and action lists - triggers actions or navigation
31
+ >
32
+ > Choose the right component:
33
+ >
34
+ > - Less than 5 options → Consider RadioButton or RadioGroup
35
+ > - More than 20 options → Consider Autocomplete with search
36
+ > - Actions/navigation → Use Dropdown with Menu
37
+
38
+ ## Usage
39
+
40
+ ```tsx
41
+ import { Select } from '@ceed/ads';
42
+
43
+ const options = [
44
+ { value: 'dog', label: 'Dog' },
45
+ { value: 'cat', label: 'Cat' },
46
+ { value: 'fish', label: 'Fish' },
47
+ ];
48
+
49
+ function MyComponent() {
50
+ return (
51
+ <Select
52
+ label="Choose a pet"
53
+ options={options}
54
+ defaultValue="dog"
55
+ />
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## Examples
61
+
62
+ ### Basic Select
63
+
64
+ The default Select with a list of options.
65
+
66
+ ```tsx
67
+ <Select options={options} />
68
+ ```
69
+
25
70
  ### Variants
26
71
 
72
+ Select supports different visual styles.
73
+
27
74
  ```tsx
28
75
  <Stack spacing={4}>
29
76
  <Select defaultValue="dog" options={options} />
@@ -35,6 +82,8 @@
35
82
 
36
83
  ### Sizes
37
84
 
85
+ Select comes in different sizes.
86
+
38
87
  ```tsx
39
88
  <Stack spacing={4}>
40
89
  <Select defaultValue="dog" size="sm" options={options} />
@@ -45,6 +94,8 @@
45
94
 
46
95
  ### Colors
47
96
 
97
+ Apply semantic colors to Select.
98
+
48
99
  ```tsx
49
100
  <Stack spacing={4}>
50
101
  <Select defaultValue="dog" color="primary" options={options} />
@@ -54,3 +105,552 @@
54
105
  <Select defaultValue="dog" color="warning" options={options} />
55
106
  </Stack>
56
107
  ```
108
+
109
+ ### With Decorators
110
+
111
+ Add icons or badges to the Select.
112
+
113
+ ```tsx
114
+ <Select placeholder="Select a pet…" startDecorator={<FavoriteBorder />} endDecorator={<Chip size="sm" color="danger" variant="soft">
115
+ +5
116
+ </Chip>} sx={{
117
+ width: 240
118
+ }} options={options} />
119
+ ```
120
+
121
+ ### With Label
122
+
123
+ Select with an associated label.
124
+
125
+ ```tsx
126
+ <>
127
+ <Select label="Label" defaultValue="dog" options={options} />
128
+ </>
129
+ ```
130
+
131
+ ### With Helper Text
132
+
133
+ Add helper text for additional context.
134
+
135
+ ```tsx
136
+ <>
137
+ <Select label="Select" helperText="I'm helper text" defaultValue="dog" options={options} />
138
+ </>
139
+ ```
140
+
141
+ ### Error State
142
+
143
+ Show validation errors.
144
+
145
+ ```tsx
146
+ <>
147
+ <Select label="label" error defaultValue="dog" options={options} />
148
+ </>
149
+ ```
150
+
151
+ ### Form Control
152
+
153
+ Complete form integration with label and helper text.
154
+
155
+ ```tsx
156
+ <>
157
+ <Select label="Label" helperText="I'm helper text" defaultValue="dog" options={options} />
158
+ <Select label="Label" helperText="I'm helper text" defaultValue="dog" error options={options} />
159
+ </>
160
+ ```
161
+
162
+ ### Required Field
163
+
164
+ Mark the Select as required.
165
+
166
+ ```tsx
167
+ <Select
168
+ options={options}
169
+ label="Label"
170
+ helperText="I'm helper text"
171
+ required
172
+ />
173
+ ```
174
+
175
+ ### Multiple Selection
176
+
177
+ Allow selecting multiple options.
178
+
179
+ ```tsx
180
+ <Select
181
+ options={options}
182
+ label="Label"
183
+ helperText="I'm helper text"
184
+ defaultValue={['dog']}
185
+ multiple
186
+ />
187
+ ```
188
+
189
+ ### Controlled Select
190
+
191
+ Programmatically control the selected value.
192
+
193
+ ```tsx
194
+ <div>
195
+ <Select {...args} options={options} value={value} onChange={(event, newValue) => {
196
+ setValue(newValue);
197
+ args.onChange?.(event, newValue);
198
+ }} />
199
+ <Select {...args} options={['dog', 'cat', 'fish', 'bird'] as string[]} value={value} onChange={(event, newValue) => {
200
+ setValue(newValue);
201
+ args.onChange?.(event, newValue);
202
+ }} />
203
+ <Select {...args} options={['dog', 'cat', 'fish', 'bird'] as string[]} value={[value]} onChange={(event, newValue) => {
204
+ setValue(newValue);
205
+ args.onChange?.(event, newValue);
206
+ }} multiple />
207
+ </div>
208
+ ```
209
+
210
+ ### Disabled Options
211
+
212
+ Individual options can be disabled.
213
+
214
+ ```tsx
215
+ <Select
216
+ options={options.map((option, idx) => ({
217
+ ...option,
218
+ disabled: idx % 2 === 0
219
+ }))}
220
+ label="Disabled Options"
221
+ defaultValue="dog"
222
+ />
223
+ ```
224
+
225
+ ### Options with Secondary Text
226
+
227
+ Display additional information for each option.
228
+
229
+ ```tsx
230
+ <Stack spacing={4} direction="row" alignItems="flex-start">
231
+ {sizes.map(size => <Stack key={size} spacing={1}>
232
+ <span style={{
233
+ color: '#6366f1',
234
+ fontSize: 12
235
+ }}>{size}</span>
236
+ <Select placeholder="Placeholder" options={optionsWithSecondaryText} sx={{
237
+ minWidth: 200
238
+ }} size={size} />
239
+ </Stack>)}
240
+ </Stack>
241
+ ```
242
+
243
+ ## When to Use
244
+
245
+ ### ✅ Good Use Cases
246
+
247
+ - **Forms**: When users need to select a value that will be submitted as form data
248
+ - **Filters**: Selecting filter criteria for lists or search results
249
+ - **Settings**: Configuration options where user needs to pick one value
250
+ - **Data entry**: Standardized input where free text isn't appropriate
251
+ - **5-20 options**: Ideal number of choices for a Select
252
+ - **Single source of truth**: When the selected value should be clearly visible
253
+
254
+ ### ❌ When Not to Use
255
+
256
+ - **Actions/navigation**: Use Dropdown with Menu instead
257
+ - **2-4 options visible at once**: Consider RadioGroup for better visibility
258
+ - **20+ options**: Use Autocomplete with search functionality
259
+ - **Complex selections**: Consider a different pattern if selections involve complex logic
260
+ - **Toggle between states**: Use Switch or Checkbox instead
261
+
262
+ ## Common Use Cases
263
+
264
+ ### Form with Select
265
+
266
+ ```tsx
267
+ function UserForm() {
268
+ const [country, setCountry] = useState('');
269
+ const [role, setRole] = useState('');
270
+
271
+ return (
272
+ <form onSubmit={handleSubmit}>
273
+ <Stack spacing={2}>
274
+ <Select
275
+ label="Country"
276
+ required
277
+ value={country}
278
+ onChange={(e, val) => setCountry(val)}
279
+ options={countries}
280
+ placeholder="Select your country"
281
+ />
282
+
283
+ <Select
284
+ label="Role"
285
+ required
286
+ value={role}
287
+ onChange={(e, val) => setRole(val)}
288
+ options={[
289
+ { value: 'admin', label: 'Administrator' },
290
+ { value: 'editor', label: 'Editor' },
291
+ { value: 'viewer', label: 'Viewer' },
292
+ ]}
293
+ />
294
+
295
+ <Button type="submit">Submit</Button>
296
+ </Stack>
297
+ </form>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ### Filter Select
303
+
304
+ ```tsx
305
+ function ProductFilter({ onFilterChange }) {
306
+ const [category, setCategory] = useState('all');
307
+ const [sortBy, setSortBy] = useState('newest');
308
+
309
+ useEffect(() => {
310
+ onFilterChange({ category, sortBy });
311
+ }, [category, sortBy]);
312
+
313
+ return (
314
+ <Stack direction="row" spacing={2}>
315
+ <Select
316
+ label="Category"
317
+ size="sm"
318
+ value={category}
319
+ onChange={(e, val) => setCategory(val)}
320
+ options={[
321
+ { value: 'all', label: 'All Categories' },
322
+ { value: 'electronics', label: 'Electronics' },
323
+ { value: 'clothing', label: 'Clothing' },
324
+ { value: 'home', label: 'Home & Garden' },
325
+ ]}
326
+ />
327
+
328
+ <Select
329
+ label="Sort By"
330
+ size="sm"
331
+ value={sortBy}
332
+ onChange={(e, val) => setSortBy(val)}
333
+ options={[
334
+ { value: 'newest', label: 'Newest First' },
335
+ { value: 'price-low', label: 'Price: Low to High' },
336
+ { value: 'price-high', label: 'Price: High to Low' },
337
+ { value: 'popular', label: 'Most Popular' },
338
+ ]}
339
+ />
340
+ </Stack>
341
+ );
342
+ }
343
+ ```
344
+
345
+ ### Multiple Select with Tags
346
+
347
+ ```tsx
348
+ function TagSelect() {
349
+ const [selectedTags, setSelectedTags] = useState([]);
350
+
351
+ const tagOptions = [
352
+ { value: 'urgent', label: 'Urgent' },
353
+ { value: 'feature', label: 'Feature' },
354
+ { value: 'bug', label: 'Bug' },
355
+ { value: 'enhancement', label: 'Enhancement' },
356
+ { value: 'documentation', label: 'Documentation' },
357
+ ];
358
+
359
+ return (
360
+ <Select
361
+ label="Tags"
362
+ multiple
363
+ value={selectedTags}
364
+ onChange={(e, val) => setSelectedTags(val)}
365
+ options={tagOptions}
366
+ placeholder="Select tags..."
367
+ />
368
+ );
369
+ }
370
+ ```
371
+
372
+ ### Dependent Selects
373
+
374
+ ```tsx
375
+ function LocationSelect() {
376
+ const [country, setCountry] = useState('');
377
+ const [city, setCity] = useState('');
378
+
379
+ const cities = useMemo(() => {
380
+ if (!country) return [];
381
+ return getCitiesByCountry(country);
382
+ }, [country]);
383
+
384
+ return (
385
+ <Stack spacing={2}>
386
+ <Select
387
+ label="Country"
388
+ value={country}
389
+ onChange={(e, val) => {
390
+ setCountry(val);
391
+ setCity(''); // Reset city when country changes
392
+ }}
393
+ options={countries}
394
+ />
395
+
396
+ <Select
397
+ label="City"
398
+ value={city}
399
+ onChange={(e, val) => setCity(val)}
400
+ options={cities}
401
+ disabled={!country}
402
+ placeholder={country ? 'Select city' : 'Select country first'}
403
+ />
404
+ </Stack>
405
+ );
406
+ }
407
+ ```
408
+
409
+ ### With Custom Option Rendering
410
+
411
+ ```tsx
412
+ function UserSelect() {
413
+ const userOptions = [
414
+ { value: 'user1', label: 'John Doe', secondaryText: 'john@example.com' },
415
+ { value: 'user2', label: 'Jane Smith', secondaryText: 'jane@example.com' },
416
+ { value: 'user3', label: 'Bob Johnson', secondaryText: 'bob@example.com' },
417
+ ];
418
+
419
+ return (
420
+ <Select
421
+ label="Assign to"
422
+ options={userOptions}
423
+ placeholder="Select a user..."
424
+ />
425
+ );
426
+ }
427
+ ```
428
+
429
+ ## Props and Customization
430
+
431
+ ### Key Props
432
+
433
+ | Prop | Type | Default | Description |
434
+ | -------------- | -------------------------------------------------------------- | ------------ | ------------------------------- |
435
+ | `options` | `Option[]` | `[]` | Array of options to display |
436
+ | `value` | `T \| T[]` | - | Selected value(s) (controlled) |
437
+ | `defaultValue` | `T \| T[]` | - | Default value (uncontrolled) |
438
+ | `onChange` | `function` | - | Callback when selection changes |
439
+ | `multiple` | `boolean` | `false` | Allow multiple selections |
440
+ | `label` | `string` | - | Label text above the Select |
441
+ | `helperText` | `string` | - | Helper text below the Select |
442
+ | `error` | `boolean` | `false` | Show error state |
443
+ | `required` | `boolean` | `false` | Mark as required |
444
+ | `disabled` | `boolean` | `false` | Disable the Select |
445
+ | `placeholder` | `string` | - | Placeholder text |
446
+ | `variant` | `'plain' \| 'outlined' \| 'soft' \| 'solid'` | `'outlined'` | Visual style |
447
+ | `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | - | Color scheme |
448
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
449
+
450
+ ### Option Type
451
+
452
+ ```tsx
453
+ interface Option {
454
+ value: string | number;
455
+ label: string;
456
+ secondaryText?: string;
457
+ disabled?: boolean;
458
+ }
459
+ ```
460
+
461
+ ### Numeric Values
462
+
463
+ Select supports both string and numeric values:
464
+
465
+ ```tsx
466
+ <Stack spacing={4}>
467
+ <Select defaultValue={1} options={numericOptions} />
468
+ <Select defaultValue={1} variant="plain" options={numericOptions} />
469
+ <Select defaultValue={1} variant="soft" options={numericOptions} />
470
+ <Select defaultValue={1} variant="solid" options={numericOptions} />
471
+ </Stack>
472
+ ```
473
+
474
+ ```tsx
475
+ const numericOptions = [
476
+ { value: 1, label: 'Option 1' },
477
+ { value: 2, label: 'Option 2' },
478
+ { value: 3, label: 'Option 3' },
479
+ ];
480
+
481
+ <Select options={numericOptions} defaultValue={1} />
482
+ ```
483
+
484
+ ### Custom Styling
485
+
486
+ ```tsx
487
+ <Select
488
+ options={options}
489
+ sx={{
490
+ minWidth: 200,
491
+ '& .MuiSelect-button': {
492
+ borderRadius: 'xl',
493
+ },
494
+ }}
495
+ />
496
+ ```
497
+
498
+ ## Accessibility
499
+
500
+ Select components follow accessibility best practices:
501
+
502
+ ### ARIA Attributes
503
+
504
+ - Uses native listbox role with proper ARIA attributes
505
+ - `aria-labelledby` connects to the label
506
+ - `aria-describedby` connects to helper text
507
+ - `aria-required` when required prop is true
508
+ - `aria-invalid` when error prop is true
509
+
510
+ ### Keyboard Navigation
511
+
512
+ - **Tab**: Focus the Select
513
+ - **Enter/Space**: Open the dropdown
514
+ - **Arrow Up/Down**: Navigate through options
515
+ - **Home/End**: Jump to first/last option
516
+ - **Enter**: Select the focused option
517
+ - **Escape**: Close the dropdown
518
+
519
+ ### Screen Reader Support
520
+
521
+ ```tsx
522
+ <Select
523
+ label="Country"
524
+ helperText="Select your country of residence"
525
+ options={countries}
526
+ aria-label="Country selection"
527
+ />
528
+ ```
529
+
530
+ ### Form Association
531
+
532
+ ```tsx
533
+ <FormControl required error={hasError}>
534
+ <FormLabel>Country</FormLabel>
535
+ <Select options={countries} />
536
+ <FormHelperText>{hasError ? 'Please select a country' : 'Required field'}</FormHelperText>
537
+ </FormControl>
538
+ ```
539
+
540
+ ## Best Practices
541
+
542
+ ### ✅ Do
543
+
544
+ 1. **Use clear labels**: Labels should clearly describe what the user is selecting
545
+
546
+ ```tsx
547
+ // ✅ Good: Clear, specific label
548
+ <Select label="Preferred contact method" options={contactMethods} />
549
+
550
+ // ❌ Bad: Vague label
551
+ <Select label="Select" options={contactMethods} />
552
+ ```
553
+
554
+ 2. **Provide meaningful option labels**: Options should be easy to understand
555
+
556
+ ```tsx
557
+ // ✅ Good: Descriptive options
558
+ { value: 'express', label: 'Express Shipping (1-2 days)' }
559
+
560
+ // ❌ Bad: Cryptic options
561
+ { value: 'exp', label: 'EXP' }
562
+ ```
563
+
564
+ 3. **Order options logically**: Alphabetically, by frequency, or by importance
565
+
566
+ 4. **Include a placeholder**: Help users understand what to select
567
+
568
+ ```tsx
569
+ <Select placeholder="Select a country..." options={countries} />
570
+ ```
571
+
572
+ 5. **Use helper text**: Provide additional context when needed
573
+
574
+ ```tsx
575
+ <Select
576
+ label="Timezone"
577
+ helperText="Select the timezone for your reports"
578
+ options={timezones}
579
+ />
580
+ ```
581
+
582
+ ### ❌ Don't
583
+
584
+ 1. **Don't use for actions**: Select is for data, not navigation or actions
585
+
586
+ ```tsx
587
+ // ❌ Bad: Using Select for actions
588
+ <Select options={[
589
+ { value: 'edit', label: 'Edit' },
590
+ { value: 'delete', label: 'Delete' },
591
+ ]} />
592
+
593
+ // ✅ Good: Use Dropdown for actions
594
+ <Dropdown>
595
+ <MenuButton>Actions</MenuButton>
596
+ <Menu>
597
+ <MenuItem onClick={handleEdit}>Edit</MenuItem>
598
+ <MenuItem onClick={handleDelete}>Delete</MenuItem>
599
+ </Menu>
600
+ </Dropdown>
601
+ ```
602
+
603
+ 2. **Don't disable without explanation**: If options are unavailable, explain why
604
+
605
+ 3. **Don't use with very few options**: Consider RadioGroup for 2-4 options
606
+
607
+ 4. **Don't use with many options without search**: Use Autocomplete for 20+ options
608
+
609
+ ## Performance Considerations
610
+
611
+ ### Large Option Lists
612
+
613
+ For lists with many options, consider:
614
+
615
+ 1. **Pagination or virtual scrolling**: For very large lists
616
+ 2. **Autocomplete**: When users need to search through options
617
+ 3. **Lazy loading**: Load options on demand
618
+
619
+ ```tsx
620
+ // For many options, use Autocomplete instead
621
+ <Autocomplete
622
+ options={largeOptionList}
623
+ getOptionLabel={(option) => option.label}
624
+ renderInput={(params) => <Input {...params} label="Search..." />}
625
+ />
626
+ ```
627
+
628
+ ### Controlled vs Uncontrolled
629
+
630
+ Use controlled mode only when necessary:
631
+
632
+ ```tsx
633
+ // Uncontrolled - simpler, better performance
634
+ <Select defaultValue="option1" options={options} />
635
+
636
+ // Controlled - when you need to sync with other state
637
+ const [value, setValue] = useState('option1');
638
+ <Select value={value} onChange={(e, val) => setValue(val)} options={options} />
639
+ ```
640
+
641
+ ### Memoize Options
642
+
643
+ When options are computed, memoize them:
644
+
645
+ ```tsx
646
+ const options = useMemo(() =>
647
+ data.map(item => ({
648
+ value: item.id,
649
+ label: item.name,
650
+ })),
651
+ [data]);
652
+
653
+ <Select options={options} />
654
+ ```
655
+
656
+ Select is a fundamental form component that provides a clean interface for choosing from predefined options. Use it appropriately to create intuitive forms and ensure users can easily make selections.