@neynar/ui 1.0.1 → 1.0.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/context7.json +17 -0
- package/llm/components/accordion.llm.md +205 -0
- package/llm/components/alert-dialog.llm.md +289 -0
- package/llm/components/alert.llm.md +310 -0
- package/llm/components/aspect-ratio.llm.md +110 -0
- package/llm/components/avatar.llm.md +282 -0
- package/llm/components/badge.llm.md +185 -0
- package/llm/components/blockquote.llm.md +86 -0
- package/llm/components/breadcrumb.llm.md +245 -0
- package/llm/components/button-group.llm.md +248 -0
- package/llm/components/button.llm.md +247 -0
- package/llm/components/calendar.llm.md +252 -0
- package/llm/components/card.llm.md +356 -0
- package/llm/components/carousel.llm.md +281 -0
- package/llm/components/chart.llm.md +278 -0
- package/llm/components/checkbox.llm.md +234 -0
- package/llm/components/code.llm.md +75 -0
- package/llm/components/collapsible.llm.md +271 -0
- package/llm/components/color-mode.llm.md +196 -0
- package/llm/components/combobox.llm.md +346 -0
- package/llm/components/command.llm.md +353 -0
- package/llm/components/context-menu.llm.md +368 -0
- package/llm/components/dialog.llm.md +283 -0
- package/llm/components/drawer.llm.md +326 -0
- package/llm/components/dropdown-menu.llm.md +404 -0
- package/llm/components/empty.llm.md +282 -0
- package/llm/components/field.llm.md +303 -0
- package/llm/components/first-light.llm.md +129 -0
- package/llm/components/hover-card.llm.md +278 -0
- package/llm/components/input-group.llm.md +334 -0
- package/llm/components/input-otp.llm.md +270 -0
- package/llm/components/input.llm.md +197 -0
- package/llm/components/item.llm.md +347 -0
- package/llm/components/kbd.llm.md +221 -0
- package/llm/components/label.llm.md +219 -0
- package/llm/components/menubar.llm.md +378 -0
- package/llm/components/navigation-menu.llm.md +320 -0
- package/llm/components/pagination.llm.md +337 -0
- package/llm/components/popover.llm.md +278 -0
- package/llm/components/progress.llm.md +259 -0
- package/llm/components/radio-group.llm.md +269 -0
- package/llm/components/resizable.llm.md +222 -0
- package/llm/components/scroll-area.llm.md +290 -0
- package/llm/components/select.llm.md +338 -0
- package/llm/components/separator.llm.md +129 -0
- package/llm/components/sheet.llm.md +275 -0
- package/llm/components/sidebar.llm.md +528 -0
- package/llm/components/skeleton.llm.md +140 -0
- package/llm/components/slider.llm.md +213 -0
- package/llm/components/sonner.llm.md +299 -0
- package/llm/components/spinner.llm.md +187 -0
- package/llm/components/switch.llm.md +258 -0
- package/llm/components/table.llm.md +334 -0
- package/llm/components/tabs.llm.md +245 -0
- package/llm/components/text.llm.md +108 -0
- package/llm/components/textarea.llm.md +236 -0
- package/llm/components/title.llm.md +88 -0
- package/llm/components/toggle-group.llm.md +228 -0
- package/llm/components/toggle.llm.md +235 -0
- package/llm/components/tooltip.llm.md +191 -0
- package/llm/contributing.llm.md +273 -0
- package/llm/hooks.llm.md +91 -0
- package/llm/index.llm.md +178 -0
- package/llm/theming.llm.md +381 -0
- package/llm/utilities.llm.md +97 -0
- package/llms-full.txt +15995 -0
- package/llms.txt +182 -0
- package/package.json +5 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Color Mode
|
|
2
|
+
|
|
3
|
+
SSR-safe color mode system with automatic system preference detection, cookie persistence, and FOUC prevention.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
ColorModeInitializer,
|
|
10
|
+
ColorModeToggle,
|
|
11
|
+
useColorMode,
|
|
12
|
+
type ColorModePreference,
|
|
13
|
+
} from "@neynar/ui/color-mode"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
|
|
18
|
+
| Component | Description |
|
|
19
|
+
|-----------|-------------|
|
|
20
|
+
| ColorModeInitializer | Inline script preventing flash of wrong color mode |
|
|
21
|
+
| ColorModeToggle | Dropdown button for switching between system/light/dark |
|
|
22
|
+
| useColorMode | Hook for programmatic color mode control |
|
|
23
|
+
|
|
24
|
+
## ColorModeInitializer
|
|
25
|
+
|
|
26
|
+
Prevents flash of unstyled content (FOUC) by applying the correct color mode before first paint.
|
|
27
|
+
|
|
28
|
+
### Usage
|
|
29
|
+
|
|
30
|
+
Place in your root layout's `<head>`:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// app/layout.tsx
|
|
34
|
+
import { ColorModeInitializer } from "@neynar/ui/color-mode";
|
|
35
|
+
|
|
36
|
+
export default function RootLayout({ children }) {
|
|
37
|
+
return (
|
|
38
|
+
<html lang="en" suppressHydrationWarning>
|
|
39
|
+
<head>
|
|
40
|
+
<ColorModeInitializer />
|
|
41
|
+
</head>
|
|
42
|
+
<body>{children}</body>
|
|
43
|
+
</html>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### How It Works
|
|
49
|
+
|
|
50
|
+
1. Reads `color-mode` cookie for saved preference
|
|
51
|
+
2. Falls back to `prefers-color-scheme` media query
|
|
52
|
+
3. Applies `dark` class to `<html>` if needed
|
|
53
|
+
4. Sets `color-scheme` CSS property for native elements
|
|
54
|
+
5. Executes synchronously before first paint
|
|
55
|
+
|
|
56
|
+
### Props
|
|
57
|
+
|
|
58
|
+
No props. Zero configuration.
|
|
59
|
+
|
|
60
|
+
## ColorModeToggle
|
|
61
|
+
|
|
62
|
+
Dropdown button for switching between system, light, and dark modes.
|
|
63
|
+
|
|
64
|
+
### Props
|
|
65
|
+
|
|
66
|
+
| Prop | Type | Default | Description |
|
|
67
|
+
|------|------|---------|-------------|
|
|
68
|
+
| size | "xs" \| "sm" \| "default" \| "lg" | "default" | Button size |
|
|
69
|
+
| showLabel | boolean | false | Show mode name next to icon |
|
|
70
|
+
| align | "start" \| "center" \| "end" | "end" | Dropdown alignment |
|
|
71
|
+
| variant | ButtonVariant | "outline" | Button visual style |
|
|
72
|
+
|
|
73
|
+
Extends Button props (excluding `size`).
|
|
74
|
+
|
|
75
|
+
### Examples
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Icon-only (default)
|
|
79
|
+
<ColorModeToggle />
|
|
80
|
+
|
|
81
|
+
// With label
|
|
82
|
+
<ColorModeToggle showLabel />
|
|
83
|
+
|
|
84
|
+
// Small ghost button
|
|
85
|
+
<ColorModeToggle variant="ghost" size="sm" />
|
|
86
|
+
|
|
87
|
+
// In a header
|
|
88
|
+
<header className="flex justify-between p-4">
|
|
89
|
+
<Logo />
|
|
90
|
+
<ColorModeToggle />
|
|
91
|
+
</header>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## useColorMode
|
|
95
|
+
|
|
96
|
+
Hook for programmatic color mode control.
|
|
97
|
+
|
|
98
|
+
### Returns
|
|
99
|
+
|
|
100
|
+
| Property | Type | Description |
|
|
101
|
+
|----------|------|-------------|
|
|
102
|
+
| preference | "system" \| "light" \| "dark" | User's saved preference |
|
|
103
|
+
| mode | "light" \| "dark" | Actual resolved mode |
|
|
104
|
+
| setPreference | (pref: ColorModePreference) => void | Update preference |
|
|
105
|
+
|
|
106
|
+
### Examples
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// Toggle between modes
|
|
110
|
+
function ToggleButton() {
|
|
111
|
+
const { mode, setPreference } = useColorMode();
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<button onClick={() => setPreference(mode === "dark" ? "light" : "dark")}>
|
|
115
|
+
Currently: {mode}
|
|
116
|
+
</button>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Settings dropdown
|
|
121
|
+
function ColorModeSelect() {
|
|
122
|
+
const { preference, setPreference } = useColorMode();
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<select
|
|
126
|
+
value={preference}
|
|
127
|
+
onChange={(e) => setPreference(e.target.value as ColorModePreference)}
|
|
128
|
+
>
|
|
129
|
+
<option value="system">System</option>
|
|
130
|
+
<option value="light">Light</option>
|
|
131
|
+
<option value="dark">Dark</option>
|
|
132
|
+
</select>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Conditional rendering
|
|
137
|
+
function ThemedIcon() {
|
|
138
|
+
const { mode } = useColorMode();
|
|
139
|
+
return mode === "dark" ? <MoonIcon /> : <SunIcon />;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Cookie Format
|
|
144
|
+
|
|
145
|
+
Stored in `color-mode` cookie as JSON:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"preference": "system" | "light" | "dark",
|
|
150
|
+
"mode": "light" | "dark"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `preference`: What user selected
|
|
155
|
+
- `mode`: Resolved value (for SSR)
|
|
156
|
+
- Max age: 1 year
|
|
157
|
+
- SameSite: lax
|
|
158
|
+
|
|
159
|
+
## Event System
|
|
160
|
+
|
|
161
|
+
Color mode changes broadcast via custom event:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// Listen for changes
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const handler = (e: CustomEvent) => {
|
|
167
|
+
console.log('Mode changed:', e.detail.mode);
|
|
168
|
+
};
|
|
169
|
+
window.addEventListener('color-mode-change', handler);
|
|
170
|
+
return () => window.removeEventListener('color-mode-change', handler);
|
|
171
|
+
}, []);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Multiple ColorModeToggle instances stay synchronized automatically.
|
|
175
|
+
|
|
176
|
+
## Data Attributes
|
|
177
|
+
|
|
178
|
+
Applied to `<html>` element:
|
|
179
|
+
|
|
180
|
+
| Attribute | Value |
|
|
181
|
+
|-----------|-------|
|
|
182
|
+
| class | "dark" when dark mode active |
|
|
183
|
+
| style.colorScheme | "light" \| "dark" |
|
|
184
|
+
|
|
185
|
+
## Accessibility
|
|
186
|
+
|
|
187
|
+
- ColorModeToggle has full keyboard navigation
|
|
188
|
+
- Screen reader announces current selection
|
|
189
|
+
- Focus indicators meet WCAG 2.1 AA
|
|
190
|
+
- Respects `prefers-color-scheme` for system mode
|
|
191
|
+
|
|
192
|
+
## Related
|
|
193
|
+
|
|
194
|
+
- [Theming](../theming.llm.md) - CSS variables, themes, customization
|
|
195
|
+
- [Button](./button.llm.md) - ColorModeToggle uses Button internally
|
|
196
|
+
- [DropdownMenu](./dropdown-menu.llm.md) - ColorModeToggle uses DropdownMenu
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Combobox
|
|
2
|
+
|
|
3
|
+
Searchable select component supporting single and multi-select with optional chips UI.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
Combobox,
|
|
10
|
+
ComboboxInput,
|
|
11
|
+
ComboboxContent,
|
|
12
|
+
ComboboxList,
|
|
13
|
+
ComboboxItem,
|
|
14
|
+
ComboboxGroup,
|
|
15
|
+
ComboboxLabel,
|
|
16
|
+
ComboboxEmpty,
|
|
17
|
+
ComboboxSeparator,
|
|
18
|
+
ComboboxChips,
|
|
19
|
+
ComboboxChip,
|
|
20
|
+
ComboboxChipsInput,
|
|
21
|
+
useComboboxAnchor,
|
|
22
|
+
} from "@neynar/ui/combobox"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Anatomy
|
|
26
|
+
|
|
27
|
+
### Single Select
|
|
28
|
+
```tsx
|
|
29
|
+
<Combobox>
|
|
30
|
+
<ComboboxInput />
|
|
31
|
+
<ComboboxContent>
|
|
32
|
+
<ComboboxList>
|
|
33
|
+
<ComboboxEmpty />
|
|
34
|
+
<ComboboxItem />
|
|
35
|
+
</ComboboxList>
|
|
36
|
+
</ComboboxContent>
|
|
37
|
+
</Combobox>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Multi-Select with Chips
|
|
41
|
+
```tsx
|
|
42
|
+
const anchorRef = useComboboxAnchor()
|
|
43
|
+
|
|
44
|
+
<Combobox multiple>
|
|
45
|
+
<ComboboxChips ref={anchorRef}>
|
|
46
|
+
<ComboboxChip />
|
|
47
|
+
<ComboboxChipsInput />
|
|
48
|
+
</ComboboxChips>
|
|
49
|
+
<ComboboxContent anchor={anchorRef}>
|
|
50
|
+
<ComboboxList>
|
|
51
|
+
<ComboboxItem />
|
|
52
|
+
</ComboboxList>
|
|
53
|
+
</ComboboxContent>
|
|
54
|
+
</Combobox>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Components
|
|
58
|
+
|
|
59
|
+
| Component | Description |
|
|
60
|
+
|-----------|-------------|
|
|
61
|
+
| Combobox | Root component managing state (doesn't render element) |
|
|
62
|
+
| ComboboxInput | Text input with optional trigger/clear buttons for single-select |
|
|
63
|
+
| ComboboxContent | Popup container with automatic portal and positioning |
|
|
64
|
+
| ComboboxList | Scrollable container for items with overflow handling |
|
|
65
|
+
| ComboboxItem | Selectable item with check indicator when selected |
|
|
66
|
+
| ComboboxChips | Container for selected chips in multi-select mode |
|
|
67
|
+
| ComboboxChip | Individual chip with optional remove button |
|
|
68
|
+
| ComboboxChipsInput | Text input for multi-select (use inside ComboboxChips) |
|
|
69
|
+
| ComboboxGroup | Groups related items together |
|
|
70
|
+
| ComboboxLabel | Label for item groups |
|
|
71
|
+
| ComboboxEmpty | Message shown when no results match search |
|
|
72
|
+
| ComboboxSeparator | Visual divider between groups |
|
|
73
|
+
| useComboboxAnchor | Hook returning ref for anchoring popup to chips |
|
|
74
|
+
|
|
75
|
+
## Props
|
|
76
|
+
|
|
77
|
+
### Combobox
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
|------|------|---------|-------------|
|
|
81
|
+
| value | string \| string[] \| null | - | Controlled selected value(s) |
|
|
82
|
+
| onValueChange | (value) => void | - | Called when selection changes |
|
|
83
|
+
| defaultValue | string \| string[] \| null | - | Uncontrolled initial value |
|
|
84
|
+
| inputValue | string | - | Controlled input text value |
|
|
85
|
+
| onInputValueChange | (value) => void | - | Called when input text changes |
|
|
86
|
+
| defaultInputValue | string | - | Uncontrolled initial input text |
|
|
87
|
+
| multiple | boolean | false | Enable multi-select mode |
|
|
88
|
+
| disabled | boolean | false | Disable all interactions |
|
|
89
|
+
| open | boolean | - | Controlled open state |
|
|
90
|
+
| onOpenChange | (open) => void | - | Called when popup opens/closes |
|
|
91
|
+
| openOnInputClick | boolean | true | Whether clicking input opens popup |
|
|
92
|
+
|
|
93
|
+
### ComboboxInput
|
|
94
|
+
|
|
95
|
+
Single-select input wrapped in InputGroup with optional addons.
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Description |
|
|
98
|
+
|------|------|---------|-------------|
|
|
99
|
+
| showTrigger | boolean | true | Show dropdown trigger button |
|
|
100
|
+
| showClear | boolean | false | Show clear button when value selected |
|
|
101
|
+
| placeholder | string | - | Input placeholder text |
|
|
102
|
+
| disabled | boolean | false | Disable input |
|
|
103
|
+
|
|
104
|
+
### ComboboxContent
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Default | Description |
|
|
107
|
+
|------|------|---------|-------------|
|
|
108
|
+
| side | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | Popup placement side |
|
|
109
|
+
| align | 'start' \| 'center' \| 'end' | 'start' | Popup alignment |
|
|
110
|
+
| sideOffset | number | 6 | Distance from anchor |
|
|
111
|
+
| alignOffset | number | 0 | Alignment adjustment |
|
|
112
|
+
| anchor | RefObject | - | Ref to anchor element (for chips mode) |
|
|
113
|
+
|
|
114
|
+
**Anchoring for Multi-Select:**
|
|
115
|
+
```tsx
|
|
116
|
+
const anchorRef = useComboboxAnchor()
|
|
117
|
+
<ComboboxChips ref={anchorRef}>...</ComboboxChips>
|
|
118
|
+
<ComboboxContent anchor={anchorRef}>...</ComboboxContent>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### ComboboxItem
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Default | Description |
|
|
124
|
+
|------|------|---------|-------------|
|
|
125
|
+
| value | any | - | **Required**. Unique value identifying this item |
|
|
126
|
+
| disabled | boolean | false | Disable item interaction |
|
|
127
|
+
| onClick | MouseEventHandler | - | Click handler for item selection |
|
|
128
|
+
|
|
129
|
+
### ComboboxChips
|
|
130
|
+
|
|
131
|
+
Container for chips in multi-select mode. Must be used with `useComboboxAnchor()` hook.
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Default | Description |
|
|
134
|
+
|------|------|---------|-------------|
|
|
135
|
+
| ref | RefObject | - | Pass ref from useComboboxAnchor() |
|
|
136
|
+
| children | ReactNode | - | ComboboxChip and ComboboxChipsInput elements |
|
|
137
|
+
|
|
138
|
+
### ComboboxChip
|
|
139
|
+
|
|
140
|
+
| Prop | Type | Default | Description |
|
|
141
|
+
|------|------|---------|-------------|
|
|
142
|
+
| showRemove | boolean | true | Show X button to remove chip |
|
|
143
|
+
| children | ReactNode | - | Chip content (usually text/icon) |
|
|
144
|
+
|
|
145
|
+
### ComboboxChipsInput
|
|
146
|
+
|
|
147
|
+
| Prop | Type | Default | Description |
|
|
148
|
+
|------|------|---------|-------------|
|
|
149
|
+
| placeholder | string | - | Input placeholder text |
|
|
150
|
+
|
|
151
|
+
## Data Attributes
|
|
152
|
+
|
|
153
|
+
### ComboboxInput
|
|
154
|
+
- `data-popup-open` - Popup is open
|
|
155
|
+
- `data-disabled` - Input is disabled
|
|
156
|
+
- `data-focused` - Input has focus
|
|
157
|
+
|
|
158
|
+
### ComboboxContent
|
|
159
|
+
- `data-open` - Popup is open (triggers animations)
|
|
160
|
+
- `data-closed` - Popup is closed (triggers animations)
|
|
161
|
+
- `data-side` - Current placement side (top/bottom/left/right)
|
|
162
|
+
- `data-chips` - Anchored to chips container (multi-select)
|
|
163
|
+
|
|
164
|
+
### ComboboxItem
|
|
165
|
+
- `data-selected` - Item is selected
|
|
166
|
+
- `data-highlighted` - Item is keyboard highlighted
|
|
167
|
+
- `data-disabled` - Item is disabled
|
|
168
|
+
|
|
169
|
+
### ComboboxList
|
|
170
|
+
- `data-empty` - List has no items (shows ComboboxEmpty)
|
|
171
|
+
|
|
172
|
+
## Examples
|
|
173
|
+
|
|
174
|
+
### Basic Single Select
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
function FrameworkPicker() {
|
|
178
|
+
const [value, setValue] = useState<string | null>(null)
|
|
179
|
+
|
|
180
|
+
const frameworks = [
|
|
181
|
+
{ value: "next", label: "Next.js" },
|
|
182
|
+
{ value: "react", label: "React" },
|
|
183
|
+
{ value: "vue", label: "Vue" },
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
|
188
|
+
<ComboboxInput placeholder="Select framework..." showClear />
|
|
189
|
+
<ComboboxContent>
|
|
190
|
+
<ComboboxList>
|
|
191
|
+
<ComboboxEmpty>No frameworks found</ComboboxEmpty>
|
|
192
|
+
{frameworks.map((fw) => (
|
|
193
|
+
<ComboboxItem key={fw.value} value={fw.value}>
|
|
194
|
+
{fw.label}
|
|
195
|
+
</ComboboxItem>
|
|
196
|
+
))}
|
|
197
|
+
</ComboboxList>
|
|
198
|
+
</ComboboxContent>
|
|
199
|
+
</Combobox>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Multi-Select with Chips
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
function TagPicker() {
|
|
208
|
+
const [tags, setTags] = useState<string[]>([])
|
|
209
|
+
const anchorRef = useComboboxAnchor()
|
|
210
|
+
|
|
211
|
+
const availableTags = ["react", "typescript", "nextjs", "tailwind"]
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<Combobox multiple value={tags} onValueChange={(v) => setTags(v as string[])}>
|
|
215
|
+
<ComboboxChips ref={anchorRef}>
|
|
216
|
+
{tags.map((tag) => (
|
|
217
|
+
<ComboboxChip key={tag}>{tag}</ComboboxChip>
|
|
218
|
+
))}
|
|
219
|
+
<ComboboxChipsInput placeholder="Add tags..." />
|
|
220
|
+
</ComboboxChips>
|
|
221
|
+
<ComboboxContent anchor={anchorRef}>
|
|
222
|
+
<ComboboxList>
|
|
223
|
+
<ComboboxEmpty>No tags found</ComboboxEmpty>
|
|
224
|
+
{availableTags.map((tag) => (
|
|
225
|
+
<ComboboxItem key={tag} value={tag}>
|
|
226
|
+
{tag}
|
|
227
|
+
</ComboboxItem>
|
|
228
|
+
))}
|
|
229
|
+
</ComboboxList>
|
|
230
|
+
</ComboboxContent>
|
|
231
|
+
</Combobox>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Grouped Items with Labels
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
function FoodPicker() {
|
|
240
|
+
const [value, setValue] = useState<string | null>(null)
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
|
244
|
+
<ComboboxInput placeholder="Select food..." showClear />
|
|
245
|
+
<ComboboxContent>
|
|
246
|
+
<ComboboxList>
|
|
247
|
+
<ComboboxEmpty>No food found</ComboboxEmpty>
|
|
248
|
+
<ComboboxGroup>
|
|
249
|
+
<ComboboxLabel>Fruits</ComboboxLabel>
|
|
250
|
+
<ComboboxItem value="apple">Apple</ComboboxItem>
|
|
251
|
+
<ComboboxItem value="banana">Banana</ComboboxItem>
|
|
252
|
+
</ComboboxGroup>
|
|
253
|
+
<ComboboxSeparator />
|
|
254
|
+
<ComboboxGroup>
|
|
255
|
+
<ComboboxLabel>Vegetables</ComboboxLabel>
|
|
256
|
+
<ComboboxItem value="carrot">Carrot</ComboboxItem>
|
|
257
|
+
<ComboboxItem value="broccoli">Broccoli</ComboboxItem>
|
|
258
|
+
</ComboboxGroup>
|
|
259
|
+
</ComboboxList>
|
|
260
|
+
</ComboboxContent>
|
|
261
|
+
</Combobox>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Items with Icons and Metadata
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
function ChannelPicker() {
|
|
270
|
+
const [channel, setChannel] = useState<string | null>(null)
|
|
271
|
+
|
|
272
|
+
const channels = [
|
|
273
|
+
{ id: "1", name: "farcaster", subscribers: 89450 },
|
|
274
|
+
{ id: "2", name: "base", subscribers: 45230 },
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<Combobox value={channel} onValueChange={(v) => setChannel(v as string)}>
|
|
279
|
+
<ComboboxInput placeholder="Search channels..." showClear />
|
|
280
|
+
<ComboboxContent>
|
|
281
|
+
<ComboboxList>
|
|
282
|
+
<ComboboxEmpty>No channels found</ComboboxEmpty>
|
|
283
|
+
{channels.map((ch) => (
|
|
284
|
+
<ComboboxItem key={ch.id} value={ch.id}>
|
|
285
|
+
<HashIcon className="text-muted-foreground size-4" />
|
|
286
|
+
<div className="flex-1">
|
|
287
|
+
<div className="font-medium">/{ch.name}</div>
|
|
288
|
+
<div className="text-muted-foreground text-xs">
|
|
289
|
+
{ch.subscribers.toLocaleString()} subscribers
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</ComboboxItem>
|
|
293
|
+
))}
|
|
294
|
+
</ComboboxList>
|
|
295
|
+
</ComboboxContent>
|
|
296
|
+
</Combobox>
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Without Trigger Button
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
<Combobox>
|
|
305
|
+
<ComboboxInput
|
|
306
|
+
placeholder="Type to search..."
|
|
307
|
+
showTrigger={false}
|
|
308
|
+
showClear
|
|
309
|
+
/>
|
|
310
|
+
<ComboboxContent>
|
|
311
|
+
<ComboboxList>
|
|
312
|
+
<ComboboxItem value="item1">Item 1</ComboboxItem>
|
|
313
|
+
</ComboboxList>
|
|
314
|
+
</ComboboxContent>
|
|
315
|
+
</Combobox>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Keyboard Interactions
|
|
319
|
+
|
|
320
|
+
| Key | Action |
|
|
321
|
+
|-----|--------|
|
|
322
|
+
| ArrowDown | Highlight next item (opens popup if closed) |
|
|
323
|
+
| ArrowUp | Highlight previous item (opens popup if closed) |
|
|
324
|
+
| Enter | Select highlighted item and close popup |
|
|
325
|
+
| Escape | Close popup and clear highlight |
|
|
326
|
+
| Home | Highlight first item |
|
|
327
|
+
| End | Highlight last item |
|
|
328
|
+
| Tab | Close popup and move focus |
|
|
329
|
+
| Backspace | Remove last chip (multi-select, when input empty) |
|
|
330
|
+
|
|
331
|
+
## Accessibility
|
|
332
|
+
|
|
333
|
+
- ARIA combobox pattern with `role="combobox"` on input
|
|
334
|
+
- `aria-expanded` indicates popup state
|
|
335
|
+
- `aria-controls` links input to popup listbox
|
|
336
|
+
- `aria-activedescendant` tracks highlighted item
|
|
337
|
+
- Items use `role="option"` with `aria-selected` state
|
|
338
|
+
- Automatic focus management when opening/closing
|
|
339
|
+
- Screen readers announce selection changes and item counts
|
|
340
|
+
- Keyboard navigation follows ARIA authoring practices
|
|
341
|
+
|
|
342
|
+
## Related
|
|
343
|
+
|
|
344
|
+
- [Select](/components/select.llm.md) - Simple dropdown without search
|
|
345
|
+
- [Input](/components/input.llm.md) - Basic text input
|
|
346
|
+
- [Command](/components/command.llm.md) - Command palette pattern
|