@telegraph/select 0.0.55 → 0.0.56
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/CHANGELOG.md +7 -0
- package/README.md +677 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,17 +1,687 @@
|
|
|
1
|
-
|
|
1
|
+
# 📋 Select
|
|
2
|
+
|
|
3
|
+
> Simplified dropdown select component built on Telegraph's Combobox with streamlined API for common selection patterns.
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+

|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@telegraph/select)
|
|
8
|
+
[](https://bundlephobia.com/result?p=@telegraph/select)
|
|
9
|
+
[](https://github.com/knocklabs/telegraph/blob/main/LICENSE)
|
|
7
10
|
|
|
8
|
-
## Installation
|
|
11
|
+
## Installation
|
|
9
12
|
|
|
10
|
-
```
|
|
13
|
+
```bash
|
|
11
14
|
npm install @telegraph/select
|
|
12
15
|
```
|
|
13
16
|
|
|
14
17
|
### Add stylesheet
|
|
18
|
+
|
|
19
|
+
Pick one:
|
|
20
|
+
|
|
21
|
+
Via CSS (preferred):
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
@import "@telegraph/select";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Via Javascript:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import "@telegraph/select/default.css";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> Then, include `className="tgph"` on the farthest parent element wrapping the telegraph components
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Select } from "@telegraph/select";
|
|
39
|
+
|
|
40
|
+
export const CountrySelector = () => {
|
|
41
|
+
const [country, setCountry] = useState("us");
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Select.Root value={country} onValueChange={setCountry}>
|
|
45
|
+
<Select.Option value="us">United States</Select.Option>
|
|
46
|
+
<Select.Option value="ca">Canada</Select.Option>
|
|
47
|
+
<Select.Option value="mx">Mexico</Select.Option>
|
|
48
|
+
<Select.Option value="uk">United Kingdom</Select.Option>
|
|
49
|
+
<Select.Option value="de">Germany</Select.Option>
|
|
50
|
+
</Select.Root>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## When to Use Select vs Combobox
|
|
56
|
+
|
|
57
|
+
**Use Select when:**
|
|
58
|
+
|
|
59
|
+
- Simple dropdown selection
|
|
60
|
+
- Predefined list of options
|
|
61
|
+
- No search/filtering needed
|
|
62
|
+
- Standard selection patterns
|
|
63
|
+
|
|
64
|
+
**Use Combobox when:**
|
|
65
|
+
|
|
66
|
+
- Need search functionality
|
|
67
|
+
- Large option lists
|
|
68
|
+
- Custom option rendering
|
|
69
|
+
- Advanced filtering
|
|
70
|
+
- Multi-select with complex UI
|
|
71
|
+
|
|
72
|
+
## API Reference
|
|
73
|
+
|
|
74
|
+
### `<Select.Root>`
|
|
75
|
+
|
|
76
|
+
The main select container component.
|
|
77
|
+
|
|
78
|
+
| Prop | Type | Default | Description |
|
|
79
|
+
| --------------- | ------------------------------------- | ------------- | --------------------------------------- |
|
|
80
|
+
| `value` | `string \| string[]` | `undefined` | Selected value(s) |
|
|
81
|
+
| `onValueChange` | `(value: string \| string[]) => void` | `undefined` | Called when selection changes |
|
|
82
|
+
| `size` | `"0" \| "1" \| "2" \| "3" \| "4"` | `"1"` | Size of the trigger button |
|
|
83
|
+
| `placeholder` | `string` | `"Select..."` | Placeholder text when no value selected |
|
|
84
|
+
| `disabled` | `boolean` | `false` | Whether the select is disabled |
|
|
85
|
+
| `triggerProps` | `ComboboxTriggerProps` | `{}` | Props passed to the trigger button |
|
|
86
|
+
| `contentProps` | `ComboboxContentProps` | `{}` | Props passed to the dropdown content |
|
|
87
|
+
| `optionsProps` | `ComboboxOptionsProps` | `{}` | Props passed to the options container |
|
|
88
|
+
|
|
89
|
+
### `<Select.Option>`
|
|
90
|
+
|
|
91
|
+
Individual option within the select dropdown.
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
| ---------- | ----------- | ------- | ----------------------------------- |
|
|
95
|
+
| `value` | `string` | - | Unique value for this option |
|
|
96
|
+
| `disabled` | `boolean` | `false` | Whether this option is disabled |
|
|
97
|
+
| `children` | `ReactNode` | - | Display text/content for the option |
|
|
98
|
+
|
|
99
|
+
## Usage Patterns
|
|
100
|
+
|
|
101
|
+
### Basic Selection
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { Select } from "@telegraph/select";
|
|
105
|
+
|
|
106
|
+
export const LanguageSelector = () => {
|
|
107
|
+
const [language, setLanguage] = useState("en");
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Select.Root
|
|
111
|
+
value={language}
|
|
112
|
+
onValueChange={setLanguage}
|
|
113
|
+
placeholder="Choose language"
|
|
114
|
+
>
|
|
115
|
+
<Select.Option value="en">English</Select.Option>
|
|
116
|
+
<Select.Option value="es">Spanish</Select.Option>
|
|
117
|
+
<Select.Option value="fr">French</Select.Option>
|
|
118
|
+
<Select.Option value="de">German</Select.Option>
|
|
119
|
+
</Select.Root>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Multiple Selection
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { Select } from "@telegraph/select";
|
|
128
|
+
|
|
129
|
+
export const SkillSelector = () => {
|
|
130
|
+
const [skills, setSkills] = useState([]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Select.Root
|
|
134
|
+
value={skills}
|
|
135
|
+
onValueChange={setSkills}
|
|
136
|
+
placeholder="Select skills"
|
|
137
|
+
>
|
|
138
|
+
<Select.Option value="javascript">JavaScript</Select.Option>
|
|
139
|
+
<Select.Option value="typescript">TypeScript</Select.Option>
|
|
140
|
+
<Select.Option value="react">React</Select.Option>
|
|
141
|
+
<Select.Option value="vue">Vue</Select.Option>
|
|
142
|
+
<Select.Option value="angular">Angular</Select.Option>
|
|
143
|
+
</Select.Root>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Different Sizes
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Select } from "@telegraph/select";
|
|
152
|
+
|
|
153
|
+
export const SizedSelects = () => (
|
|
154
|
+
<div>
|
|
155
|
+
{/* Small */}
|
|
156
|
+
<Select.Root size="0" value="" onValueChange={console.log}>
|
|
157
|
+
<Select.Option value="small">Small Option</Select.Option>
|
|
158
|
+
</Select.Root>
|
|
159
|
+
|
|
160
|
+
{/* Medium (default) */}
|
|
161
|
+
<Select.Root size="1" value="" onValueChange={console.log}>
|
|
162
|
+
<Select.Option value="medium">Medium Option</Select.Option>
|
|
163
|
+
</Select.Root>
|
|
164
|
+
|
|
165
|
+
{/* Large */}
|
|
166
|
+
<Select.Root size="3" value="" onValueChange={console.log}>
|
|
167
|
+
<Select.Option value="large">Large Option</Select.Option>
|
|
168
|
+
</Select.Root>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### With Disabled Options
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<Select.Root value={selectedOption} onValueChange={setSelectedOption}>
|
|
177
|
+
<Select.Option value="available">Available Option</Select.Option>
|
|
178
|
+
<Select.Option value="disabled" disabled>
|
|
179
|
+
Disabled Option
|
|
180
|
+
</Select.Option>
|
|
181
|
+
<Select.Option value="premium">Premium Feature</Select.Option>
|
|
182
|
+
</Select.Root>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Empty State
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { Select } from "@telegraph/select";
|
|
189
|
+
|
|
190
|
+
export const EmptySelect = () => {
|
|
191
|
+
const [value, setValue] = useState("");
|
|
192
|
+
const options = []; // Empty options array
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Select.Root value={value} onValueChange={setValue}>
|
|
196
|
+
{options.length === 0 ? (
|
|
197
|
+
<Select.Option value="" disabled>
|
|
198
|
+
No options available
|
|
199
|
+
</Select.Option>
|
|
200
|
+
) : (
|
|
201
|
+
options.map((option) => (
|
|
202
|
+
<Select.Option key={option.value} value={option.value}>
|
|
203
|
+
{option.label}
|
|
204
|
+
</Select.Option>
|
|
205
|
+
))
|
|
206
|
+
)}
|
|
207
|
+
</Select.Root>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Advanced Usage
|
|
213
|
+
|
|
214
|
+
### Form Integration
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { Select } from "@telegraph/select";
|
|
218
|
+
import { Controller, useForm } from "react-hook-form";
|
|
219
|
+
|
|
220
|
+
type FormData = {
|
|
221
|
+
priority: string;
|
|
222
|
+
categories: string[];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const TaskForm = () => {
|
|
226
|
+
const {
|
|
227
|
+
control,
|
|
228
|
+
handleSubmit,
|
|
229
|
+
formState: { errors },
|
|
230
|
+
} = useForm<FormData>();
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<form onSubmit={handleSubmit(console.log)}>
|
|
234
|
+
<div>
|
|
235
|
+
<label htmlFor="priority">Priority</label>
|
|
236
|
+
<Controller
|
|
237
|
+
name="priority"
|
|
238
|
+
control={control}
|
|
239
|
+
rules={{ required: "Priority is required" }}
|
|
240
|
+
render={({ field }) => (
|
|
241
|
+
<Select.Root
|
|
242
|
+
value={field.value || ""}
|
|
243
|
+
onValueChange={field.onChange}
|
|
244
|
+
placeholder="Select priority"
|
|
245
|
+
>
|
|
246
|
+
<Select.Option value="low">Low Priority</Select.Option>
|
|
247
|
+
<Select.Option value="medium">Medium Priority</Select.Option>
|
|
248
|
+
<Select.Option value="high">High Priority</Select.Option>
|
|
249
|
+
<Select.Option value="urgent">Urgent</Select.Option>
|
|
250
|
+
</Select.Root>
|
|
251
|
+
)}
|
|
252
|
+
/>
|
|
253
|
+
{errors.priority && (
|
|
254
|
+
<span className="error">{errors.priority.message}</span>
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div>
|
|
259
|
+
<label htmlFor="categories">Categories</label>
|
|
260
|
+
<Controller
|
|
261
|
+
name="categories"
|
|
262
|
+
control={control}
|
|
263
|
+
render={({ field }) => (
|
|
264
|
+
<Select.Root
|
|
265
|
+
value={field.value || []}
|
|
266
|
+
onValueChange={field.onChange}
|
|
267
|
+
placeholder="Select categories"
|
|
268
|
+
>
|
|
269
|
+
<Select.Option value="frontend">Frontend</Select.Option>
|
|
270
|
+
<Select.Option value="backend">Backend</Select.Option>
|
|
271
|
+
<Select.Option value="design">Design</Select.Option>
|
|
272
|
+
<Select.Option value="testing">Testing</Select.Option>
|
|
273
|
+
</Select.Root>
|
|
274
|
+
)}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
</form>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Dynamic Options
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import { Select } from "@telegraph/select";
|
|
286
|
+
|
|
287
|
+
export const DynamicSelect = () => {
|
|
288
|
+
const [category, setCategory] = useState("");
|
|
289
|
+
const [options, setOptions] = useState([]);
|
|
290
|
+
const [loading, setLoading] = useState(false);
|
|
291
|
+
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
if (category) {
|
|
294
|
+
setLoading(true);
|
|
295
|
+
fetchOptionsForCategory(category)
|
|
296
|
+
.then(setOptions)
|
|
297
|
+
.finally(() => setLoading(false));
|
|
298
|
+
}
|
|
299
|
+
}, [category]);
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div>
|
|
303
|
+
<Select.Root value={category} onValueChange={setCategory}>
|
|
304
|
+
<Select.Option value="electronics">Electronics</Select.Option>
|
|
305
|
+
<Select.Option value="clothing">Clothing</Select.Option>
|
|
306
|
+
<Select.Option value="books">Books</Select.Option>
|
|
307
|
+
</Select.Root>
|
|
308
|
+
|
|
309
|
+
{category && (
|
|
310
|
+
<Select.Root
|
|
311
|
+
disabled={loading}
|
|
312
|
+
value=""
|
|
313
|
+
onValueChange={console.log}
|
|
314
|
+
placeholder={loading ? "Loading..." : "Select subcategory"}
|
|
315
|
+
>
|
|
316
|
+
{options.map((option) => (
|
|
317
|
+
<Select.Option key={option.id} value={option.id}>
|
|
318
|
+
{option.name}
|
|
319
|
+
</Select.Option>
|
|
320
|
+
))}
|
|
321
|
+
</Select.Root>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Custom Trigger Styling
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import { Select } from "@telegraph/select";
|
|
332
|
+
|
|
333
|
+
export const CustomStyledSelect = () => {
|
|
334
|
+
const [value, setValue] = useState("");
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<Select.Root
|
|
338
|
+
value={value}
|
|
339
|
+
onValueChange={setValue}
|
|
340
|
+
triggerProps={{
|
|
341
|
+
variant: "outline",
|
|
342
|
+
color: "accent",
|
|
343
|
+
}}
|
|
344
|
+
contentProps={{
|
|
345
|
+
align: "start",
|
|
346
|
+
sideOffset: 4,
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
<Select.Option value="option1">Option 1</Select.Option>
|
|
350
|
+
<Select.Option value="option2">Option 2</Select.Option>
|
|
351
|
+
<Select.Option value="option3">Option 3</Select.Option>
|
|
352
|
+
</Select.Root>
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Conditional Rendering
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import { Select } from "@telegraph/select";
|
|
361
|
+
|
|
362
|
+
export const ConditionalSelect = ({ userRole, permissions }) => {
|
|
363
|
+
const [access, setAccess] = useState("");
|
|
364
|
+
|
|
365
|
+
const availableRoles = [
|
|
366
|
+
{ value: "read", label: "Read Only", permission: "read" },
|
|
367
|
+
{ value: "write", label: "Read & Write", permission: "write" },
|
|
368
|
+
{ value: "admin", label: "Administrator", permission: "admin" },
|
|
369
|
+
].filter((role) => permissions.includes(role.permission));
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<Select.Root value={access} onValueChange={setAccess}>
|
|
373
|
+
{availableRoles.map((role) => (
|
|
374
|
+
<Select.Option key={role.value} value={role.value}>
|
|
375
|
+
{role.label}
|
|
376
|
+
</Select.Option>
|
|
377
|
+
))}
|
|
378
|
+
</Select.Root>
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Loading State
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
import { Select } from "@telegraph/select";
|
|
387
|
+
import { Skeleton } from "@telegraph/skeleton";
|
|
388
|
+
|
|
389
|
+
export const SelectWithLoading = ({ loading, options, ...props }) => {
|
|
390
|
+
if (loading) {
|
|
391
|
+
return <Skeleton width="200px" height="32px" />;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<Select.Root {...props}>
|
|
396
|
+
{options.map((option) => (
|
|
397
|
+
<Select.Option key={option.value} value={option.value}>
|
|
398
|
+
{option.label}
|
|
399
|
+
</Select.Option>
|
|
400
|
+
))}
|
|
401
|
+
</Select.Root>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Grouped Options
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
import { Select } from "@telegraph/select";
|
|
410
|
+
|
|
411
|
+
export const GroupedSelect = () => {
|
|
412
|
+
const [value, setValue] = useState("");
|
|
413
|
+
|
|
414
|
+
const optionGroups = [
|
|
415
|
+
{
|
|
416
|
+
label: "Fruits",
|
|
417
|
+
options: [
|
|
418
|
+
{ value: "apple", label: "Apple" },
|
|
419
|
+
{ value: "banana", label: "Banana" },
|
|
420
|
+
{ value: "orange", label: "Orange" },
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
label: "Vegetables",
|
|
425
|
+
options: [
|
|
426
|
+
{ value: "carrot", label: "Carrot" },
|
|
427
|
+
{ value: "lettuce", label: "Lettuce" },
|
|
428
|
+
{ value: "tomato", label: "Tomato" },
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
return (
|
|
434
|
+
<Select.Root value={value} onValueChange={setValue}>
|
|
435
|
+
{optionGroups.map((group) => (
|
|
436
|
+
<React.Fragment key={group.label}>
|
|
437
|
+
<Select.Option value="" disabled>
|
|
438
|
+
{group.label}
|
|
439
|
+
</Select.Option>
|
|
440
|
+
{group.options.map((option) => (
|
|
441
|
+
<Select.Option key={option.value} value={option.value}>
|
|
442
|
+
{option.label}
|
|
443
|
+
</Select.Option>
|
|
444
|
+
))}
|
|
445
|
+
</React.Fragment>
|
|
446
|
+
))}
|
|
447
|
+
</Select.Root>
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Accessibility
|
|
453
|
+
|
|
454
|
+
Since Select is built on Combobox, it inherits all accessibility features:
|
|
455
|
+
|
|
456
|
+
- ✅ **Keyboard Navigation**: Arrow keys, Enter, Escape support
|
|
457
|
+
- ✅ **Screen Reader Support**: Proper ARIA attributes and announcements
|
|
458
|
+
- ✅ **Focus Management**: Logical focus order and indicators
|
|
459
|
+
- ✅ **High Contrast**: Compatible with high contrast modes
|
|
460
|
+
- ✅ **Touch Support**: Optimized for mobile and touch devices
|
|
461
|
+
|
|
462
|
+
### Keyboard Shortcuts
|
|
463
|
+
|
|
464
|
+
| Key | Action |
|
|
465
|
+
| ----------------- | ------------------------- |
|
|
466
|
+
| `Space` / `Enter` | Open/close dropdown |
|
|
467
|
+
| `Arrow Up/Down` | Navigate options |
|
|
468
|
+
| `Escape` | Close dropdown |
|
|
469
|
+
| `Home` / `End` | Jump to first/last option |
|
|
470
|
+
| `Type to search` | Quick option selection |
|
|
471
|
+
|
|
472
|
+
### ARIA Attributes
|
|
473
|
+
|
|
474
|
+
- `role="combobox"` - Applied to the trigger
|
|
475
|
+
- `aria-expanded` - Indicates dropdown state
|
|
476
|
+
- `aria-haspopup="listbox"` - Describes dropdown content
|
|
477
|
+
- `role="listbox"` - Applied to options container
|
|
478
|
+
- `role="option"` - Applied to each option
|
|
479
|
+
- `aria-selected` - Indicates selected options
|
|
480
|
+
|
|
481
|
+
### Best Practices
|
|
482
|
+
|
|
483
|
+
1. **Provide Clear Labels**: Use descriptive labels for the select
|
|
484
|
+
2. **Meaningful Placeholders**: Write helpful placeholder text
|
|
485
|
+
3. **Logical Option Order**: Order options alphabetically or by relevance
|
|
486
|
+
4. **Handle Empty States**: Provide feedback when no options available
|
|
487
|
+
5. **Error Messaging**: Show clear validation errors
|
|
488
|
+
|
|
489
|
+
## Examples
|
|
490
|
+
|
|
491
|
+
### Basic Example
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
import { Select } from "@telegraph/select";
|
|
495
|
+
|
|
496
|
+
export const StatusSelector = () => {
|
|
497
|
+
const [status, setStatus] = useState("pending");
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
<div>
|
|
501
|
+
<label htmlFor="status">Status</label>
|
|
502
|
+
<Select.Root value={status} onValueChange={setStatus}>
|
|
503
|
+
<Select.Option value="pending">Pending</Select.Option>
|
|
504
|
+
<Select.Option value="approved">Approved</Select.Option>
|
|
505
|
+
<Select.Option value="rejected">Rejected</Select.Option>
|
|
506
|
+
<Select.Option value="cancelled">Cancelled</Select.Option>
|
|
507
|
+
</Select.Root>
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
};
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Advanced Example
|
|
514
|
+
|
|
515
|
+
```tsx
|
|
516
|
+
import { Select } from "@telegraph/select";
|
|
517
|
+
import { Controller, useForm } from "react-hook-form";
|
|
518
|
+
|
|
519
|
+
export const UserPreferencesForm = () => {
|
|
520
|
+
const { control, handleSubmit, watch } = useForm({
|
|
521
|
+
defaultValues: {
|
|
522
|
+
theme: "system",
|
|
523
|
+
language: "en",
|
|
524
|
+
notifications: [],
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const theme = watch("theme");
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
<form onSubmit={handleSubmit(console.log)}>
|
|
532
|
+
<div className="form-group">
|
|
533
|
+
<label>Theme Preference</label>
|
|
534
|
+
<Controller
|
|
535
|
+
name="theme"
|
|
536
|
+
control={control}
|
|
537
|
+
render={({ field }) => (
|
|
538
|
+
<Select.Root
|
|
539
|
+
value={field.value}
|
|
540
|
+
onValueChange={field.onChange}
|
|
541
|
+
size="2"
|
|
542
|
+
>
|
|
543
|
+
<Select.Option value="light">Light Mode</Select.Option>
|
|
544
|
+
<Select.Option value="dark">Dark Mode</Select.Option>
|
|
545
|
+
<Select.Option value="system">System Default</Select.Option>
|
|
546
|
+
</Select.Root>
|
|
547
|
+
)}
|
|
548
|
+
/>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<div className="form-group">
|
|
552
|
+
<label>Language</label>
|
|
553
|
+
<Controller
|
|
554
|
+
name="language"
|
|
555
|
+
control={control}
|
|
556
|
+
render={({ field }) => (
|
|
557
|
+
<Select.Root
|
|
558
|
+
value={field.value}
|
|
559
|
+
onValueChange={field.onChange}
|
|
560
|
+
triggerProps={{ variant: "outline" }}
|
|
561
|
+
>
|
|
562
|
+
<Select.Option value="en">English</Select.Option>
|
|
563
|
+
<Select.Option value="es">Español</Select.Option>
|
|
564
|
+
<Select.Option value="fr">Français</Select.Option>
|
|
565
|
+
<Select.Option value="de">Deutsch</Select.Option>
|
|
566
|
+
</Select.Root>
|
|
567
|
+
)}
|
|
568
|
+
/>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
<div className="form-group">
|
|
572
|
+
<label>Notification Types</label>
|
|
573
|
+
<Controller
|
|
574
|
+
name="notifications"
|
|
575
|
+
control={control}
|
|
576
|
+
render={({ field }) => (
|
|
577
|
+
<Select.Root
|
|
578
|
+
value={field.value}
|
|
579
|
+
onValueChange={field.onChange}
|
|
580
|
+
placeholder="Select notification types"
|
|
581
|
+
>
|
|
582
|
+
<Select.Option value="email">Email Updates</Select.Option>
|
|
583
|
+
<Select.Option value="push">Push Notifications</Select.Option>
|
|
584
|
+
<Select.Option value="sms">SMS Alerts</Select.Option>
|
|
585
|
+
<Select.Option value="desktop">
|
|
586
|
+
Desktop Notifications
|
|
587
|
+
</Select.Option>
|
|
588
|
+
</Select.Root>
|
|
589
|
+
)}
|
|
590
|
+
/>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<button type="submit">Save Preferences</button>
|
|
594
|
+
</form>
|
|
595
|
+
);
|
|
596
|
+
};
|
|
15
597
|
```
|
|
16
|
-
|
|
598
|
+
|
|
599
|
+
### Real-world Example
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
import { Select } from "@telegraph/select";
|
|
603
|
+
import { useEffect, useState } from "react";
|
|
604
|
+
|
|
605
|
+
export const CountryRegionSelector = () => {
|
|
606
|
+
const [country, setCountry] = useState("");
|
|
607
|
+
const [region, setRegion] = useState("");
|
|
608
|
+
const [regions, setRegions] = useState([]);
|
|
609
|
+
const [loading, setLoading] = useState(false);
|
|
610
|
+
|
|
611
|
+
useEffect(() => {
|
|
612
|
+
if (country) {
|
|
613
|
+
setLoading(true);
|
|
614
|
+
setRegion(""); // Reset region when country changes
|
|
615
|
+
|
|
616
|
+
fetchRegionsForCountry(country)
|
|
617
|
+
.then(setRegions)
|
|
618
|
+
.finally(() => setLoading(false));
|
|
619
|
+
}
|
|
620
|
+
}, [country]);
|
|
621
|
+
|
|
622
|
+
const countries = [
|
|
623
|
+
{ value: "us", label: "United States" },
|
|
624
|
+
{ value: "ca", label: "Canada" },
|
|
625
|
+
{ value: "mx", label: "Mexico" },
|
|
626
|
+
{ value: "uk", label: "United Kingdom" },
|
|
627
|
+
];
|
|
628
|
+
|
|
629
|
+
return (
|
|
630
|
+
<div className="address-form">
|
|
631
|
+
<div className="form-row">
|
|
632
|
+
<div className="form-group">
|
|
633
|
+
<label htmlFor="country">Country</label>
|
|
634
|
+
<Select.Root
|
|
635
|
+
value={country}
|
|
636
|
+
onValueChange={setCountry}
|
|
637
|
+
placeholder="Select country"
|
|
638
|
+
size="2"
|
|
639
|
+
>
|
|
640
|
+
{countries.map((country) => (
|
|
641
|
+
<Select.Option key={country.value} value={country.value}>
|
|
642
|
+
{country.label}
|
|
643
|
+
</Select.Option>
|
|
644
|
+
))}
|
|
645
|
+
</Select.Root>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
<div className="form-group">
|
|
649
|
+
<label htmlFor="region">State/Province</label>
|
|
650
|
+
<Select.Root
|
|
651
|
+
value={region}
|
|
652
|
+
onValueChange={setRegion}
|
|
653
|
+
disabled={!country || loading}
|
|
654
|
+
placeholder={
|
|
655
|
+
!country
|
|
656
|
+
? "Select country first"
|
|
657
|
+
: loading
|
|
658
|
+
? "Loading..."
|
|
659
|
+
: "Select region"
|
|
660
|
+
}
|
|
661
|
+
size="2"
|
|
662
|
+
>
|
|
663
|
+
{regions.map((region) => (
|
|
664
|
+
<Select.Option key={region.code} value={region.code}>
|
|
665
|
+
{region.name}
|
|
666
|
+
</Select.Option>
|
|
667
|
+
))}
|
|
668
|
+
</Select.Root>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
);
|
|
673
|
+
};
|
|
17
674
|
```
|
|
675
|
+
|
|
676
|
+
## References
|
|
677
|
+
|
|
678
|
+
- [Storybook Demo](https://storybook.telegraph.dev/?path=/docs/select)
|
|
679
|
+
- [Combobox Component](../combobox/README.md) - Full-featured dropdown with search
|
|
680
|
+
|
|
681
|
+
## Contributing
|
|
682
|
+
|
|
683
|
+
See our [Contributing Guide](../../CONTRIBUTING.md) for more details.
|
|
684
|
+
|
|
685
|
+
## License
|
|
686
|
+
|
|
687
|
+
MIT License - see [LICENSE](../../LICENSE) for details.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telegraph/select",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.56",
|
|
4
4
|
"description": "A simple select component built on top of @telegraph/combobox",
|
|
5
5
|
"repository": "https://github.com/knocklabs/telegraph/tree/main/packages/select",
|
|
6
6
|
"author": "@knocklabs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"preview": "vite preview"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@telegraph/combobox": "0.1.
|
|
33
|
+
"@telegraph/combobox": "0.1.2",
|
|
34
34
|
"@telegraph/helpers": "0.0.13"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|