@neynar/ui 1.0.1 → 1.0.3
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,303 @@
|
|
|
1
|
+
# Field
|
|
2
|
+
|
|
3
|
+
Comprehensive form field system with labels, descriptions, error handling, and flexible layouts for building accessible forms.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
Field,
|
|
10
|
+
FieldLabel,
|
|
11
|
+
FieldContent,
|
|
12
|
+
FieldDescription,
|
|
13
|
+
FieldError,
|
|
14
|
+
FieldGroup,
|
|
15
|
+
FieldSet,
|
|
16
|
+
FieldLegend,
|
|
17
|
+
FieldSeparator,
|
|
18
|
+
FieldTitle,
|
|
19
|
+
} from "@neynar/ui/field"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Anatomy
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<Field orientation="vertical" data-invalid={hasError}>
|
|
26
|
+
<FieldLabel htmlFor="input-id">Label Text</FieldLabel>
|
|
27
|
+
<FieldContent>
|
|
28
|
+
<Input id="input-id" />
|
|
29
|
+
<FieldDescription>Helper text</FieldDescription>
|
|
30
|
+
<FieldError errors={errors} />
|
|
31
|
+
</FieldContent>
|
|
32
|
+
</Field>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Components
|
|
36
|
+
|
|
37
|
+
| Component | Description |
|
|
38
|
+
|-----------|-------------|
|
|
39
|
+
| Field | Base field wrapper with orientation support |
|
|
40
|
+
| FieldLabel | Label for the field input |
|
|
41
|
+
| FieldContent | Container for input + description + error |
|
|
42
|
+
| FieldDescription | Helper text providing context |
|
|
43
|
+
| FieldError | Error message display with auto-deduplication |
|
|
44
|
+
| FieldGroup | Container for multiple related fields |
|
|
45
|
+
| FieldSet | Semantic fieldset container for field groups |
|
|
46
|
+
| FieldLegend | Legend for FieldSet with variant styles |
|
|
47
|
+
| FieldSeparator | Visual separator between field sections |
|
|
48
|
+
| FieldTitle | Title text for checkbox/switch fields |
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
### Field
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Description |
|
|
55
|
+
|------|------|---------|-------------|
|
|
56
|
+
| orientation | "vertical" \| "horizontal" \| "responsive" | "vertical" | Layout orientation |
|
|
57
|
+
| data-invalid | boolean | - | Apply error styling when true |
|
|
58
|
+
| data-disabled | boolean | - | Apply disabled styling when true |
|
|
59
|
+
|
|
60
|
+
### FieldLabel
|
|
61
|
+
|
|
62
|
+
Extends Label component. Use `htmlFor` to associate with input.
|
|
63
|
+
|
|
64
|
+
### FieldContent
|
|
65
|
+
|
|
66
|
+
Container with consistent spacing for input + description + error.
|
|
67
|
+
|
|
68
|
+
### FieldDescription
|
|
69
|
+
|
|
70
|
+
Helper text automatically styled for placement. Supports links with underline styling.
|
|
71
|
+
|
|
72
|
+
### FieldError
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Default | Description |
|
|
75
|
+
|------|------|---------|-------------|
|
|
76
|
+
| errors | Array<{ message?: string }> | - | Error objects to display |
|
|
77
|
+
| children | ReactNode | - | Custom error content |
|
|
78
|
+
|
|
79
|
+
Automatically deduplicates errors by message. Shows single error as text, multiple errors as bulleted list.
|
|
80
|
+
|
|
81
|
+
### FieldGroup
|
|
82
|
+
|
|
83
|
+
Container for multiple fields with consistent spacing. Provides `@container/field-group` for responsive layouts.
|
|
84
|
+
|
|
85
|
+
### FieldSet
|
|
86
|
+
|
|
87
|
+
Semantic `<fieldset>` element for grouping related fields. Use with FieldLegend.
|
|
88
|
+
|
|
89
|
+
### FieldLegend
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Default | Description |
|
|
92
|
+
|------|------|---------|-------------|
|
|
93
|
+
| variant | "legend" \| "label" | "legend" | Visual style variant |
|
|
94
|
+
|
|
95
|
+
### FieldSeparator
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Description |
|
|
98
|
+
|------|------|---------|-------------|
|
|
99
|
+
| children | ReactNode | - | Optional label text on separator |
|
|
100
|
+
|
|
101
|
+
### FieldTitle
|
|
102
|
+
|
|
103
|
+
Title text for fields with embedded controls (checkboxes, switches). Use inside FieldLabel.
|
|
104
|
+
|
|
105
|
+
## Orientation Variants
|
|
106
|
+
|
|
107
|
+
| Variant | Behavior |
|
|
108
|
+
|---------|----------|
|
|
109
|
+
| vertical | Label and input stacked vertically (default) |
|
|
110
|
+
| horizontal | Label and input side by side |
|
|
111
|
+
| responsive | Vertical on mobile, horizontal on larger screens (requires FieldGroup) |
|
|
112
|
+
|
|
113
|
+
## Data Attributes
|
|
114
|
+
|
|
115
|
+
| Attribute | When Present | Used For |
|
|
116
|
+
|-----------|--------------|----------|
|
|
117
|
+
| data-invalid | Field has errors | Error styling |
|
|
118
|
+
| data-disabled | Field is disabled | Disabled styling |
|
|
119
|
+
| data-orientation | Always | Current orientation value |
|
|
120
|
+
| data-slot | Always | Component identification |
|
|
121
|
+
|
|
122
|
+
## Examples
|
|
123
|
+
|
|
124
|
+
### Basic Field
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
<Field>
|
|
128
|
+
<FieldLabel htmlFor="username">Username</FieldLabel>
|
|
129
|
+
<Input id="username" placeholder="Enter username" />
|
|
130
|
+
</Field>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Field with Description
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<Field>
|
|
137
|
+
<FieldLabel htmlFor="email">Email Address</FieldLabel>
|
|
138
|
+
<FieldContent>
|
|
139
|
+
<Input id="email" type="email" placeholder="your@email.com" />
|
|
140
|
+
<FieldDescription>We'll never share your email</FieldDescription>
|
|
141
|
+
</FieldContent>
|
|
142
|
+
</Field>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Field with Error State
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const [errors, setErrors] = useState({ email: { message: "Invalid email" } })
|
|
149
|
+
|
|
150
|
+
<Field data-invalid={!!errors.email}>
|
|
151
|
+
<FieldLabel htmlFor="email">Email *</FieldLabel>
|
|
152
|
+
<FieldContent>
|
|
153
|
+
<Input
|
|
154
|
+
id="email"
|
|
155
|
+
type="email"
|
|
156
|
+
aria-invalid={!!errors.email}
|
|
157
|
+
/>
|
|
158
|
+
<FieldError errors={[errors.email]} />
|
|
159
|
+
</FieldContent>
|
|
160
|
+
</Field>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Horizontal Layout with Switch
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<Field orientation="horizontal">
|
|
167
|
+
<FieldLabel htmlFor="notifications">
|
|
168
|
+
<Switch id="notifications" />
|
|
169
|
+
<div>
|
|
170
|
+
<FieldTitle>Enable Notifications</FieldTitle>
|
|
171
|
+
<FieldDescription>Receive email updates</FieldDescription>
|
|
172
|
+
</div>
|
|
173
|
+
</FieldLabel>
|
|
174
|
+
</Field>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Grouped Fields with Separator
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<FieldGroup>
|
|
181
|
+
<Field>
|
|
182
|
+
<FieldLabel htmlFor="name">Name</FieldLabel>
|
|
183
|
+
<Input id="name" />
|
|
184
|
+
</Field>
|
|
185
|
+
|
|
186
|
+
<FieldSeparator>Security Settings</FieldSeparator>
|
|
187
|
+
|
|
188
|
+
<Field>
|
|
189
|
+
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
190
|
+
<Input id="password" type="password" />
|
|
191
|
+
</Field>
|
|
192
|
+
</FieldGroup>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Field Set with Radio Group
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<FieldSet>
|
|
199
|
+
<FieldLegend variant="label">Event Type</FieldLegend>
|
|
200
|
+
<FieldGroup>
|
|
201
|
+
<RadioGroup value={value} onValueChange={setValue}>
|
|
202
|
+
<Field orientation="horizontal">
|
|
203
|
+
<FieldLabel htmlFor="option-1">
|
|
204
|
+
<RadioGroupItem value="option1" id="option-1" />
|
|
205
|
+
<div>
|
|
206
|
+
<FieldTitle>Option One</FieldTitle>
|
|
207
|
+
<FieldDescription>First choice description</FieldDescription>
|
|
208
|
+
</div>
|
|
209
|
+
</FieldLabel>
|
|
210
|
+
</Field>
|
|
211
|
+
|
|
212
|
+
<Field orientation="horizontal">
|
|
213
|
+
<FieldLabel htmlFor="option-2">
|
|
214
|
+
<RadioGroupItem value="option2" id="option-2" />
|
|
215
|
+
<div>
|
|
216
|
+
<FieldTitle>Option Two</FieldTitle>
|
|
217
|
+
<FieldDescription>Second choice description</FieldDescription>
|
|
218
|
+
</div>
|
|
219
|
+
</FieldLabel>
|
|
220
|
+
</Field>
|
|
221
|
+
</RadioGroup>
|
|
222
|
+
</FieldGroup>
|
|
223
|
+
</FieldSet>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Multiple Errors
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
<Field data-invalid={true}>
|
|
230
|
+
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
231
|
+
<FieldContent>
|
|
232
|
+
<Input id="password" type="password" aria-invalid={true} />
|
|
233
|
+
<FieldError
|
|
234
|
+
errors={[
|
|
235
|
+
{ message: "Must be at least 8 characters" },
|
|
236
|
+
{ message: "Must contain a number" },
|
|
237
|
+
{ message: "Must contain a special character" },
|
|
238
|
+
]}
|
|
239
|
+
/>
|
|
240
|
+
</FieldContent>
|
|
241
|
+
</Field>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Responsive Layout
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
<FieldGroup>
|
|
248
|
+
<Field orientation="responsive">
|
|
249
|
+
<FieldLabel htmlFor="phone">Phone Number</FieldLabel>
|
|
250
|
+
<FieldContent>
|
|
251
|
+
<Input id="phone" placeholder="+1 (555) 123-4567" />
|
|
252
|
+
<FieldDescription>
|
|
253
|
+
Vertical on mobile, horizontal on larger screens
|
|
254
|
+
</FieldDescription>
|
|
255
|
+
</FieldContent>
|
|
256
|
+
</Field>
|
|
257
|
+
</FieldGroup>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Disabled Field
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
<Field data-disabled={true}>
|
|
264
|
+
<FieldLabel htmlFor="readonly">Read Only Field</FieldLabel>
|
|
265
|
+
<FieldContent>
|
|
266
|
+
<Input
|
|
267
|
+
id="readonly"
|
|
268
|
+
disabled
|
|
269
|
+
defaultValue="Cannot edit"
|
|
270
|
+
/>
|
|
271
|
+
<FieldDescription>This field cannot be modified</FieldDescription>
|
|
272
|
+
</FieldContent>
|
|
273
|
+
</Field>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Accessibility
|
|
277
|
+
|
|
278
|
+
- Uses semantic HTML elements (`fieldset`, `legend`, `label`)
|
|
279
|
+
- FieldError uses `role="alert"` for screen reader announcements
|
|
280
|
+
- Supports `aria-invalid` on inputs for error states
|
|
281
|
+
- FieldLabel properly associates with inputs via `htmlFor`
|
|
282
|
+
- FieldDescription provides additional context without cluttering labels
|
|
283
|
+
- Disabled state reduces opacity for visual indication
|
|
284
|
+
|
|
285
|
+
## Best Practices
|
|
286
|
+
|
|
287
|
+
- Always use `htmlFor` on FieldLabel to associate with input `id`
|
|
288
|
+
- Set `data-invalid` on Field and `aria-invalid` on input for error states
|
|
289
|
+
- Use FieldContent when you have description or error messages
|
|
290
|
+
- For checkbox/switch fields, use horizontal orientation with FieldTitle
|
|
291
|
+
- Use FieldSet/FieldLegend for semantically related field groups
|
|
292
|
+
- Use FieldGroup for visual grouping without semantic fieldset
|
|
293
|
+
- Set `data-disabled` on Field when input is disabled
|
|
294
|
+
- Use FieldSeparator to organize long forms into sections
|
|
295
|
+
|
|
296
|
+
## Related
|
|
297
|
+
|
|
298
|
+
- [Input](./input.llm.md) - Text input component
|
|
299
|
+
- [Textarea](./textarea.llm.md) - Multi-line text input
|
|
300
|
+
- [Checkbox](./checkbox.llm.md) - Checkbox input
|
|
301
|
+
- [Switch](./switch.llm.md) - Toggle switch
|
|
302
|
+
- [Radio Group](./radio-group.llm.md) - Radio button group
|
|
303
|
+
- [Label](./label.llm.md) - Base label component
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# First Light
|
|
2
|
+
|
|
3
|
+
SVG filter component that enables hand-drawn wobble effects for the First Light theme.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { FirstLightFilters } from "@neynar/ui/first-light"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Render once in your root layout when using the First Light theme:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// app/layout.tsx
|
|
17
|
+
import "@neynar/ui/themes/first-light";
|
|
18
|
+
import { FirstLightFilters } from "@neynar/ui/first-light";
|
|
19
|
+
import { ColorModeInitializer } from "@neynar/ui/color-mode";
|
|
20
|
+
|
|
21
|
+
export default function Layout({ children }) {
|
|
22
|
+
return (
|
|
23
|
+
<html lang="en" suppressHydrationWarning>
|
|
24
|
+
<head>
|
|
25
|
+
<ColorModeInitializer />
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<FirstLightFilters />
|
|
29
|
+
{children}
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
No props. Zero configuration.
|
|
39
|
+
|
|
40
|
+
## How It Works
|
|
41
|
+
|
|
42
|
+
Renders an invisible SVG containing `<filter>` definitions that create displacement effects using fractal noise. The First Light theme CSS references these filters via `filter: url(#first-light-filter)`.
|
|
43
|
+
|
|
44
|
+
Without this component, the theme works but edges are straight instead of wobbly.
|
|
45
|
+
|
|
46
|
+
## Filter Definitions
|
|
47
|
+
|
|
48
|
+
| Filter ID | Effect | Use Case |
|
|
49
|
+
|-----------|--------|----------|
|
|
50
|
+
| `#first-light-filter` | Standard wobble | Default, applied to most components |
|
|
51
|
+
| `#first-light-filter-light` | Subtle wobble | Barely noticeable, for delicate elements |
|
|
52
|
+
| `#first-light-filter-heavy` | Pronounced wobble | More dramatic hand-drawn effect |
|
|
53
|
+
|
|
54
|
+
### Filter Parameters
|
|
55
|
+
|
|
56
|
+
| Filter | Base Frequency | Octaves | Scale |
|
|
57
|
+
|--------|----------------|---------|-------|
|
|
58
|
+
| Standard | 0.015 | 2 | 1.5 |
|
|
59
|
+
| Light | 0.006 | 1 | 0.4 |
|
|
60
|
+
| Heavy | 0.012 | 3 | 1.5 |
|
|
61
|
+
|
|
62
|
+
## CSS Classes
|
|
63
|
+
|
|
64
|
+
The First Light theme provides utility classes to apply filters:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Apply heavier wobble
|
|
68
|
+
<Card className="first-light-heavy">...</Card>
|
|
69
|
+
|
|
70
|
+
// Apply lighter wobble
|
|
71
|
+
<Button className="first-light-light">Subtle</Button>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Class | Effect |
|
|
75
|
+
|-------|--------|
|
|
76
|
+
| `.first-light-light` | Applies `#first-light-filter-light` |
|
|
77
|
+
| `.first-light-heavy` | Applies `#first-light-filter-heavy` |
|
|
78
|
+
|
|
79
|
+
## Technical Details
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// The component renders this (simplified):
|
|
83
|
+
<svg aria-hidden="true" style={{ position: "absolute", width: 0, height: 0 }}>
|
|
84
|
+
<defs>
|
|
85
|
+
<filter id="first-light-filter">
|
|
86
|
+
<feTurbulence type="fractalNoise" baseFrequency="0.015" numOctaves="2" />
|
|
87
|
+
<feDisplacementMap scale="1.5" xChannelSelector="R" yChannelSelector="G" />
|
|
88
|
+
</filter>
|
|
89
|
+
{/* + light and heavy variants */}
|
|
90
|
+
</defs>
|
|
91
|
+
</svg>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Uses `feTurbulence` for organic noise generation
|
|
95
|
+
- Uses `feDisplacementMap` to distort element edges
|
|
96
|
+
- Each filter uses different seed for variety
|
|
97
|
+
- Filter extends beyond element bounds (x="-3%" etc.) to avoid clipping
|
|
98
|
+
|
|
99
|
+
## Theme Integration
|
|
100
|
+
|
|
101
|
+
The First Light theme automatically applies filters to components via CSS:
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
/* In first-light.css */
|
|
105
|
+
.theme-first-light [data-slot="card"],
|
|
106
|
+
.theme-first-light [data-slot="button"],
|
|
107
|
+
/* ... etc ... */ {
|
|
108
|
+
filter: url(#first-light-filter);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Performance
|
|
113
|
+
|
|
114
|
+
- SVG is hidden and takes no layout space
|
|
115
|
+
- Filters are GPU-accelerated in modern browsers
|
|
116
|
+
- Minimal memory footprint (single SVG with 3 filters)
|
|
117
|
+
- No runtime JavaScript after initial render
|
|
118
|
+
|
|
119
|
+
## Accessibility
|
|
120
|
+
|
|
121
|
+
- SVG has `aria-hidden="true"`
|
|
122
|
+
- Purely decorative - no impact on screen readers
|
|
123
|
+
- Does not affect content readability
|
|
124
|
+
- Motion-safe (no animation)
|
|
125
|
+
|
|
126
|
+
## Related
|
|
127
|
+
|
|
128
|
+
- [Theming](../theming.llm.md) - Full theming documentation
|
|
129
|
+
- First Light theme: `@neynar/ui/themes/first-light`
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# HoverCard
|
|
2
|
+
|
|
3
|
+
Displays rich preview content when hovering over a trigger element.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { HoverCard, HoverCardTrigger, HoverCardContent } from "@neynar/ui/hover-card"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<HoverCard>
|
|
15
|
+
<HoverCardTrigger>
|
|
16
|
+
<a href="#">Hover over me</a>
|
|
17
|
+
</HoverCardTrigger>
|
|
18
|
+
<HoverCardContent>
|
|
19
|
+
Preview content appears here
|
|
20
|
+
</HoverCardContent>
|
|
21
|
+
</HoverCard>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Components
|
|
25
|
+
|
|
26
|
+
| Component | Description |
|
|
27
|
+
|-----------|-------------|
|
|
28
|
+
| HoverCard | Root container, manages hover state and timing |
|
|
29
|
+
| HoverCardTrigger | Element that triggers the hover card (link, button, text, avatar) |
|
|
30
|
+
| HoverCardContent | Content container with automatic portal, positioning, and animations |
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
### HoverCard
|
|
35
|
+
|
|
36
|
+
Inherits all props from `@base-ui/react/preview-card` Root component.
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|------|------|---------|-------------|
|
|
40
|
+
| open | boolean | - | Controlled open state |
|
|
41
|
+
| defaultOpen | boolean | false | Uncontrolled initial open state |
|
|
42
|
+
| onOpenChange | (open: boolean) => void | - | Called when open state changes |
|
|
43
|
+
|
|
44
|
+
### HoverCardTrigger
|
|
45
|
+
|
|
46
|
+
Inherits all props from `@base-ui/react/preview-card` Trigger component.
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Description |
|
|
49
|
+
|------|------|---------|-------------|
|
|
50
|
+
| render | ReactElement \| function | - | Custom element or render function for the trigger |
|
|
51
|
+
|
|
52
|
+
The `render` prop accepts a ReactElement or function for customization:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// As a ReactElement
|
|
56
|
+
<HoverCardTrigger render={<Button variant="outline">Hover</Button>}>
|
|
57
|
+
Content
|
|
58
|
+
</HoverCardTrigger>
|
|
59
|
+
|
|
60
|
+
// As a function
|
|
61
|
+
<HoverCardTrigger render={(props) => <a {...props} href="#">Link</a>}>
|
|
62
|
+
Content
|
|
63
|
+
</HoverCardTrigger>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### HoverCardContent
|
|
67
|
+
|
|
68
|
+
Automatically renders portal and positioner. Combines props from Popup and Positioner components.
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
|------|------|---------|-------------|
|
|
72
|
+
| side | "top" \| "right" \| "bottom" \| "left" | "bottom" | Which side of trigger to position the card |
|
|
73
|
+
| sideOffset | number | 4 | Distance from trigger in pixels |
|
|
74
|
+
| align | "start" \| "center" \| "end" | "center" | How to align relative to the trigger |
|
|
75
|
+
| alignOffset | number | 4 | Additional alignment axis offset in pixels |
|
|
76
|
+
| className | string | - | Custom CSS classes (merged with defaults) |
|
|
77
|
+
| initialFocus | boolean \| RefObject \| function | - | Element to focus when opened |
|
|
78
|
+
| finalFocus | boolean \| RefObject \| function | - | Element to focus when closed |
|
|
79
|
+
|
|
80
|
+
## Data Attributes
|
|
81
|
+
|
|
82
|
+
Use these for custom styling and animations:
|
|
83
|
+
|
|
84
|
+
| Attribute | When Present | Description |
|
|
85
|
+
|-----------|--------------|-------------|
|
|
86
|
+
| data-open | Card is open | Apply open state styles |
|
|
87
|
+
| data-closed | Card is closed | Apply closed state styles |
|
|
88
|
+
| data-side | Always | Value: "top" \| "right" \| "bottom" \| "left" |
|
|
89
|
+
| data-align | Always | Value: "start" \| "center" \| "end" |
|
|
90
|
+
| data-starting-style | Opening animation | Initial animation state |
|
|
91
|
+
| data-ending-style | Closing animation | Final animation state |
|
|
92
|
+
|
|
93
|
+
## Examples
|
|
94
|
+
|
|
95
|
+
### User Profile Preview
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<HoverCard>
|
|
99
|
+
<HoverCardTrigger>
|
|
100
|
+
<button className="inline-flex items-center gap-2">
|
|
101
|
+
<Avatar size="sm">
|
|
102
|
+
<AvatarImage src="/user.jpg" />
|
|
103
|
+
<AvatarFallback>DR</AvatarFallback>
|
|
104
|
+
</Avatar>
|
|
105
|
+
<span className="font-medium">@dwr</span>
|
|
106
|
+
</button>
|
|
107
|
+
</HoverCardTrigger>
|
|
108
|
+
<HoverCardContent className="w-80">
|
|
109
|
+
<div className="space-y-3">
|
|
110
|
+
<div className="flex items-start justify-between">
|
|
111
|
+
<div className="flex items-center gap-3">
|
|
112
|
+
<Avatar size="lg">
|
|
113
|
+
<AvatarImage src="/user.jpg" />
|
|
114
|
+
<AvatarFallback>DR</AvatarFallback>
|
|
115
|
+
</Avatar>
|
|
116
|
+
<div>
|
|
117
|
+
<p className="font-semibold">Dan Romero</p>
|
|
118
|
+
<p className="text-muted-foreground text-sm">@dwr</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<Button size="sm">Follow</Button>
|
|
122
|
+
</div>
|
|
123
|
+
<p className="text-sm">
|
|
124
|
+
Building Farcaster and Warpcast. Previously VP Eng at Coinbase.
|
|
125
|
+
</p>
|
|
126
|
+
<div className="flex items-center gap-4 text-sm">
|
|
127
|
+
<div>
|
|
128
|
+
<span className="font-semibold">124.5K</span>{" "}
|
|
129
|
+
<span className="text-muted-foreground">followers</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
<span className="font-semibold">428</span>{" "}
|
|
133
|
+
<span className="text-muted-foreground">following</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</HoverCardContent>
|
|
138
|
+
</HoverCard>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Simple Text Preview
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<p>
|
|
145
|
+
Learn more about{" "}
|
|
146
|
+
<HoverCard>
|
|
147
|
+
<HoverCardTrigger>
|
|
148
|
+
<a href="#" className="text-primary hover:underline">
|
|
149
|
+
typography
|
|
150
|
+
</a>
|
|
151
|
+
</HoverCardTrigger>
|
|
152
|
+
<HoverCardContent>
|
|
153
|
+
<p className="text-sm">
|
|
154
|
+
Typography is the art and science of arranging type to make written
|
|
155
|
+
language clear, visually appealing, and effective.
|
|
156
|
+
</p>
|
|
157
|
+
</HoverCardContent>
|
|
158
|
+
</HoverCard>
|
|
159
|
+
{" "}in design.
|
|
160
|
+
</p>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Positioned Above with Custom Width
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<HoverCard>
|
|
167
|
+
<HoverCardTrigger>
|
|
168
|
+
<Badge variant="secondary" className="cursor-pointer">
|
|
169
|
+
React 19
|
|
170
|
+
</Badge>
|
|
171
|
+
</HoverCardTrigger>
|
|
172
|
+
<HoverCardContent side="top" className="w-96">
|
|
173
|
+
<div className="space-y-2">
|
|
174
|
+
<h4 className="text-sm font-semibold">React 19</h4>
|
|
175
|
+
<p className="text-muted-foreground text-xs leading-relaxed">
|
|
176
|
+
Latest version of React with improved concurrent rendering,
|
|
177
|
+
automatic batching, and new hooks for better performance.
|
|
178
|
+
</p>
|
|
179
|
+
</div>
|
|
180
|
+
</HoverCardContent>
|
|
181
|
+
</HoverCard>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Rich Content with Avatar and Actions
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
<HoverCard>
|
|
188
|
+
<HoverCardTrigger>
|
|
189
|
+
<Button variant="outline">Farcaster Protocol</Button>
|
|
190
|
+
</HoverCardTrigger>
|
|
191
|
+
<HoverCardContent className="w-72">
|
|
192
|
+
<div className="space-y-3">
|
|
193
|
+
<div className="flex items-start gap-3">
|
|
194
|
+
<Avatar size="sm">
|
|
195
|
+
<AvatarFallback>FC</AvatarFallback>
|
|
196
|
+
</Avatar>
|
|
197
|
+
<div className="flex-1 space-y-1">
|
|
198
|
+
<h4 className="text-sm font-semibold">Farcaster Protocol</h4>
|
|
199
|
+
<p className="text-muted-foreground text-xs">
|
|
200
|
+
A sufficiently decentralized social network.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div className="flex gap-2">
|
|
205
|
+
<Button size="xs" variant="secondary">Learn More</Button>
|
|
206
|
+
<Button size="xs" variant="outline">Documentation</Button>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</HoverCardContent>
|
|
210
|
+
</HoverCard>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Multiple Triggers in Feed
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<div className="space-y-4">
|
|
217
|
+
{posts.map((post) => (
|
|
218
|
+
<div key={post.id} className="border rounded-lg p-4">
|
|
219
|
+
<div className="mb-2 flex items-center gap-2">
|
|
220
|
+
<HoverCard>
|
|
221
|
+
<HoverCardTrigger>
|
|
222
|
+
<button className="inline-flex items-center gap-2">
|
|
223
|
+
<Avatar size="sm">
|
|
224
|
+
<AvatarImage src={post.author.avatar} />
|
|
225
|
+
<AvatarFallback>{post.author.initials}</AvatarFallback>
|
|
226
|
+
</Avatar>
|
|
227
|
+
<span className="font-medium hover:underline">
|
|
228
|
+
@{post.author.username}
|
|
229
|
+
</span>
|
|
230
|
+
</button>
|
|
231
|
+
</HoverCardTrigger>
|
|
232
|
+
<HoverCardContent className="w-80">
|
|
233
|
+
<UserProfilePreview user={post.author} />
|
|
234
|
+
</HoverCardContent>
|
|
235
|
+
</HoverCard>
|
|
236
|
+
<span className="text-muted-foreground text-sm">{post.time}</span>
|
|
237
|
+
</div>
|
|
238
|
+
<p className="text-sm">{post.content}</p>
|
|
239
|
+
</div>
|
|
240
|
+
))}
|
|
241
|
+
</div>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Keyboard
|
|
245
|
+
|
|
246
|
+
HoverCard is primarily mouse/touch interaction, but keyboard navigation is supported:
|
|
247
|
+
|
|
248
|
+
| Key | Action |
|
|
249
|
+
|-----|--------|
|
|
250
|
+
| Hover | Open hover card after delay |
|
|
251
|
+
| Mouse leave | Close hover card after delay |
|
|
252
|
+
| Tab | Navigate through interactive elements inside content |
|
|
253
|
+
| Escape | Close hover card immediately |
|
|
254
|
+
|
|
255
|
+
## Accessibility
|
|
256
|
+
|
|
257
|
+
- Automatically manages focus when opening/closing
|
|
258
|
+
- Content is rendered in a portal to avoid z-index issues
|
|
259
|
+
- Uses ARIA attributes for screen reader support
|
|
260
|
+
- Supports keyboard navigation for interactive content
|
|
261
|
+
- Trigger remains accessible and keyboard-navigable
|
|
262
|
+
- Respects user's motion preferences for animations
|
|
263
|
+
|
|
264
|
+
## Styling Notes
|
|
265
|
+
|
|
266
|
+
- Default width is `w-64` (256px), customize with `className`
|
|
267
|
+
- Uses frosted glass effect with `bg-popover` at 75% opacity and backdrop blur
|
|
268
|
+
- Includes entry/exit animations via data attributes
|
|
269
|
+
- Side-specific slide animations (slides from opposite direction)
|
|
270
|
+
- Respects `--transform-origin` CSS variable for proper animation origins
|
|
271
|
+
- Compatible with all themes (classic, frosted, sketch)
|
|
272
|
+
|
|
273
|
+
## Related
|
|
274
|
+
|
|
275
|
+
- [Tooltip](/components/tooltip.llm.md) - For simple text hints (non-interactive)
|
|
276
|
+
- [Popover](/components/popover.llm.md) - For click-triggered interactive content
|
|
277
|
+
- [Dialog](/components/dialog.llm.md) - For modal overlays
|
|
278
|
+
- [Avatar](/components/avatar.llm.md) - Commonly used in hover card triggers
|