@neynar/ui 1.0.0 → 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/README.md +1 -1
- 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 +6 -2
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
@neynar/ui theming system using CSS custom properties and Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
**Important:** Styles must be imported via CSS `@import`, not JS imports. This is because the CSS uses `@import "tailwindcss"` which requires Tailwind CSS v4 processing.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
/* In your global CSS file (e.g., globals.css, index.css) */
|
|
11
|
+
@import "@neynar/ui/styles";
|
|
12
|
+
@import "@neynar/ui/themes/purple-dawn";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then import the CSS file in your app entry:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// main.tsx or layout.tsx
|
|
19
|
+
import "./index.css"; // or "./globals.css"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Prerequisite:** Tailwind CSS v4 must be configured. See [llms.txt](../llms.txt) for Vite/Next.js setup.
|
|
23
|
+
|
|
24
|
+
## Concepts
|
|
25
|
+
|
|
26
|
+
- **Theme** = Visual aesthetic (purple-dawn, first-light) - imported via CSS
|
|
27
|
+
- **Color Mode** = Light or dark variant - controlled at runtime via `.dark` class
|
|
28
|
+
|
|
29
|
+
## Available Themes
|
|
30
|
+
|
|
31
|
+
| Theme | Import | Description |
|
|
32
|
+
|-------|--------|-------------|
|
|
33
|
+
| Purple Dawn | `@neynar/ui/themes/purple-dawn` | Default. Elegant translucent surfaces with purple tint |
|
|
34
|
+
| First Light | `@neynar/ui/themes/first-light` | Hand-drawn sketch aesthetic with wobbly edges |
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
### base.css (Infrastructure)
|
|
39
|
+
|
|
40
|
+
Shared by all themes. Contains:
|
|
41
|
+
- Tailwind CSS imports (`tailwindcss`, `tw-animate-css`)
|
|
42
|
+
- `@theme` block mapping CSS variables to Tailwind utilities
|
|
43
|
+
- `@source` directive for component class scanning
|
|
44
|
+
- Base layer styles (borders, body background)
|
|
45
|
+
- Surface blur rules for overlay components via `[data-slot]` selectors
|
|
46
|
+
|
|
47
|
+
**Do NOT import base.css directly.** Always import a theme.
|
|
48
|
+
|
|
49
|
+
### Theme Files
|
|
50
|
+
|
|
51
|
+
Each theme defines CSS custom properties for:
|
|
52
|
+
- Colors (background, foreground, semantic colors)
|
|
53
|
+
- Typography (font-family)
|
|
54
|
+
- Spacing (radius, surface-blur)
|
|
55
|
+
- Both light (`:root`) and dark (`.dark`) modes
|
|
56
|
+
|
|
57
|
+
## CSS Variables
|
|
58
|
+
|
|
59
|
+
### Core Colors
|
|
60
|
+
|
|
61
|
+
| Variable | Usage |
|
|
62
|
+
|----------|-------|
|
|
63
|
+
| `--background` | Page background |
|
|
64
|
+
| `--foreground` | Default text |
|
|
65
|
+
| `--card` / `--card-foreground` | Card surfaces |
|
|
66
|
+
| `--popover` / `--popover-foreground` | Dropdown/dialog surfaces |
|
|
67
|
+
| `--primary` / `--primary-foreground` | Primary buttons, links |
|
|
68
|
+
| `--secondary` / `--secondary-foreground` | Secondary actions |
|
|
69
|
+
| `--muted` / `--muted-foreground` | Subtle backgrounds, helper text |
|
|
70
|
+
| `--subtle-foreground` | Even lighter text (40% opacity) |
|
|
71
|
+
| `--accent` / `--accent-foreground` | Hover states |
|
|
72
|
+
| `--border` | Border colors |
|
|
73
|
+
| `--input` | Input borders |
|
|
74
|
+
| `--ring` | Focus ring |
|
|
75
|
+
|
|
76
|
+
### Semantic Colors
|
|
77
|
+
|
|
78
|
+
| Variable | Usage |
|
|
79
|
+
|----------|-------|
|
|
80
|
+
| `--destructive` | Errors, delete actions |
|
|
81
|
+
| `--success` | Success states |
|
|
82
|
+
| `--warning` | Warning states |
|
|
83
|
+
| `--info` | Informational states |
|
|
84
|
+
|
|
85
|
+
### Chart Colors
|
|
86
|
+
|
|
87
|
+
`--chart-1` through `--chart-5` for data visualization.
|
|
88
|
+
|
|
89
|
+
### Sidebar Colors
|
|
90
|
+
|
|
91
|
+
`--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring`
|
|
92
|
+
|
|
93
|
+
### Theme-Specific Variables
|
|
94
|
+
|
|
95
|
+
| Variable | Theme | Usage |
|
|
96
|
+
|----------|-------|-------|
|
|
97
|
+
| `--surface-blur` | All | Backdrop blur amount (12px default, 4px for First Light) |
|
|
98
|
+
| `--font-family` | All | Primary font family |
|
|
99
|
+
| `--radius` | All | Border radius base (0.625rem default, 0 for First Light) |
|
|
100
|
+
| `--first-light-shadow` | First Light | Offset shadow effect (3px 3px) |
|
|
101
|
+
| `--first-light-shadow-hover` | First Light | Hover shadow (2px 2px) |
|
|
102
|
+
| `--first-light-shadow-active` | First Light | Active/pressed shadow (0px 0px) |
|
|
103
|
+
|
|
104
|
+
## Radius Utilities
|
|
105
|
+
|
|
106
|
+
The `--radius` variable is used to calculate multiple radius sizes via `@theme inline`:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
110
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
111
|
+
--radius-lg: var(--radius);
|
|
112
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
113
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
114
|
+
--radius-3xl: calc(var(--radius) + 12px);
|
|
115
|
+
--radius-4xl: calc(var(--radius) + 16px);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Dark Mode
|
|
119
|
+
|
|
120
|
+
Add `.dark` class to `<html>` or a parent element:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<html className="dark">
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Themes define both light (`:root`) and dark (`.dark`) variants.
|
|
127
|
+
|
|
128
|
+
### Setup with ColorModeInitializer
|
|
129
|
+
|
|
130
|
+
To prevent flash of incorrect color mode:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { ColorModeInitializer } from "@neynar/ui/color-mode";
|
|
134
|
+
|
|
135
|
+
<html suppressHydrationWarning>
|
|
136
|
+
<head>
|
|
137
|
+
<ColorModeInitializer />
|
|
138
|
+
</head>
|
|
139
|
+
<body>{children}</body>
|
|
140
|
+
</html>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Color Mode API
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// Get current mode
|
|
147
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
148
|
+
|
|
149
|
+
// Set mode programmatically
|
|
150
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
151
|
+
document.documentElement.classList.add('dark');
|
|
152
|
+
|
|
153
|
+
// Persist preference
|
|
154
|
+
document.cookie = 'color-mode={"preference":"dark","mode":"dark"}; path=/; max-age=31536000';
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Runtime Theme Switching
|
|
158
|
+
|
|
159
|
+
For Storybook or dynamic switching, add theme class to `<html>`:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// Switch to First Light theme
|
|
163
|
+
document.documentElement.classList.add("theme-first-light")
|
|
164
|
+
document.documentElement.classList.remove("theme-purple-dawn")
|
|
165
|
+
|
|
166
|
+
// Switch to Purple Dawn theme
|
|
167
|
+
document.documentElement.classList.add("theme-purple-dawn")
|
|
168
|
+
document.documentElement.classList.remove("theme-first-light")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Purple Dawn Theme
|
|
172
|
+
|
|
173
|
+
The default theme with elegant translucent surfaces.
|
|
174
|
+
|
|
175
|
+
### Characteristics
|
|
176
|
+
|
|
177
|
+
- **Font**: Figtree Variable (sans-serif)
|
|
178
|
+
- **Radius**: 0.625rem (10px)
|
|
179
|
+
- **Surface Blur**: 12px
|
|
180
|
+
- **Color Tint**: Purple (hue 290)
|
|
181
|
+
- **Surface Opacity**: 75% for cards and popovers
|
|
182
|
+
|
|
183
|
+
### Light Mode Colors
|
|
184
|
+
|
|
185
|
+
```css
|
|
186
|
+
--background: oklch(0.96 0.06 290); /* Light purple-tinted white */
|
|
187
|
+
--foreground: oklch(0.18 0.08 290); /* Dark purple-tinted black */
|
|
188
|
+
--card: oklch(0.93 0.08 290 / 75%); /* Translucent purple */
|
|
189
|
+
--primary: oklch(0.3 0.09 290); /* Dark purple */
|
|
190
|
+
--border: oklch(0.18 0.08 290 / 20%); /* 20% opacity border */
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Dark Mode Colors
|
|
194
|
+
|
|
195
|
+
```css
|
|
196
|
+
--background: oklch(0.145 0.02 290); /* Very dark purple */
|
|
197
|
+
--foreground: oklch(0.985 0.01 290); /* Near-white */
|
|
198
|
+
--card: oklch(0.205 0.03 290 / 75%); /* Translucent dark purple */
|
|
199
|
+
--primary: oklch(0.87 0.02 290); /* Light purple */
|
|
200
|
+
--border: oklch(0.985 0.01 290 / 15%); /* 15% opacity border */
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## First Light Theme
|
|
204
|
+
|
|
205
|
+
Hand-drawn wireframe aesthetic with wobbly SVG filters.
|
|
206
|
+
|
|
207
|
+
### Setup
|
|
208
|
+
|
|
209
|
+
First Light requires an SVG filter component for the wobbly edge effect:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import "@neynar/ui/themes/first-light"
|
|
213
|
+
import { FirstLightFilters } from "@neynar/ui/first-light"
|
|
214
|
+
|
|
215
|
+
// Add once in your root layout
|
|
216
|
+
export function Layout({ children }) {
|
|
217
|
+
return (
|
|
218
|
+
<>
|
|
219
|
+
<FirstLightFilters />
|
|
220
|
+
{children}
|
|
221
|
+
</>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Characteristics
|
|
227
|
+
|
|
228
|
+
- **Font**: Architects Daughter (handwriting)
|
|
229
|
+
- **Radius**: 0 (sharp corners)
|
|
230
|
+
- **Surface Blur**: 4px (minimal)
|
|
231
|
+
- **Color Style**: High contrast black/white with strong borders
|
|
232
|
+
- **Special Effect**: SVG turbulence filter for wobbly edges
|
|
233
|
+
|
|
234
|
+
### Light Mode (Paper)
|
|
235
|
+
|
|
236
|
+
```css
|
|
237
|
+
--background: #fafaf8; /* Warm white paper */
|
|
238
|
+
--foreground: #1a1a1a; /* Pencil black */
|
|
239
|
+
--border: rgba(0, 0, 0, 0.7); /* Strong black border */
|
|
240
|
+
--accent: #fff3b0; /* Highlighter yellow */
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Dark Mode (Chalkboard)
|
|
244
|
+
|
|
245
|
+
```css
|
|
246
|
+
--background: #1e2a1e; /* Green-tinted chalkboard */
|
|
247
|
+
--foreground: #e8e8e8; /* Chalk white */
|
|
248
|
+
--border: rgba(255, 255, 255, 0.6); /* White chalk border */
|
|
249
|
+
--accent: #e8d44d; /* Yellow chalk */
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### First Light Utility Classes
|
|
253
|
+
|
|
254
|
+
| Class | Effect |
|
|
255
|
+
|-------|--------|
|
|
256
|
+
| `.first-light-paper` | Grid paper background (20px grid) |
|
|
257
|
+
| `.first-light-lined` | Lined paper background (28px lines) |
|
|
258
|
+
| `.first-light-highlight` | Yellow highlighter effect |
|
|
259
|
+
| `.first-light-underline` | Hand-drawn underline (slightly rotated) |
|
|
260
|
+
| `.first-light-scribble` | Dashed underline effect |
|
|
261
|
+
| `.first-light-light` | Lighter wobble filter intensity |
|
|
262
|
+
| `.first-light-heavy` | Heavier wobble filter intensity |
|
|
263
|
+
|
|
264
|
+
### SVG Filter Details
|
|
265
|
+
|
|
266
|
+
The `FirstLightFilters` component provides three filter intensities:
|
|
267
|
+
|
|
268
|
+
| Filter ID | Base Frequency | Octaves | Scale | Use Case |
|
|
269
|
+
|-----------|----------------|---------|-------|----------|
|
|
270
|
+
| `#first-light-filter` | 0.015 | 2 | 1.5 | Default wobble |
|
|
271
|
+
| `#first-light-filter-light` | 0.006 | 1 | 0.4 | Subtle wobble |
|
|
272
|
+
| `#first-light-filter-heavy` | 0.012 | 3 | 1.5 | Pronounced wobble |
|
|
273
|
+
|
|
274
|
+
## Frosted Glass Effect
|
|
275
|
+
|
|
276
|
+
Cards, popovers, dialogs, sheets, and menus use translucent backgrounds with backdrop blur:
|
|
277
|
+
|
|
278
|
+
- **Surface Opacity**: 75% on `--card` and `--popover`
|
|
279
|
+
- **Backdrop Blur**: Via `--surface-blur` variable (applied to `[data-slot]` elements)
|
|
280
|
+
- **Transparent Borders**: 10-20% opacity
|
|
281
|
+
|
|
282
|
+
Components that receive the blur effect (via `base.css`):
|
|
283
|
+
- `[data-slot="card"]`
|
|
284
|
+
- `[data-slot="popover-content"]`
|
|
285
|
+
- `[data-slot="hover-card-content"]`
|
|
286
|
+
- `[data-slot="dialog-content"]`
|
|
287
|
+
- `[data-slot="alert-dialog-content"]`
|
|
288
|
+
- `[data-slot="sheet-content"]`
|
|
289
|
+
- `[data-slot="drawer-content"]`
|
|
290
|
+
- `[data-slot="dropdown-menu-content"]`
|
|
291
|
+
- `[data-slot="context-menu-content"]`
|
|
292
|
+
- `[data-slot="menubar-content"]`
|
|
293
|
+
- `[data-slot="navigation-menu-popup"]`
|
|
294
|
+
- `[data-slot="combobox-content"]`
|
|
295
|
+
- `[data-slot="select-content"]`
|
|
296
|
+
- `[data-slot="command"]`
|
|
297
|
+
|
|
298
|
+
## Creating Custom Themes
|
|
299
|
+
|
|
300
|
+
1. Create a new CSS file importing base.css
|
|
301
|
+
2. Define CSS variables in `:root` and `.dark`
|
|
302
|
+
3. Optionally add `.theme-{name}` selector for runtime switching
|
|
303
|
+
|
|
304
|
+
```css
|
|
305
|
+
@import "../base.css";
|
|
306
|
+
|
|
307
|
+
:root,
|
|
308
|
+
html.theme-custom {
|
|
309
|
+
--font-family: "Inter", sans-serif;
|
|
310
|
+
--radius: 0.5rem;
|
|
311
|
+
--surface-blur: 8px;
|
|
312
|
+
--background: oklch(0.98 0 0);
|
|
313
|
+
--foreground: oklch(0.1 0 0);
|
|
314
|
+
/* ... rest of variables */
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.dark,
|
|
318
|
+
html.theme-custom.dark {
|
|
319
|
+
--background: oklch(0.1 0 0);
|
|
320
|
+
--foreground: oklch(0.98 0 0);
|
|
321
|
+
/* ... dark mode overrides */
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Color Format
|
|
326
|
+
|
|
327
|
+
Themes use **oklch()** for perceptually uniform colors:
|
|
328
|
+
|
|
329
|
+
```css
|
|
330
|
+
--primary: oklch(0.5 0.18 290);
|
|
331
|
+
/* L C H
|
|
332
|
+
| | +-- Hue (0-360)
|
|
333
|
+
| +------- Chroma (0-0.37)
|
|
334
|
+
+------------ Lightness (0-1)
|
|
335
|
+
*/
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Benefits:
|
|
339
|
+
- Consistent perceived brightness across hues
|
|
340
|
+
- Easy to create harmonious palettes
|
|
341
|
+
- Supports relative color syntax for derived colors
|
|
342
|
+
|
|
343
|
+
### Relative Color Syntax
|
|
344
|
+
|
|
345
|
+
First Light theme uses relative color syntax for toast colors:
|
|
346
|
+
|
|
347
|
+
```css
|
|
348
|
+
--success-bg: oklch(from var(--success) 0.92 0.05 h / 90%);
|
|
349
|
+
--success-text: oklch(from var(--success) 0.25 c h);
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
This derives new colors from the base `--success` color, preserving hue while adjusting lightness and chroma.
|
|
353
|
+
|
|
354
|
+
## Using Theme Tokens in Tailwind
|
|
355
|
+
|
|
356
|
+
All CSS variables are mapped to Tailwind utilities via `@theme inline` in base.css:
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
<div className="bg-background text-foreground">
|
|
360
|
+
<button className="bg-primary text-primary-foreground hover:bg-accent">
|
|
361
|
+
Click
|
|
362
|
+
</button>
|
|
363
|
+
</div>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Available color utilities: `bg-{token}`, `text-{token}`, `border-{token}`, etc.
|
|
367
|
+
|
|
368
|
+
## Customization
|
|
369
|
+
|
|
370
|
+
Override any token in your CSS:
|
|
371
|
+
|
|
372
|
+
```css
|
|
373
|
+
:root {
|
|
374
|
+
--primary: oklch(0.6 0.25 260); /* Custom purple */
|
|
375
|
+
--radius: 0.5rem; /* Smaller corners */
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.dark {
|
|
379
|
+
--primary: oklch(0.7 0.2 260);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
Helper functions provided by @neynar/ui.
|
|
4
|
+
|
|
5
|
+
## cn()
|
|
6
|
+
|
|
7
|
+
Merges class names with Tailwind CSS conflict resolution.
|
|
8
|
+
|
|
9
|
+
### Import
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { cn } from "@neynar/ui/utils"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Signature
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
function cn(...inputs: ClassValue[]): string
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### How It Works
|
|
22
|
+
|
|
23
|
+
1. **clsx** - Handles conditionals, arrays, and objects
|
|
24
|
+
2. **tailwind-merge** - Resolves Tailwind conflicts (last class wins)
|
|
25
|
+
|
|
26
|
+
### Examples
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// Conditional classes
|
|
30
|
+
cn("px-4", isActive && "bg-primary")
|
|
31
|
+
|
|
32
|
+
// Merge with className prop
|
|
33
|
+
cn("rounded-lg border", className)
|
|
34
|
+
|
|
35
|
+
// Tailwind conflict resolution
|
|
36
|
+
cn("text-red-500", "text-blue-500") // → "text-blue-500"
|
|
37
|
+
cn("px-4 py-2", "px-8") // → "px-8 py-2"
|
|
38
|
+
|
|
39
|
+
// Objects and arrays
|
|
40
|
+
cn({ "opacity-50": disabled }, ["flex", "items-center"])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Common Patterns
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Component with className prop
|
|
47
|
+
function Card({ className, ...props }) {
|
|
48
|
+
return (
|
|
49
|
+
<div className={cn("rounded-lg border bg-card", className)} {...props} />
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Conditional styling
|
|
54
|
+
<Button className={cn(isLoading && "opacity-50 cursor-wait")}>
|
|
55
|
+
Submit
|
|
56
|
+
</Button>
|
|
57
|
+
|
|
58
|
+
// Variant-based styling
|
|
59
|
+
<div className={cn(
|
|
60
|
+
"p-4",
|
|
61
|
+
variant === "destructive" && "bg-destructive text-destructive-foreground",
|
|
62
|
+
variant === "success" && "bg-success text-success-foreground"
|
|
63
|
+
)}>
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Internal Utilities
|
|
71
|
+
|
|
72
|
+
The following utilities are used internally by components but are **not exported**:
|
|
73
|
+
|
|
74
|
+
### CVA Variants (lib/variants.ts)
|
|
75
|
+
|
|
76
|
+
Internal variant definitions for consistent styling across components:
|
|
77
|
+
|
|
78
|
+
- **menuItemVariants** - Styling for DropdownMenuItem, ContextMenuItem, MenubarItem
|
|
79
|
+
- **typographyColorVariants** - Color options for Title, Text, Code, Blockquote
|
|
80
|
+
- **titleVariants** - Size and weight options for Title
|
|
81
|
+
- **textVariants** - Size, weight, alignment for Text
|
|
82
|
+
|
|
83
|
+
These are exposed via component props rather than direct imports:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Use component props (correct)
|
|
87
|
+
<Text color="muted" size="sm">Helper text</Text>
|
|
88
|
+
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
|
89
|
+
|
|
90
|
+
// Don't try to import variants directly (not exported)
|
|
91
|
+
// import { menuItemVariants } from "@neynar/ui/lib/variants" // ❌
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Related
|
|
95
|
+
|
|
96
|
+
- [Theming](./theming.llm.md) - CSS variables and themes
|
|
97
|
+
- [Hooks](./hooks.llm.md) - React hooks
|