@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.
Files changed (68) hide show
  1. package/context7.json +17 -0
  2. package/llm/components/accordion.llm.md +205 -0
  3. package/llm/components/alert-dialog.llm.md +289 -0
  4. package/llm/components/alert.llm.md +310 -0
  5. package/llm/components/aspect-ratio.llm.md +110 -0
  6. package/llm/components/avatar.llm.md +282 -0
  7. package/llm/components/badge.llm.md +185 -0
  8. package/llm/components/blockquote.llm.md +86 -0
  9. package/llm/components/breadcrumb.llm.md +245 -0
  10. package/llm/components/button-group.llm.md +248 -0
  11. package/llm/components/button.llm.md +247 -0
  12. package/llm/components/calendar.llm.md +252 -0
  13. package/llm/components/card.llm.md +356 -0
  14. package/llm/components/carousel.llm.md +281 -0
  15. package/llm/components/chart.llm.md +278 -0
  16. package/llm/components/checkbox.llm.md +234 -0
  17. package/llm/components/code.llm.md +75 -0
  18. package/llm/components/collapsible.llm.md +271 -0
  19. package/llm/components/color-mode.llm.md +196 -0
  20. package/llm/components/combobox.llm.md +346 -0
  21. package/llm/components/command.llm.md +353 -0
  22. package/llm/components/context-menu.llm.md +368 -0
  23. package/llm/components/dialog.llm.md +283 -0
  24. package/llm/components/drawer.llm.md +326 -0
  25. package/llm/components/dropdown-menu.llm.md +404 -0
  26. package/llm/components/empty.llm.md +282 -0
  27. package/llm/components/field.llm.md +303 -0
  28. package/llm/components/first-light.llm.md +129 -0
  29. package/llm/components/hover-card.llm.md +278 -0
  30. package/llm/components/input-group.llm.md +334 -0
  31. package/llm/components/input-otp.llm.md +270 -0
  32. package/llm/components/input.llm.md +197 -0
  33. package/llm/components/item.llm.md +347 -0
  34. package/llm/components/kbd.llm.md +221 -0
  35. package/llm/components/label.llm.md +219 -0
  36. package/llm/components/menubar.llm.md +378 -0
  37. package/llm/components/navigation-menu.llm.md +320 -0
  38. package/llm/components/pagination.llm.md +337 -0
  39. package/llm/components/popover.llm.md +278 -0
  40. package/llm/components/progress.llm.md +259 -0
  41. package/llm/components/radio-group.llm.md +269 -0
  42. package/llm/components/resizable.llm.md +222 -0
  43. package/llm/components/scroll-area.llm.md +290 -0
  44. package/llm/components/select.llm.md +338 -0
  45. package/llm/components/separator.llm.md +129 -0
  46. package/llm/components/sheet.llm.md +275 -0
  47. package/llm/components/sidebar.llm.md +528 -0
  48. package/llm/components/skeleton.llm.md +140 -0
  49. package/llm/components/slider.llm.md +213 -0
  50. package/llm/components/sonner.llm.md +299 -0
  51. package/llm/components/spinner.llm.md +187 -0
  52. package/llm/components/switch.llm.md +258 -0
  53. package/llm/components/table.llm.md +334 -0
  54. package/llm/components/tabs.llm.md +245 -0
  55. package/llm/components/text.llm.md +108 -0
  56. package/llm/components/textarea.llm.md +236 -0
  57. package/llm/components/title.llm.md +88 -0
  58. package/llm/components/toggle-group.llm.md +228 -0
  59. package/llm/components/toggle.llm.md +235 -0
  60. package/llm/components/tooltip.llm.md +191 -0
  61. package/llm/contributing.llm.md +273 -0
  62. package/llm/hooks.llm.md +91 -0
  63. package/llm/index.llm.md +178 -0
  64. package/llm/theming.llm.md +381 -0
  65. package/llm/utilities.llm.md +97 -0
  66. package/llms-full.txt +15995 -0
  67. package/llms.txt +182 -0
  68. 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