@telegraph/combobox 0.1.0 → 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/CHANGELOG.md +29 -0
- package/README.md +262 -334
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.mjs +1733 -1721
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
# 🔍 Combobox
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> A searchable select component combining input and dropdown functionality with single and multi-select support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@telegraph/combobox)
|
|
8
|
+
[](https://bundlephobia.com/result?p=@telegraph/combobox)
|
|
9
|
+
[](https://github.com/knocklabs/telegraph/blob/main/LICENSE)
|
|
8
10
|
|
|
9
|
-
## Installation
|
|
11
|
+
## Installation
|
|
10
12
|
|
|
11
|
-
```
|
|
13
|
+
```bash
|
|
12
14
|
npm install @telegraph/combobox
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
### Add stylesheet
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
Pick one:
|
|
20
|
+
|
|
21
|
+
Via CSS (preferred):
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
@import "@telegraph/combobox";
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
Via Javascript:
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
```tsx
|
|
30
|
+
import "@telegraph/combobox/default.css";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> Then, include `className="tgph"` on the farthest parent element wrapping the telegraph components
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
## Quick Start
|
|
26
36
|
|
|
27
37
|
```tsx
|
|
28
38
|
import { Combobox } from "@telegraph/combobox";
|
|
29
39
|
import { useState } from "react";
|
|
30
40
|
|
|
31
41
|
// Single select example
|
|
32
|
-
const SingleSelectExample = () => {
|
|
42
|
+
export const SingleSelectExample = () => {
|
|
33
43
|
const [value, setValue] = useState<string>("");
|
|
34
44
|
|
|
35
45
|
return (
|
|
@@ -45,98 +55,72 @@ const SingleSelectExample = () => {
|
|
|
45
55
|
</Combobox.Root>
|
|
46
56
|
);
|
|
47
57
|
};
|
|
48
|
-
|
|
49
|
-
// Multi select example
|
|
50
|
-
const MultiSelectExample = () => {
|
|
51
|
-
const [values, setValues] = useState<string[]>([]);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Combobox.Root value={values} onValueChange={setValues}>
|
|
55
|
-
<Combobox.Trigger placeholder="Select options..." />
|
|
56
|
-
<Combobox.Content>
|
|
57
|
-
<Combobox.Search />
|
|
58
|
-
<Combobox.Options>
|
|
59
|
-
<Combobox.Option value="option1">Option 1</Combobox.Option>
|
|
60
|
-
<Combobox.Option value="option2">Option 2</Combobox.Option>
|
|
61
|
-
</Combobox.Options>
|
|
62
|
-
</Combobox.Content>
|
|
63
|
-
</Combobox.Root>
|
|
64
|
-
);
|
|
65
|
-
};
|
|
66
58
|
```
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#### Single Select Types
|
|
60
|
+
## API Reference
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
// String values (recommended)
|
|
74
|
-
const [value, setValue] = useState<string>("");
|
|
75
|
-
```
|
|
62
|
+
### `<Combobox.Root>`
|
|
76
63
|
|
|
77
|
-
|
|
64
|
+
The root component that manages the state and context for the combobox.
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
66
|
+
| Prop | Type | Default | Description |
|
|
67
|
+
| ---------------- | ----------------------------------------------------------- | ------------ | -------------------------------------- |
|
|
68
|
+
| `value` | `string \| string[] \| Option \| Option[]` | `undefined` | The selected value(s) |
|
|
69
|
+
| `onValueChange` | `(value: string \| string[] \| Option \| Option[]) => void` | `undefined` | Callback when selection changes |
|
|
70
|
+
| `layout` | `"truncate" \| "wrap"` | `"truncate"` | How to display multiple selections |
|
|
71
|
+
| `open` | `boolean` | `undefined` | Controlled open state |
|
|
72
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state |
|
|
73
|
+
| `errored` | `boolean` | `false` | Shows error styling |
|
|
74
|
+
| `placeholder` | `string` | `undefined` | Placeholder text |
|
|
75
|
+
| `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback when open state changes |
|
|
76
|
+
| `modal` | `boolean` | `true` | Whether to render in a modal |
|
|
77
|
+
| `closeOnSelect` | `boolean` | `true` | Close menu after selection |
|
|
78
|
+
| `clearable` | `boolean` | `false` | Show clear button |
|
|
79
|
+
| `disabled` | `boolean` | `false` | Disable the combobox |
|
|
80
|
+
| `legacyBehavior` | `boolean` | `false` | Use legacy object format ⚠️ Deprecated |
|
|
81
|
+
|
|
82
|
+
### `<Combobox.Trigger>`
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
The button that triggers the combobox dropdown.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
| ------------- | ------------------- | ----------- | ---------------------- |
|
|
88
|
+
| `size` | `"1" \| "2" \| "3"` | `"2"` | Size of the trigger |
|
|
89
|
+
| `placeholder` | `string` | `undefined` | Placeholder text |
|
|
90
|
+
| `children` | `ReactNode` | `undefined` | Custom trigger content |
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
- Keyboard navigation:
|
|
90
|
-
- `↓` / `↑`: Navigate options
|
|
91
|
-
- `Enter` / `Space`: Select option
|
|
92
|
-
- `Escape`: Close dropdown
|
|
93
|
-
- `Tab`: Move focus
|
|
94
|
-
- Screen reader announcements for selection changes
|
|
95
|
-
- Focus management
|
|
92
|
+
### Other Components
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
- **`<Combobox.Content>`** - Dropdown menu content container
|
|
95
|
+
- **`<Combobox.Options>`** - Container for option items
|
|
96
|
+
- **`<Combobox.Option>`** - Individual selectable option
|
|
97
|
+
- **`<Combobox.Search>`** - Search input for filtering options
|
|
98
|
+
- **`<Combobox.Empty>`** - Empty state when no options match
|
|
99
|
+
- **`<Combobox.Create>`** - Option to create new values
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
For detailed props of each component, see the [Complete Component Reference](#complete-component-reference) section below.
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
import { useForm } from "react-hook-form";
|
|
103
|
+
## Advanced Usage
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
const { register, setValue } = useForm();
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<form>
|
|
109
|
-
<Combobox.Root onValueChange={(value) => setValue("field", value)}>
|
|
110
|
-
{/* ... */}
|
|
111
|
-
</Combobox.Root>
|
|
112
|
-
</form>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### Custom Filtering
|
|
105
|
+
### Multi-Select with Tags
|
|
118
106
|
|
|
119
107
|
```tsx
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
opt.toLowerCase().includes(searchQuery.toLowerCase()),
|
|
126
|
-
),
|
|
127
|
-
[searchQuery],
|
|
128
|
-
);
|
|
108
|
+
import { Combobox } from "@telegraph/combobox";
|
|
109
|
+
import { useState } from "react";
|
|
110
|
+
|
|
111
|
+
export const MultiSelectExample = () => {
|
|
112
|
+
const [values, setValues] = useState<string[]>([]);
|
|
129
113
|
|
|
130
114
|
return (
|
|
131
|
-
<Combobox.Root>
|
|
115
|
+
<Combobox.Root value={values} onValueChange={setValues} clearable>
|
|
116
|
+
<Combobox.Trigger placeholder="Select multiple options..." />
|
|
132
117
|
<Combobox.Content>
|
|
133
|
-
<Combobox.Search
|
|
118
|
+
<Combobox.Search />
|
|
134
119
|
<Combobox.Options>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
))}
|
|
120
|
+
<Combobox.Option value="react">React</Combobox.Option>
|
|
121
|
+
<Combobox.Option value="vue">Vue</Combobox.Option>
|
|
122
|
+
<Combobox.Option value="svelte">Svelte</Combobox.Option>
|
|
123
|
+
<Combobox.Option value="angular">Angular</Combobox.Option>
|
|
140
124
|
</Combobox.Options>
|
|
141
125
|
</Combobox.Content>
|
|
142
126
|
</Combobox.Root>
|
|
@@ -144,329 +128,275 @@ const MyCombobox = () => {
|
|
|
144
128
|
};
|
|
145
129
|
```
|
|
146
130
|
|
|
147
|
-
|
|
131
|
+
### Async Loading with Search
|
|
148
132
|
|
|
149
133
|
```tsx
|
|
150
|
-
|
|
134
|
+
import { Combobox } from "@telegraph/combobox";
|
|
135
|
+
import { Box } from "@telegraph/layout";
|
|
136
|
+
import { useMemo, useState } from "react";
|
|
137
|
+
|
|
138
|
+
export const AsyncCombobox = () => {
|
|
151
139
|
const [isLoading, setIsLoading] = useState(false);
|
|
152
|
-
const [options, setOptions] = useState([]);
|
|
140
|
+
const [options, setOptions] = useState<string[]>([]);
|
|
141
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
153
142
|
|
|
154
|
-
const loadOptions = async (query) => {
|
|
143
|
+
const loadOptions = async (query: string) => {
|
|
155
144
|
setIsLoading(true);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
145
|
+
try {
|
|
146
|
+
const results = await fetchOptions(query);
|
|
147
|
+
setOptions(results);
|
|
148
|
+
} finally {
|
|
149
|
+
setIsLoading(false);
|
|
150
|
+
}
|
|
159
151
|
};
|
|
160
152
|
|
|
153
|
+
const filteredOptions = useMemo(() => {
|
|
154
|
+
return options.filter((option) =>
|
|
155
|
+
option.toLowerCase().includes(searchQuery.toLowerCase()),
|
|
156
|
+
);
|
|
157
|
+
}, [options, searchQuery]);
|
|
158
|
+
|
|
161
159
|
return (
|
|
162
160
|
<Combobox.Root>
|
|
161
|
+
<Combobox.Trigger placeholder="Search for options..." />
|
|
163
162
|
<Combobox.Content>
|
|
164
|
-
<Combobox.Search
|
|
163
|
+
<Combobox.Search
|
|
164
|
+
value={searchQuery}
|
|
165
|
+
onValueChange={(query) => {
|
|
166
|
+
setSearchQuery(query);
|
|
167
|
+
loadOptions(query);
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
165
170
|
<Combobox.Options>
|
|
166
171
|
{isLoading ? (
|
|
167
|
-
<Box
|
|
172
|
+
<Box padding="4" textAlign="center">
|
|
168
173
|
Loading...
|
|
169
174
|
</Box>
|
|
170
175
|
) : (
|
|
171
|
-
|
|
172
|
-
<Combobox.Option key={
|
|
173
|
-
{
|
|
176
|
+
filteredOptions.map((option) => (
|
|
177
|
+
<Combobox.Option key={option} value={option}>
|
|
178
|
+
{option}
|
|
174
179
|
</Combobox.Option>
|
|
175
180
|
))
|
|
176
181
|
)}
|
|
177
182
|
</Combobox.Options>
|
|
183
|
+
<Combobox.Empty />
|
|
178
184
|
</Combobox.Content>
|
|
179
185
|
</Combobox.Root>
|
|
180
186
|
);
|
|
181
187
|
};
|
|
182
188
|
```
|
|
183
189
|
|
|
184
|
-
|
|
190
|
+
### Create New Options
|
|
185
191
|
|
|
186
192
|
```tsx
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
import { Combobox } from "@telegraph/combobox";
|
|
194
|
+
import { useState } from "react";
|
|
195
|
+
|
|
196
|
+
export const CreatableCombobox = () => {
|
|
197
|
+
const [options, setOptions] = useState(["Option 1", "Option 2"]);
|
|
198
|
+
const [value, setValue] = useState("");
|
|
199
|
+
|
|
200
|
+
const handleCreate = (newValue: string) => {
|
|
201
|
+
setOptions((prev) => [...prev, newValue]);
|
|
202
|
+
setValue(newValue);
|
|
203
|
+
};
|
|
189
204
|
|
|
190
205
|
return (
|
|
191
|
-
<
|
|
192
|
-
<Combobox.
|
|
193
|
-
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
<Combobox.Root value={value} onValueChange={setValue}>
|
|
207
|
+
<Combobox.Trigger placeholder="Type to create..." />
|
|
208
|
+
<Combobox.Content>
|
|
209
|
+
<Combobox.Search />
|
|
210
|
+
<Combobox.Options>
|
|
211
|
+
{options.map((option) => (
|
|
212
|
+
<Combobox.Option key={option} value={option}>
|
|
213
|
+
{option}
|
|
214
|
+
</Combobox.Option>
|
|
215
|
+
))}
|
|
216
|
+
</Combobox.Options>
|
|
217
|
+
<Combobox.Create
|
|
218
|
+
values={options}
|
|
219
|
+
onCreate={handleCreate}
|
|
220
|
+
leadingText="Create"
|
|
221
|
+
/>
|
|
222
|
+
</Combobox.Content>
|
|
223
|
+
</Combobox.Root>
|
|
199
224
|
);
|
|
200
225
|
};
|
|
201
226
|
```
|
|
202
227
|
|
|
203
|
-
###
|
|
228
|
+
### Form Integration
|
|
204
229
|
|
|
205
|
-
|
|
230
|
+
```tsx
|
|
231
|
+
import { Combobox } from "@telegraph/combobox";
|
|
232
|
+
import { useForm } from "react-hook-form";
|
|
206
233
|
|
|
207
|
-
|
|
234
|
+
type FormData = {
|
|
235
|
+
framework: string;
|
|
236
|
+
languages: string[];
|
|
237
|
+
};
|
|
208
238
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
| Name | Type | Default | Description |
|
|
212
|
-
| -------------- | --------------------------------------------------------- | ---------- | ---------------------------------- |
|
|
213
|
-
| value | string \| string[] \| Option \| Option[] | undefined | The selected value(s) |
|
|
214
|
-
| onValueChange | (value: string \| string[] \| Option \| Option[]) => void | undefined | Callback when selection changes |
|
|
215
|
-
| layout | "truncate" \| "wrap" | "truncate" | How to display multiple selections |
|
|
216
|
-
| open | boolean | undefined | Controlled open state |
|
|
217
|
-
| defaultOpen | boolean | false | Initial open state |
|
|
218
|
-
| errored | boolean | false | Shows error styling |
|
|
219
|
-
| placeholder | string | undefined | Placeholder text |
|
|
220
|
-
| onOpenChange | (open: boolean) => void | undefined | Callback when open state changes |
|
|
221
|
-
| modal | boolean | true | Whether to render in a modal |
|
|
222
|
-
| closeOnSelect | boolean | true | Close menu after selection |
|
|
223
|
-
| clearable | boolean | false | Show clear button |
|
|
224
|
-
| disabled | boolean | false | Disable the combobox |
|
|
225
|
-
| legacyBehavior | boolean | false | Use legacy object format |
|
|
226
|
-
|
|
227
|
-
#### `<Combobox.Trigger/>`
|
|
239
|
+
export const FormIntegration = () => {
|
|
240
|
+
const { register, setValue, watch } = useForm<FormData>();
|
|
228
241
|
|
|
229
|
-
|
|
242
|
+
return (
|
|
243
|
+
<form>
|
|
244
|
+
<Combobox.Root
|
|
245
|
+
value={watch("framework")}
|
|
246
|
+
onValueChange={(value) => setValue("framework", value)}
|
|
247
|
+
>
|
|
248
|
+
<Combobox.Trigger placeholder="Select framework..." />
|
|
249
|
+
<Combobox.Content>
|
|
250
|
+
<Combobox.Search />
|
|
251
|
+
<Combobox.Options>
|
|
252
|
+
<Combobox.Option value="react">React</Combobox.Option>
|
|
253
|
+
<Combobox.Option value="vue">Vue</Combobox.Option>
|
|
254
|
+
</Combobox.Options>
|
|
255
|
+
</Combobox.Content>
|
|
256
|
+
</Combobox.Root>
|
|
257
|
+
</form>
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
```
|
|
230
261
|
|
|
231
|
-
|
|
262
|
+
### Custom Trigger with Primitives
|
|
232
263
|
|
|
233
|
-
|
|
264
|
+
```tsx
|
|
265
|
+
import { Combobox } from "@telegraph/combobox";
|
|
266
|
+
import { Box, Stack } from "@telegraph/layout";
|
|
267
|
+
|
|
268
|
+
export const CustomTrigger = () => (
|
|
269
|
+
<Combobox.Root clearable>
|
|
270
|
+
<Combobox.Trigger>
|
|
271
|
+
<Stack direction="row" justify="space-between" align="center" w="full">
|
|
272
|
+
<Box flex="1" overflow="hidden">
|
|
273
|
+
<Combobox.Primitives.TriggerTagsContainer>
|
|
274
|
+
<Combobox.Primitives.TriggerTag.Default value="tag1" />
|
|
275
|
+
<Combobox.Primitives.TriggerTag.Default value="tag2" />
|
|
276
|
+
</Combobox.Primitives.TriggerTagsContainer>
|
|
277
|
+
</Box>
|
|
278
|
+
<Stack direction="row" gap="1" align="center">
|
|
279
|
+
<Combobox.Primitives.TriggerClear />
|
|
280
|
+
<Box borderLeft="1" h="4" />
|
|
281
|
+
<Combobox.Primitives.TriggerIndicator />
|
|
282
|
+
</Stack>
|
|
283
|
+
</Stack>
|
|
284
|
+
</Combobox.Trigger>
|
|
285
|
+
<Combobox.Content>{/* Content */}</Combobox.Content>
|
|
286
|
+
</Combobox.Root>
|
|
287
|
+
);
|
|
288
|
+
```
|
|
234
289
|
|
|
235
|
-
|
|
236
|
-
| ----------- | ----------------- | --------- | ---------------------- |
|
|
237
|
-
| size | "1" \| "2" \| "3" | "2" | Size of the trigger |
|
|
238
|
-
| placeholder | string | undefined | Placeholder text |
|
|
239
|
-
| children | ReactNode | undefined | Custom trigger content |
|
|
290
|
+
## Accessibility
|
|
240
291
|
|
|
241
|
-
|
|
292
|
+
- ✅ **ARIA Compliance**: Implements [ARIA Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)
|
|
293
|
+
- ✅ **Keyboard Navigation**: Full keyboard support with arrow keys, Enter, Escape
|
|
294
|
+
- ✅ **Screen Readers**: Proper ARIA roles, labels, and state announcements
|
|
295
|
+
- ✅ **Focus Management**: Logical focus order and focus restoration
|
|
242
296
|
|
|
243
|
-
|
|
297
|
+
### Keyboard Shortcuts
|
|
244
298
|
|
|
245
|
-
|
|
299
|
+
| Key | Action |
|
|
300
|
+
| ----------------- | ------------------------------ |
|
|
301
|
+
| `↓` / `↑` | Navigate options |
|
|
302
|
+
| `Enter` / `Space` | Select option |
|
|
303
|
+
| `Escape` | Close dropdown |
|
|
304
|
+
| `Tab` | Move focus |
|
|
305
|
+
| `Backspace` | Remove last tag (multi-select) |
|
|
246
306
|
|
|
247
|
-
|
|
307
|
+
### ARIA Attributes
|
|
248
308
|
|
|
249
|
-
|
|
309
|
+
- `role="combobox"` - On trigger element
|
|
310
|
+
- `aria-expanded` - Indicates dropdown state
|
|
311
|
+
- `aria-controls` - Links trigger to dropdown
|
|
312
|
+
- `aria-selected` - Indicates selected options
|
|
313
|
+
- `role="listbox"` and `role="option"` - On dropdown and options
|
|
250
314
|
|
|
251
|
-
|
|
315
|
+
### Best Practices
|
|
252
316
|
|
|
253
|
-
|
|
317
|
+
1. **Provide labels**: Use clear, descriptive placeholders
|
|
318
|
+
2. **Handle loading states**: Show loading indicators during async operations
|
|
319
|
+
3. **Error feedback**: Use the `errored` prop and provide error messages
|
|
320
|
+
4. **Reasonable limits**: Consider pagination for large option lists
|
|
254
321
|
|
|
255
|
-
|
|
322
|
+
## Complete Component Reference
|
|
256
323
|
|
|
257
|
-
|
|
324
|
+
### `<Combobox.Option>`
|
|
258
325
|
|
|
259
326
|
Individual selectable option item.
|
|
260
327
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
| label | string | undefined | Display label |
|
|
267
|
-
| selected | boolean | undefined | Force selected state |
|
|
328
|
+
| Prop | Type | Default | Description |
|
|
329
|
+
| ---------- | --------- | ----------- | -------------------- |
|
|
330
|
+
| `value` | `string` | required | Option value |
|
|
331
|
+
| `label` | `string` | `undefined` | Display label |
|
|
332
|
+
| `selected` | `boolean` | `undefined` | Force selected state |
|
|
268
333
|
|
|
269
|
-
|
|
334
|
+
### `<Combobox.Search>`
|
|
270
335
|
|
|
271
336
|
Search input field for filtering options.
|
|
272
337
|
|
|
273
|
-
|
|
338
|
+
| Prop | Type | Default | Description |
|
|
339
|
+
| ------------- | -------- | ---------- | ------------------- |
|
|
340
|
+
| `label` | `string` | `"Search"` | Accessibility label |
|
|
341
|
+
| `placeholder` | `string` | `"Search"` | Input placeholder |
|
|
274
342
|
|
|
275
|
-
|
|
276
|
-
| ----------- | ------ | -------- | ------------------- |
|
|
277
|
-
| label | string | "Search" | Accessibility label |
|
|
278
|
-
| placeholder | string | "Search" | Input placeholder |
|
|
279
|
-
|
|
280
|
-
#### `<Combobox.Empty/>`
|
|
343
|
+
### `<Combobox.Empty>`
|
|
281
344
|
|
|
282
345
|
Empty state component shown when no options match search.
|
|
283
346
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
| icon | IconProps \| null | SearchIcon | Empty state icon |
|
|
289
|
-
| message | string \| null | "No results found" | Empty state message |
|
|
347
|
+
| Prop | Type | Default | Description |
|
|
348
|
+
| --------- | ------------------- | -------------------- | ------------------- |
|
|
349
|
+
| `icon` | `IconProps \| null` | `SearchIcon` | Empty state icon |
|
|
350
|
+
| `message` | `string \| null` | `"No results found"` | Empty state message |
|
|
290
351
|
|
|
291
|
-
|
|
352
|
+
### `<Combobox.Create>`
|
|
292
353
|
|
|
293
354
|
Option to create new values when none match search.
|
|
294
355
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
| values | string[] \| Option[] | undefined | Existing values |
|
|
301
|
-
| onCreate | (value: string \| Option) => void | undefined | Creation callback |
|
|
356
|
+
| Prop | Type | Default | Description |
|
|
357
|
+
| ------------- | ----------------------------------- | ----------- | ------------------------ |
|
|
358
|
+
| `leadingText` | `string` | `"Create"` | Text before search value |
|
|
359
|
+
| `values` | `string[] \| Option[]` | `undefined` | Existing values |
|
|
360
|
+
| `onCreate` | `(value: string \| Option) => void` | `undefined` | Creation callback |
|
|
302
361
|
|
|
303
362
|
### Primitives
|
|
304
363
|
|
|
305
|
-
The combobox includes
|
|
364
|
+
The combobox includes primitive components for advanced customization:
|
|
306
365
|
|
|
307
366
|
#### Trigger Primitives
|
|
308
367
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
| Name | Type | Default | Description |
|
|
316
|
-
| ----------- | --------- | ----------- | ----------------------------------- |
|
|
317
|
-
| icon | IconProps | ChevronDown | The icon to display |
|
|
318
|
-
| aria-hidden | boolean | true | Whether to hide from screen readers |
|
|
319
|
-
|
|
320
|
-
```tsx
|
|
321
|
-
<Combobox.Primitives.TriggerIndicator icon={CustomIcon} aria-hidden={false} />
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
##### `<Combobox.Primitives.TriggerClear/>`
|
|
325
|
-
|
|
326
|
-
A button to clear the current selection(s). Only shown when `clearable` is true and there are selections.
|
|
327
|
-
|
|
328
|
-
###### Props
|
|
329
|
-
|
|
330
|
-
| Name | Type | Default | Description |
|
|
331
|
-
| ------------ | ---------------------------------- | --------- | --------------------- |
|
|
332
|
-
| tooltipProps | TgphComponentProps<typeof Tooltip> | undefined | Props for the tooltip |
|
|
333
|
-
|
|
334
|
-
Accepts all props from `Button`
|
|
335
|
-
|
|
336
|
-
```tsx
|
|
337
|
-
<Combobox.Primitives.TriggerClear tooltipProps={{ delay: 300 }} />
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
##### `<Combobox.Primitives.TriggerText/>`
|
|
341
|
-
|
|
342
|
-
Displays the selected value's text or label. Used in single-select mode.
|
|
343
|
-
|
|
344
|
-
###### Props
|
|
345
|
-
|
|
346
|
-
Accepts all props from `Button.Text`
|
|
347
|
-
|
|
348
|
-
```tsx
|
|
349
|
-
<Combobox.Primitives.TriggerText color="blue" weight="medium" />
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
##### `<Combobox.Primitives.TriggerPlaceholder/>`
|
|
353
|
-
|
|
354
|
-
Shows placeholder text when no value is selected.
|
|
355
|
-
|
|
356
|
-
###### Props
|
|
357
|
-
|
|
358
|
-
Accepts all props from `Button.Text`
|
|
359
|
-
|
|
360
|
-
```tsx
|
|
361
|
-
<Combobox.Primitives.TriggerPlaceholder color="gray-8" />
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
##### `<Combobox.Primitives.TriggerTagsContainer/>`
|
|
365
|
-
|
|
366
|
-
Container for selected value tags in multi-select mode. Handles tag layout and truncation.
|
|
367
|
-
|
|
368
|
-
###### Props
|
|
369
|
-
|
|
370
|
-
Accepts all props from `Stack`
|
|
371
|
-
|
|
372
|
-
```tsx
|
|
373
|
-
<Combobox.Primitives.TriggerTagsContainer gap="2" wrap="wrap" />
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
##### `<Combobox.Primitives.TriggerTag/>`
|
|
377
|
-
|
|
378
|
-
A collection of components for building custom tags in multi-select mode:
|
|
379
|
-
|
|
380
|
-
###### `<Combobox.Primitives.TriggerTag.Root/>`
|
|
381
|
-
|
|
382
|
-
The base container for a tag.
|
|
383
|
-
|
|
384
|
-
Props:
|
|
385
|
-
| Name | Type | Default | Description |
|
|
386
|
-
| ---- | ---- | ------- | ----------- |
|
|
387
|
-
| value | string | required | The value this tag represents |
|
|
388
|
-
|
|
389
|
-
###### `<Combobox.Primitives.TriggerTag.Text/>`
|
|
390
|
-
|
|
391
|
-
The text content of a tag.
|
|
392
|
-
|
|
393
|
-
Props: Accepts all props from `Text`
|
|
394
|
-
|
|
395
|
-
###### `<Combobox.Primitives.TriggerTag.Button/>`
|
|
396
|
-
|
|
397
|
-
The remove button for a tag.
|
|
398
|
-
|
|
399
|
-
Props: Accepts all props from `Button`
|
|
400
|
-
|
|
401
|
-
###### `<Combobox.Primitives.TriggerTag.Default/>`
|
|
368
|
+
- **`<Combobox.Primitives.TriggerIndicator>`** - Dropdown chevron icon
|
|
369
|
+
- **`<Combobox.Primitives.TriggerClear>`** - Clear button
|
|
370
|
+
- **`<Combobox.Primitives.TriggerText>`** - Selected value text
|
|
371
|
+
- **`<Combobox.Primitives.TriggerPlaceholder>`** - Placeholder text
|
|
372
|
+
- **`<Combobox.Primitives.TriggerTagsContainer>`** - Multi-select tags container
|
|
373
|
+
- **`<Combobox.Primitives.TriggerTag.*>`** - Tag components for multi-select
|
|
402
374
|
|
|
403
|
-
|
|
375
|
+
For detailed primitive documentation, see the [Primitives](#primitives) section above.
|
|
404
376
|
|
|
405
|
-
|
|
406
|
-
| Name | Type | Default | Description |
|
|
407
|
-
| ---- | ---- | ------- | ----------- |
|
|
408
|
-
| value | string | required | The value this tag represents |
|
|
377
|
+
## Type Examples
|
|
409
378
|
|
|
410
|
-
|
|
379
|
+
### Single Select Types
|
|
411
380
|
|
|
412
381
|
```tsx
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
<Combobox.Primitives.TriggerTag.Text>
|
|
416
|
-
Custom Tag
|
|
417
|
-
</Combobox.Primitives.TriggerTag.Text>
|
|
418
|
-
<Combobox.Primitives.TriggerTag.Button
|
|
419
|
-
icon={{ icon: XIcon, alt: "Remove" }}
|
|
420
|
-
/>
|
|
421
|
-
</Combobox.Primitives.TriggerTag.Root>
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Example of using the default tag:
|
|
425
|
-
|
|
426
|
-
```tsx
|
|
427
|
-
<Combobox.Primitives.TriggerTag.Default value="example" />
|
|
382
|
+
// String values (recommended)
|
|
383
|
+
const [value, setValue] = useState<string>("");
|
|
428
384
|
```
|
|
429
385
|
|
|
430
|
-
|
|
386
|
+
### Multi Select Types
|
|
431
387
|
|
|
432
388
|
```tsx
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
<Stack direction="row" justify="space-between" align="center" w="full">
|
|
436
|
-
{/* Left side: Text or Tags */}
|
|
437
|
-
<Box flex="1" overflow="hidden">
|
|
438
|
-
<Combobox.Primitives.TriggerTagsContainer>
|
|
439
|
-
<Combobox.Primitives.TriggerTag.Default value="tag1" />
|
|
440
|
-
<Combobox.Primitives.TriggerTag.Default value="tag2" />
|
|
441
|
-
</Combobox.Primitives.TriggerTagsContainer>
|
|
442
|
-
</Box>
|
|
443
|
-
|
|
444
|
-
{/* Right side: Clear and Indicator */}
|
|
445
|
-
<Stack direction="row" gap="1" align="center">
|
|
446
|
-
<Combobox.Primitives.TriggerClear />
|
|
447
|
-
<Box borderLeft="1" h="4" />
|
|
448
|
-
<Combobox.Primitives.TriggerIndicator />
|
|
449
|
-
</Stack>
|
|
450
|
-
</Stack>
|
|
451
|
-
</Combobox.Trigger>
|
|
452
|
-
<Combobox.Content>{/* ... */}</Combobox.Content>
|
|
453
|
-
</Combobox.Root>
|
|
389
|
+
// String array values (recommended)
|
|
390
|
+
const [values, setValues] = useState<string[]>([]);
|
|
454
391
|
```
|
|
455
392
|
|
|
456
393
|
### Legacy Behavior (⚠️ Deprecated)
|
|
457
394
|
|
|
458
|
-
> **Warning**: Legacy behavior is deprecated and will be removed in a future version.
|
|
459
|
-
|
|
460
|
-
The `legacyBehavior` prop changes how values are handled:
|
|
461
|
-
|
|
462
|
-
- When `false` (default, recommended): Values are simple strings
|
|
463
|
-
- When `true` (deprecated): Values are objects with `{ value: string; label?: string }`
|
|
464
|
-
|
|
465
|
-
#### Legacy Single Select Types
|
|
395
|
+
> **Warning**: Legacy behavior is deprecated and will be removed in a future version.
|
|
466
396
|
|
|
467
397
|
```tsx
|
|
468
398
|
// ⚠️ Deprecated - Don't use in new code
|
|
469
|
-
const [value, setValue] = useState<{ value: string; label?: string }>()
|
|
399
|
+
const [value, setValue] = useState<{ value: string; label?: string }>();
|
|
470
400
|
|
|
471
401
|
<Combobox.Root
|
|
472
402
|
value={value}
|
|
@@ -475,17 +405,15 @@ const [value, setValue] = useState<{ value: string; label?: string }>()
|
|
|
475
405
|
>
|
|
476
406
|
```
|
|
477
407
|
|
|
478
|
-
|
|
408
|
+
## References
|
|
479
409
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const [values, setValues] = useState<Array<{ value: string; label?: string }>>([])
|
|
410
|
+
- [Storybook Demo](https://storybook.telegraph.dev/?path=/docs/combobox)
|
|
411
|
+
- [ARIA Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)
|
|
483
412
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
```
|
|
413
|
+
## Contributing
|
|
414
|
+
|
|
415
|
+
See our [Contributing Guide](../../CONTRIBUTING.md) for more details.
|
|
416
|
+
|
|
417
|
+
## License
|
|
490
418
|
|
|
491
|
-
|
|
419
|
+
MIT License - see [LICENSE](../../LICENSE) for details.
|