@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,213 @@
|
|
|
1
|
+
# Slider
|
|
2
|
+
|
|
3
|
+
Slider for selecting single values or ranges with one or more draggable thumbs.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Slider } from "@neynar/ui/slider"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Slider defaultValue={[50]} min={0} max={100} />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
All props from Base UI Slider.Root are supported.
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|------|------|---------|-------------|
|
|
23
|
+
| defaultValue | number \| number[] | - | Uncontrolled initial value. Use array for ranges. |
|
|
24
|
+
| value | number \| number[] | - | Controlled value. Use array for ranges. |
|
|
25
|
+
| onValueChange | (value: number \| number[], eventDetails) => void | - | Called when value changes during interaction. |
|
|
26
|
+
| onValueCommitted | (value: number \| number[], eventDetails) => void | - | Called when interaction completes (pointerup). |
|
|
27
|
+
| min | number | 0 | Minimum value. |
|
|
28
|
+
| max | number | 100 | Maximum value. |
|
|
29
|
+
| step | number | 1 | Step increment for value changes. |
|
|
30
|
+
| largeStep | number | 10 | Step for Page Up/Down or Shift + Arrow keys. |
|
|
31
|
+
| minStepsBetweenValues | number | - | Minimum steps between thumbs in range slider. |
|
|
32
|
+
| disabled | boolean | false | Disables all interactions. |
|
|
33
|
+
| orientation | "horizontal" \| "vertical" | "horizontal" | Slider direction. |
|
|
34
|
+
| thumbAlignment | "center" \| "edge" \| "edge-client-only" | "edge" | How thumbs align at min/max positions. |
|
|
35
|
+
| thumbCollisionBehavior | "none" \| "push" \| "swap" | "push" | How thumbs interact when they collide. |
|
|
36
|
+
| name | string | - | Form field name for submission. |
|
|
37
|
+
| className | string | - | Additional classes for the control element. |
|
|
38
|
+
|
|
39
|
+
### Value Format
|
|
40
|
+
|
|
41
|
+
- **Single value**: `defaultValue={[50]}` or `value={[50]}`
|
|
42
|
+
- **Range (two thumbs)**: `defaultValue={[25, 75]}`
|
|
43
|
+
- **Multiple thumbs**: `defaultValue={[20, 40, 60, 80]}`
|
|
44
|
+
|
|
45
|
+
Values are always arrays. Component automatically creates the correct number of thumbs based on array length.
|
|
46
|
+
|
|
47
|
+
### Thumb Alignment
|
|
48
|
+
|
|
49
|
+
Set to `"edge"` by default (thumbs align to edges at min/max):
|
|
50
|
+
- `"center"` - Thumb centers align at min/max
|
|
51
|
+
- `"edge"` - Thumb edges align at min/max (default)
|
|
52
|
+
- `"edge-client-only"` - Like edge but only on client
|
|
53
|
+
|
|
54
|
+
## Data Attributes
|
|
55
|
+
|
|
56
|
+
Applied to control element for styling:
|
|
57
|
+
|
|
58
|
+
| Attribute | When Present |
|
|
59
|
+
|-----------|--------------|
|
|
60
|
+
| data-disabled | Slider is disabled |
|
|
61
|
+
| data-horizontal | Orientation is horizontal |
|
|
62
|
+
| data-vertical | Orientation is vertical |
|
|
63
|
+
|
|
64
|
+
Applied to individual elements via `data-slot`:
|
|
65
|
+
- `data-slot="slider"` - Root element
|
|
66
|
+
- `data-slot="slider-track"` - Track element
|
|
67
|
+
- `data-slot="slider-range"` - Filled indicator
|
|
68
|
+
- `data-slot="slider-thumb"` - Draggable thumb(s)
|
|
69
|
+
|
|
70
|
+
## Examples
|
|
71
|
+
|
|
72
|
+
### Basic Single Value
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
function VolumeControl() {
|
|
76
|
+
return (
|
|
77
|
+
<div className="space-y-2">
|
|
78
|
+
<label>Volume</label>
|
|
79
|
+
<Slider defaultValue={[50]} min={0} max={100} />
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Controlled with Display
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
function RateLimitControl() {
|
|
89
|
+
const [value, setValue] = useState([60])
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="space-y-2">
|
|
93
|
+
<div className="flex justify-between">
|
|
94
|
+
<label>Requests per minute</label>
|
|
95
|
+
<span className="font-mono">{value[0]}</span>
|
|
96
|
+
</div>
|
|
97
|
+
<Slider
|
|
98
|
+
value={value}
|
|
99
|
+
onValueChange={(v) => setValue(Array.isArray(v) ? [...v] : [v])}
|
|
100
|
+
min={10}
|
|
101
|
+
max={200}
|
|
102
|
+
step={10}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Range Selection
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
function PriceRangeFilter() {
|
|
113
|
+
const [range, setRange] = useState([25, 75])
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="space-y-2">
|
|
117
|
+
<label>Price Range: ${range[0]} - ${range[1]}</label>
|
|
118
|
+
<Slider
|
|
119
|
+
value={range}
|
|
120
|
+
onValueChange={(v) => setRange(Array.isArray(v) ? [...v] : [v])}
|
|
121
|
+
min={0}
|
|
122
|
+
max={100}
|
|
123
|
+
step={5}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### With Step Increments
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Slider
|
|
134
|
+
defaultValue={[50]}
|
|
135
|
+
min={0}
|
|
136
|
+
max={100}
|
|
137
|
+
step={25}
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Vertical Orientation
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<div className="h-48">
|
|
145
|
+
<Slider
|
|
146
|
+
defaultValue={[50]}
|
|
147
|
+
min={0}
|
|
148
|
+
max={100}
|
|
149
|
+
orientation="vertical"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Multiple Thresholds
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
function AlertThresholds() {
|
|
158
|
+
const [thresholds, setThresholds] = useState([25, 50, 75])
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div className="space-y-2">
|
|
162
|
+
<label>Alert Levels: {thresholds.join(', ')}</label>
|
|
163
|
+
<Slider
|
|
164
|
+
value={thresholds}
|
|
165
|
+
onValueChange={(v) => setThresholds(Array.isArray(v) ? [...v] : [v])}
|
|
166
|
+
min={0}
|
|
167
|
+
max={100}
|
|
168
|
+
step={5}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Disabled State
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Slider
|
|
179
|
+
defaultValue={[50]}
|
|
180
|
+
min={0}
|
|
181
|
+
max={100}
|
|
182
|
+
disabled
|
|
183
|
+
/>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Keyboard Navigation
|
|
187
|
+
|
|
188
|
+
| Key | Action |
|
|
189
|
+
|-----|--------|
|
|
190
|
+
| Arrow Left/Down | Decrease value by `step` |
|
|
191
|
+
| Arrow Right/Up | Increase value by `step` |
|
|
192
|
+
| Page Down | Decrease by `largeStep` (default 10) |
|
|
193
|
+
| Page Up | Increase by `largeStep` (default 10) |
|
|
194
|
+
| Shift + Arrow | Increase/decrease by `largeStep` |
|
|
195
|
+
| Home | Set to minimum value |
|
|
196
|
+
| End | Set to maximum value |
|
|
197
|
+
| Tab | Focus next thumb |
|
|
198
|
+
| Shift + Tab | Focus previous thumb |
|
|
199
|
+
|
|
200
|
+
## Accessibility
|
|
201
|
+
|
|
202
|
+
- Uses ARIA `slider` role with proper attributes
|
|
203
|
+
- Announces current value, min, max, and orientation to screen readers
|
|
204
|
+
- Full keyboard navigation support
|
|
205
|
+
- Focus indicators on thumbs with hover/focus ring
|
|
206
|
+
- Disabled state prevents interaction and reduces opacity
|
|
207
|
+
- Touch-friendly with adequate thumb size (16px)
|
|
208
|
+
|
|
209
|
+
## Related
|
|
210
|
+
|
|
211
|
+
- [Input](./input.llm.md) - For precise numeric entry
|
|
212
|
+
- [Select](./select.llm.md) - For discrete value selection
|
|
213
|
+
- [RadioGroup](./radio-group.llm.md) - For choosing one option
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Sonner (Toast Notifications)
|
|
2
|
+
|
|
3
|
+
Toast notification system built on Sonner with themed icons and automatic color system integration.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Toaster, toast } from "@neynar/ui/sonner"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// Add once to your app root
|
|
15
|
+
<Toaster position="bottom-right" />
|
|
16
|
+
|
|
17
|
+
// Trigger toasts anywhere in your app
|
|
18
|
+
toast.success("Saved successfully")
|
|
19
|
+
toast.error("Failed to save")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Components
|
|
23
|
+
|
|
24
|
+
| Component | Description |
|
|
25
|
+
|-----------|-------------|
|
|
26
|
+
| Toaster | Container component rendered once at app root. Manages all toast notifications. |
|
|
27
|
+
| toast | Function API for triggering notifications. Includes type variants and promise handling. |
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
### Toaster
|
|
32
|
+
|
|
33
|
+
Pre-configured toast container with custom icons and theme integration. Renders at the specified position and manages all active toasts.
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Default | Description |
|
|
36
|
+
|------|------|---------|-------------|
|
|
37
|
+
| position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "bottom-right" | Screen position for toasts |
|
|
38
|
+
| theme | "light" \| "dark" \| "system" | "system" | Color theme for toasts |
|
|
39
|
+
| richColors | boolean | true | Enhanced colors for toast variants |
|
|
40
|
+
| expand | boolean | false | Expand toasts on hover |
|
|
41
|
+
| visibleToasts | number | 3 | Maximum visible toasts at once |
|
|
42
|
+
| closeButton | boolean | false | Show close button on all toasts |
|
|
43
|
+
| duration | number | 4000 | Default duration in milliseconds |
|
|
44
|
+
| toastOptions | object | - | Global toast styling options |
|
|
45
|
+
|
|
46
|
+
### toast Function API
|
|
47
|
+
|
|
48
|
+
Function for triggering toast notifications. Available variants:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Basic toast
|
|
52
|
+
toast("Message")
|
|
53
|
+
|
|
54
|
+
// Type variants
|
|
55
|
+
toast.success("Success message")
|
|
56
|
+
toast.error("Error message")
|
|
57
|
+
toast.warning("Warning message")
|
|
58
|
+
toast.info("Info message")
|
|
59
|
+
toast.loading("Loading...")
|
|
60
|
+
|
|
61
|
+
// Promise handling
|
|
62
|
+
toast.promise(promise, {
|
|
63
|
+
loading: "Loading...",
|
|
64
|
+
success: "Success!",
|
|
65
|
+
error: "Failed"
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Toast Options
|
|
70
|
+
|
|
71
|
+
All toast functions accept an optional second parameter with these options:
|
|
72
|
+
|
|
73
|
+
| Option | Type | Description |
|
|
74
|
+
|--------|------|-------------|
|
|
75
|
+
| description | string | Secondary text below the title |
|
|
76
|
+
| duration | number | Override default duration (milliseconds) |
|
|
77
|
+
| icon | ReactNode | Custom icon (replaces default) |
|
|
78
|
+
| action | { label: string, onClick: () => void } | Primary action button |
|
|
79
|
+
| cancel | { label: string, onClick: () => void } | Secondary cancel button |
|
|
80
|
+
| id | string | Custom toast ID for updates |
|
|
81
|
+
| position | string | Override global position |
|
|
82
|
+
| dismissible | boolean | Allow manual dismissal (default: true) |
|
|
83
|
+
| onDismiss | () => void | Callback when dismissed |
|
|
84
|
+
| onAutoClose | () => void | Callback when auto-closed |
|
|
85
|
+
|
|
86
|
+
## Customizations
|
|
87
|
+
|
|
88
|
+
### Custom Icons
|
|
89
|
+
|
|
90
|
+
Default icons are automatically provided:
|
|
91
|
+
- Success: `CircleCheckIcon`
|
|
92
|
+
- Error: `OctagonXIcon`
|
|
93
|
+
- Warning: `TriangleAlertIcon`
|
|
94
|
+
- Info: `InfoIcon`
|
|
95
|
+
- Loading: `Loader2Icon` (animated)
|
|
96
|
+
|
|
97
|
+
Override with the `icon` option:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
toast.success("Copied!", {
|
|
101
|
+
icon: <CopyIcon className="size-4" />
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Theme Integration
|
|
106
|
+
|
|
107
|
+
Toasts automatically integrate with your theme's CSS variables:
|
|
108
|
+
- Uses `--popover` colors for normal toasts
|
|
109
|
+
- Uses `--success`, `--destructive`, `--warning`, `--info` for variants
|
|
110
|
+
- Applies `--radius` for border radius
|
|
111
|
+
- Includes backdrop blur effect
|
|
112
|
+
|
|
113
|
+
### Styling
|
|
114
|
+
|
|
115
|
+
Customize appearance via `toastOptions`:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Toaster
|
|
119
|
+
toastOptions={{
|
|
120
|
+
classNames: {
|
|
121
|
+
toast: "custom-toast-class",
|
|
122
|
+
title: "custom-title-class",
|
|
123
|
+
description: "custom-description-class"
|
|
124
|
+
},
|
|
125
|
+
style: {
|
|
126
|
+
background: "var(--custom-bg)",
|
|
127
|
+
border: "1px solid var(--custom-border)"
|
|
128
|
+
}
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
|
|
135
|
+
### Basic Usage
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
function SaveButton() {
|
|
139
|
+
const handleSave = async () => {
|
|
140
|
+
try {
|
|
141
|
+
await saveData()
|
|
142
|
+
toast.success("Data saved successfully")
|
|
143
|
+
} catch (error) {
|
|
144
|
+
toast.error("Failed to save data")
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return <Button onClick={handleSave}>Save</Button>
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### With Description
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
toast.error("Failed to save", {
|
|
156
|
+
description: "Please check your internet connection and try again"
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### With Action Button
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
toast.success("File uploaded", {
|
|
164
|
+
description: "Your file has been uploaded successfully",
|
|
165
|
+
action: {
|
|
166
|
+
label: "View",
|
|
167
|
+
onClick: () => router.push("/files")
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### With Both Actions
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
toast.success("Changes saved", {
|
|
176
|
+
description: "Do you want to publish these changes now?",
|
|
177
|
+
action: {
|
|
178
|
+
label: "Publish",
|
|
179
|
+
onClick: () => publishChanges()
|
|
180
|
+
},
|
|
181
|
+
cancel: {
|
|
182
|
+
label: "Later",
|
|
183
|
+
onClick: () => console.log("Cancelled")
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Promise Handling
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
function CreateButton() {
|
|
192
|
+
const handleCreate = () => {
|
|
193
|
+
const promise = createAPIKey()
|
|
194
|
+
|
|
195
|
+
toast.promise(promise, {
|
|
196
|
+
loading: "Creating API key...",
|
|
197
|
+
success: "API key created successfully",
|
|
198
|
+
error: "Failed to create API key"
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return <Button onClick={handleCreate}>Create Key</Button>
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Promise with Dynamic Messages
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
toast.promise(fetchUser(), {
|
|
210
|
+
loading: "Loading user...",
|
|
211
|
+
success: (data) => ({
|
|
212
|
+
message: `Welcome ${data.name}!`,
|
|
213
|
+
description: data.email,
|
|
214
|
+
duration: 5000
|
|
215
|
+
}),
|
|
216
|
+
error: (error) => ({
|
|
217
|
+
message: "Failed to load user",
|
|
218
|
+
description: error.message
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Loading State Management
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
function RegenerateButton() {
|
|
227
|
+
const handleRegenerate = async () => {
|
|
228
|
+
const toastId = toast.loading("Regenerating API key...", {
|
|
229
|
+
description: "This may take a few seconds"
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const newKey = await regenerateKey()
|
|
234
|
+
toast.success("API key regenerated", {
|
|
235
|
+
id: toastId, // Updates existing toast
|
|
236
|
+
description: "Your old key has been invalidated"
|
|
237
|
+
})
|
|
238
|
+
} catch (error) {
|
|
239
|
+
toast.error("Failed to regenerate", {
|
|
240
|
+
id: toastId
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return <Button onClick={handleRegenerate}>Regenerate</Button>
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Custom Duration
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// Short notification (2 seconds)
|
|
253
|
+
toast.success("Copied!", { duration: 2000 })
|
|
254
|
+
|
|
255
|
+
// Long notification (10 seconds)
|
|
256
|
+
toast.warning("Important update", { duration: 10000 })
|
|
257
|
+
|
|
258
|
+
// Persistent until dismissed
|
|
259
|
+
toast.info("Review required", { duration: Infinity })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Multiple Positions
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// Override global position per toast
|
|
266
|
+
toast.success("Top notification", { position: "top-center" })
|
|
267
|
+
toast.error("Bottom notification", { position: "bottom-left" })
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Stack Multiple Toasts
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
function showBatch() {
|
|
274
|
+
toast.success("First notification")
|
|
275
|
+
setTimeout(() => toast.info("Second notification"), 200)
|
|
276
|
+
setTimeout(() => toast.warning("Third notification"), 400)
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Keyboard
|
|
281
|
+
|
|
282
|
+
| Key | Action |
|
|
283
|
+
|-----|--------|
|
|
284
|
+
| Escape | Dismiss all toasts |
|
|
285
|
+
| Swipe | Dismiss individual toast (touch devices) |
|
|
286
|
+
|
|
287
|
+
## Accessibility
|
|
288
|
+
|
|
289
|
+
- ARIA live regions announce toast messages to screen readers
|
|
290
|
+
- Toasts are automatically announced with appropriate politeness level based on type
|
|
291
|
+
- Keyboard accessible dismissal with Escape key
|
|
292
|
+
- Focus management preserves user context
|
|
293
|
+
- Action buttons are keyboard navigable
|
|
294
|
+
|
|
295
|
+
## Related
|
|
296
|
+
|
|
297
|
+
- [Alert](./alert.llm.md) - Static inline notifications
|
|
298
|
+
- [Alert Dialog](./alert-dialog.llm.md) - Modal confirmations
|
|
299
|
+
- [Dialog](./dialog.llm.md) - Modal dialogs
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Spinner
|
|
2
|
+
|
|
3
|
+
Animated loading spinner for indicating processing states and asynchronous operations.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Spinner } from "@neynar/ui/spinner"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Spinner />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
Extends all standard SVG element props (`React.ComponentProps<"svg">`), including `className`, `aria-*`, and SVG attributes.
|
|
20
|
+
|
|
21
|
+
### Common Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
|------|------|---------|-------------|
|
|
25
|
+
| className | string | - | Custom CSS classes. Default size is `size-4` (16px) |
|
|
26
|
+
| color | string | - | Icon color (inherits from text color by default) |
|
|
27
|
+
| size | number \| string | - | Icon size in pixels or as string |
|
|
28
|
+
| strokeWidth | number \| string | - | Stroke width for the icon |
|
|
29
|
+
|
|
30
|
+
### Built-in Attributes
|
|
31
|
+
|
|
32
|
+
- `role="status"` - Announces loading state to screen readers
|
|
33
|
+
- `aria-label="Loading"` - Provides accessible label
|
|
34
|
+
- `animate-spin` - Continuous rotation animation
|
|
35
|
+
|
|
36
|
+
## Sizing
|
|
37
|
+
|
|
38
|
+
Use Tailwind size utilities via `className`:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Extra small (12px)
|
|
42
|
+
<Spinner className="size-3" />
|
|
43
|
+
|
|
44
|
+
// Small/Default (16px)
|
|
45
|
+
<Spinner className="size-4" />
|
|
46
|
+
|
|
47
|
+
// Medium (24px)
|
|
48
|
+
<Spinner className="size-6" />
|
|
49
|
+
|
|
50
|
+
// Large (32px)
|
|
51
|
+
<Spinner className="size-8" />
|
|
52
|
+
|
|
53
|
+
// Extra large (48px)
|
|
54
|
+
<Spinner className="size-12" />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Basic Loading Indicator
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<div className="flex items-center justify-center">
|
|
63
|
+
<Spinner className="size-8" />
|
|
64
|
+
</div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### With Loading Text
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<div className="flex flex-col items-center gap-3">
|
|
71
|
+
<Spinner className="size-8" />
|
|
72
|
+
<p className="text-sm text-muted-foreground">Loading content...</p>
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### In Button (Loading State)
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<Button disabled>
|
|
80
|
+
<Spinner data-icon="inline-start" />
|
|
81
|
+
Loading...
|
|
82
|
+
</Button>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `data-icon="inline-start"` attribute uses Button's internal spacing for icons at the start position.
|
|
86
|
+
|
|
87
|
+
### Small Button
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Button size="sm" disabled>
|
|
91
|
+
<Spinner data-icon="inline-start" className="size-3.5" />
|
|
92
|
+
Saving...
|
|
93
|
+
</Button>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Inline with Text
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
<Spinner className="size-4" />
|
|
101
|
+
<span className="text-sm">Syncing with Farcaster network...</span>
|
|
102
|
+
</div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Custom Colors
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Primary color
|
|
109
|
+
<Spinner className="size-6 text-primary" />
|
|
110
|
+
|
|
111
|
+
// Custom color
|
|
112
|
+
<Spinner className="size-6 text-blue-600" />
|
|
113
|
+
|
|
114
|
+
// Destructive
|
|
115
|
+
<Spinner className="size-6 text-destructive" />
|
|
116
|
+
|
|
117
|
+
// Muted
|
|
118
|
+
<Spinner className="size-6 text-muted-foreground" />
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Full-Page Loading
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<div className="flex min-h-[400px] items-center justify-center">
|
|
125
|
+
<Spinner className="size-12" />
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Card Loading State
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<Card>
|
|
133
|
+
<CardContent className="flex min-h-[200px] flex-col items-center justify-center gap-3">
|
|
134
|
+
<Spinner className="size-8" />
|
|
135
|
+
<p className="text-sm text-muted-foreground">Loading activity feed...</p>
|
|
136
|
+
</CardContent>
|
|
137
|
+
</Card>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Conditional Loading with Success State
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
function DataLoader() {
|
|
144
|
+
const [status, setStatus] = useState<"loading" | "success">("loading")
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Button onClick={handleLoad} disabled={status === "loading"}>
|
|
148
|
+
{status === "loading" ? (
|
|
149
|
+
<Spinner data-icon="inline-start" />
|
|
150
|
+
) : (
|
|
151
|
+
<CheckCircle2Icon data-icon="inline-start" className="text-green-600" />
|
|
152
|
+
)}
|
|
153
|
+
{status === "loading" ? "Loading..." : "Fetch Data"}
|
|
154
|
+
</Button>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Size Reference
|
|
160
|
+
|
|
161
|
+
| Class | Pixels | Use Case |
|
|
162
|
+
|-------|--------|----------|
|
|
163
|
+
| `size-3` | 12px | Extra small buttons, tight spaces |
|
|
164
|
+
| `size-4` | 16px | Default, inline text, small buttons |
|
|
165
|
+
| `size-6` | 24px | Medium buttons, prominent indicators |
|
|
166
|
+
| `size-8` | 32px | Large buttons, card loading states |
|
|
167
|
+
| `size-12` | 48px | Full page loading, empty states |
|
|
168
|
+
|
|
169
|
+
## Accessibility
|
|
170
|
+
|
|
171
|
+
- Built-in `role="status"` for ARIA live region announcement
|
|
172
|
+
- `aria-label="Loading"` provides screen reader context
|
|
173
|
+
- Animation respects `prefers-reduced-motion` when configured
|
|
174
|
+
- Color contrast should meet WCAG AA standards (use appropriate text colors)
|
|
175
|
+
|
|
176
|
+
## Implementation Notes
|
|
177
|
+
|
|
178
|
+
- Uses Lucide's `Loader2Icon` component
|
|
179
|
+
- Animated via Tailwind's `animate-spin` utility
|
|
180
|
+
- Inherits text color by default for easy theming
|
|
181
|
+
- SVG renders inline for optimal performance
|
|
182
|
+
- No additional JavaScript runtime overhead
|
|
183
|
+
|
|
184
|
+
## Related
|
|
185
|
+
|
|
186
|
+
- [Button](./button.llm.md) - Often used with Spinner for loading states
|
|
187
|
+
- [Skeleton](./skeleton.llm.md) - Alternative loading indicator for content placeholders
|