shadcn-glass-ui 2.0.8 → 2.0.10
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 +153 -109
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.d.ts +63 -322
- package/dist/components.js +2 -2
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +43 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/r/alert-glass.json +2 -1
- package/dist/r/avatar-glass.json +2 -3
- package/dist/r/button-glass.json +1 -1
- package/dist/r/circular-progress-glass.json +1 -1
- package/dist/r/combobox-glass.json +1 -1
- package/dist/r/dropdown-glass.json +3 -5
- package/dist/r/dropdown-menu-glass.json +42 -0
- package/dist/r/language-bar-glass.json +2 -2
- package/dist/r/popover-glass.json +1 -1
- package/dist/r/profile-avatar-glass.json +5 -3
- package/dist/r/rainbow-progress-glass.json +1 -1
- package/dist/r/registry.json +10 -4
- package/dist/r/sort-dropdown-glass.json +3 -4
- package/dist/r/tooltip-glass.json +3 -5
- package/dist/r/trust-score-card-glass.json +1 -1
- package/dist/r/trust-score-display-glass.json +1 -1
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-CVW50BKW.cjs → theme-context-DNe_2vWJ.cjs} +2 -2
- package/dist/theme-context-DNe_2vWJ.cjs.map +1 -0
- package/dist/{theme-context-BZoCplcU.js → theme-context-_T5r1KG4.js} +1 -1
- package/dist/theme-context-_T5r1KG4.js.map +1 -0
- package/dist/themes.cjs +1 -1
- package/dist/themes.d.ts +89 -1
- package/dist/themes.js +1 -1
- package/dist/{trust-score-card-glass-BcZbul0P.js → trust-score-card-glass-A7kas5OS.js} +353 -279
- package/dist/trust-score-card-glass-A7kas5OS.js.map +1 -0
- package/dist/{trust-score-card-glass-r3qM09Jp.cjs → trust-score-card-glass-Dgu46oWI.cjs} +551 -313
- package/dist/trust-score-card-glass-Dgu46oWI.cjs.map +1 -0
- package/dist/{use-focus-ZE8ozmZE.cjs → use-focus-BRkQtQCj.cjs} +2 -2
- package/dist/{use-focus-ZE8ozmZE.cjs.map → use-focus-BRkQtQCj.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-BuS80tbN.cjs → use-wallpaper-tint-CfShPBo2.cjs} +2 -2
- package/dist/{use-wallpaper-tint-BuS80tbN.cjs.map → use-wallpaper-tint-CfShPBo2.cjs.map} +1 -1
- package/dist/{utils-DLXayspX.cjs → utils-BXN7AcRu.cjs} +2 -2
- package/dist/{utils-DLXayspX.cjs.map → utils-BXN7AcRu.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/AI_USAGE.md +1 -32
- package/docs/API_PATTERNS_COMPARISON.md +10 -9
- package/docs/COMPONENTS_CATALOG.md +140 -45
- package/docs/DROPDOWN_ARCHITECTURE.md +290 -0
- package/docs/GETTING_STARTED.md +6 -5
- package/docs/TOKEN_ARCHITECTURE.md +100 -0
- package/docs/api/README.md +3 -3
- package/docs/migration/compound-components-v2.md +384 -0
- package/docs/visual-testing-guide.md +50 -12
- package/package.json +2 -2
- package/dist/theme-context-BZoCplcU.js.map +0 -1
- package/dist/theme-context-CVW50BKW.cjs.map +0 -1
- package/dist/trust-score-card-glass-BcZbul0P.js.map +0 -1
- package/dist/trust-score-card-glass-r3qM09Jp.cjs.map +0 -1
- package/docs/ADVANCED_PATTERNS.md +0 -408
- package/docs/BREAKING_CHANGES.md +0 -213
- package/docs/announcements/v1.0.5-devto.md +0 -363
- package/docs/announcements/v1.0.5-reddit.md +0 -115
- package/docs/announcements/v1.0.5-twitter.md +0 -70
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Compound Components Migration Guide (v2.0+)
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
As of v2.0.0, five component families have been migrated to the **compound component API** pattern
|
|
6
|
+
following shadcn/ui best practices:
|
|
7
|
+
|
|
8
|
+
- **AlertGlass** - Alert notifications with Title/Description
|
|
9
|
+
- **AvatarGlass** - User avatars with Image/Fallback
|
|
10
|
+
- **TooltipGlass** - Tooltips with Provider/Trigger/Content
|
|
11
|
+
- **PopoverGlass** - Popovers with Trigger/Content/Anchor
|
|
12
|
+
- **DropdownMenuGlass** - Dropdown menus with full shadcn/ui pattern (alongside simple
|
|
13
|
+
DropdownGlass)
|
|
14
|
+
|
|
15
|
+
All components maintain **100% backward compatibility** through wrapper components or simple APIs.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## AlertGlass Migration
|
|
20
|
+
|
|
21
|
+
### Old API (v1.x)
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<AlertGlass variant="destructive" title="Error">
|
|
25
|
+
Something went wrong
|
|
26
|
+
</AlertGlass>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### New API (v2.0+)
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<AlertGlass variant="destructive">
|
|
33
|
+
<AlertGlassTitle>Error</AlertGlassTitle>
|
|
34
|
+
<AlertGlassDescription>Something went wrong</AlertGlassDescription>
|
|
35
|
+
</AlertGlass>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Benefits
|
|
39
|
+
|
|
40
|
+
- ✅ **Flexible composition** - Mix and match Title/Description
|
|
41
|
+
- ✅ **Type safety** - Better TypeScript inference
|
|
42
|
+
- ✅ **shadcn/ui compatible** - Same pattern as shadcn Alert
|
|
43
|
+
- ✅ **Custom styling** - Style Title/Description independently
|
|
44
|
+
|
|
45
|
+
### Available Components
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// Main container
|
|
49
|
+
<AlertGlass variant="default" | "destructive" | "success" | "warning" dismissible onDismiss={() => {}}>
|
|
50
|
+
|
|
51
|
+
// Sub-components (inherit color from variant)
|
|
52
|
+
<AlertGlassTitle>...</AlertGlassTitle>
|
|
53
|
+
<AlertGlassDescription>...</AlertGlassDescription>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## AvatarGlass Migration
|
|
59
|
+
|
|
60
|
+
### Old API (v1.x)
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<AvatarGlass name="John Doe" size="lg" status="online" />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### New API (v2.0+) - Compound
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<AvatarGlass size="lg" status="online">
|
|
70
|
+
<AvatarGlassImage src="/avatar.jpg" alt="John Doe" />
|
|
71
|
+
<AvatarGlassFallback>JD</AvatarGlassFallback>
|
|
72
|
+
</AvatarGlass>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Backward Compatible - Simple Wrapper
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<AvatarGlassSimple name="John Doe" size="lg" status="online" />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Benefits
|
|
82
|
+
|
|
83
|
+
- ✅ **Image control** - Full control over image loading/fallback
|
|
84
|
+
- ✅ **Radix UI based** - Built on `@radix-ui/react-avatar`
|
|
85
|
+
- ✅ **Custom fallback** - Any React element, not just initials
|
|
86
|
+
- ✅ **asChild pattern** - Use any element as container
|
|
87
|
+
|
|
88
|
+
### Available Components
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// Main container
|
|
92
|
+
<AvatarGlass size="sm" | "md" | "lg" | "xl" status="online" | "offline" | "busy" | "away" glowing>
|
|
93
|
+
|
|
94
|
+
// Sub-components
|
|
95
|
+
<AvatarGlassImage src="..." alt="..." />
|
|
96
|
+
<AvatarGlassFallback>...</AvatarGlassFallback>
|
|
97
|
+
|
|
98
|
+
// Simple wrapper (backward compatible)
|
|
99
|
+
<AvatarGlassSimple name="..." size="..." status="..." />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## TooltipGlass Migration
|
|
105
|
+
|
|
106
|
+
### Old API (v1.x)
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<TooltipGlass content="Tooltip text" side="top">
|
|
110
|
+
<button>Hover me</button>
|
|
111
|
+
</TooltipGlass>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### New API (v2.0+) - Compound
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<TooltipGlassProvider>
|
|
118
|
+
<TooltipGlass>
|
|
119
|
+
<TooltipGlassTrigger asChild>
|
|
120
|
+
<button>Hover me</button>
|
|
121
|
+
</TooltipGlassTrigger>
|
|
122
|
+
<TooltipGlassContent side="top">
|
|
123
|
+
<p>Tooltip text</p>
|
|
124
|
+
</TooltipGlassContent>
|
|
125
|
+
</TooltipGlass>
|
|
126
|
+
</TooltipGlassProvider>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Backward Compatible - Simple Wrapper
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<TooltipGlassProvider>
|
|
133
|
+
<TooltipGlassSimple content="Tooltip text" side="top">
|
|
134
|
+
<button>Hover me</button>
|
|
135
|
+
</TooltipGlassSimple>
|
|
136
|
+
</TooltipGlassProvider>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Benefits
|
|
140
|
+
|
|
141
|
+
- ✅ **Rich content** - Any React element in tooltip
|
|
142
|
+
- ✅ **Radix UI based** - Built on `@radix-ui/react-tooltip`
|
|
143
|
+
- ✅ **Provider pattern** - Share delay settings across tooltips
|
|
144
|
+
- ✅ **Accessibility** - WCAG 2.1 AA compliant
|
|
145
|
+
|
|
146
|
+
### Available Components
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// Provider (wrap all tooltips to share settings)
|
|
150
|
+
<TooltipGlassProvider delayDuration={0}>
|
|
151
|
+
|
|
152
|
+
// Root container
|
|
153
|
+
<TooltipGlass>
|
|
154
|
+
|
|
155
|
+
// Trigger (usually with asChild)
|
|
156
|
+
<TooltipGlassTrigger asChild>
|
|
157
|
+
|
|
158
|
+
// Content
|
|
159
|
+
<TooltipGlassContent side="top" | "bottom" | "left" | "right" sideOffset={4}>
|
|
160
|
+
|
|
161
|
+
// Simple wrapper (backward compatible)
|
|
162
|
+
<TooltipGlassSimple content="..." side="..." />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## PopoverGlass Migration
|
|
168
|
+
|
|
169
|
+
### Old API (v1.x)
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<PopoverGlass trigger={<button>Open</button>} side="bottom" align="center">
|
|
173
|
+
<p>Popover content</p>
|
|
174
|
+
</PopoverGlass>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### New API (v2.0+) - Compound
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<PopoverGlass>
|
|
181
|
+
<PopoverGlassTrigger asChild>
|
|
182
|
+
<button>Open</button>
|
|
183
|
+
</PopoverGlassTrigger>
|
|
184
|
+
<PopoverGlassContent side="bottom" align="center">
|
|
185
|
+
<p>Popover content</p>
|
|
186
|
+
</PopoverGlassContent>
|
|
187
|
+
</PopoverGlass>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Backward Compatible - Legacy Wrapper
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
<PopoverGlassLegacy trigger={<button>Open</button>} side="bottom" align="center">
|
|
194
|
+
<p>Popover content</p>
|
|
195
|
+
</PopoverGlassLegacy>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Benefits
|
|
199
|
+
|
|
200
|
+
- ✅ **Flexible positioning** - Full control over trigger/content
|
|
201
|
+
- ✅ **Radix UI based** - Built on `@radix-ui/react-popover`
|
|
202
|
+
- ✅ **Anchor support** - Position relative to custom anchor
|
|
203
|
+
- ✅ **Portal rendering** - Escape z-index issues
|
|
204
|
+
|
|
205
|
+
### Available Components
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// Root container
|
|
209
|
+
<PopoverGlass>
|
|
210
|
+
|
|
211
|
+
// Trigger (usually with asChild)
|
|
212
|
+
<PopoverGlassTrigger asChild>
|
|
213
|
+
|
|
214
|
+
// Content
|
|
215
|
+
<PopoverGlassContent side="top" | "bottom" | "left" | "right" align="start" | "center" | "end">
|
|
216
|
+
|
|
217
|
+
// Optional anchor
|
|
218
|
+
<PopoverGlassAnchor>
|
|
219
|
+
|
|
220
|
+
// Legacy wrapper (backward compatible)
|
|
221
|
+
<PopoverGlassLegacy trigger={...} side="..." align="...">
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Migration Checklist
|
|
227
|
+
|
|
228
|
+
When migrating your codebase to compound components:
|
|
229
|
+
|
|
230
|
+
### 1. AlertGlass
|
|
231
|
+
|
|
232
|
+
- [ ] Replace `title` prop with `<AlertGlassTitle>`
|
|
233
|
+
- [ ] Wrap children in `<AlertGlassDescription>`
|
|
234
|
+
- [ ] Update imports to include `AlertGlassTitle`, `AlertGlassDescription`
|
|
235
|
+
|
|
236
|
+
### 2. AvatarGlass
|
|
237
|
+
|
|
238
|
+
**Option A: Full migration (recommended)**
|
|
239
|
+
|
|
240
|
+
- [ ] Use `<AvatarGlassImage>` for images
|
|
241
|
+
- [ ] Use `<AvatarGlassFallback>` for fallback content
|
|
242
|
+
- [ ] Update imports to include `AvatarGlassImage`, `AvatarGlassFallback`
|
|
243
|
+
|
|
244
|
+
**Option B: Quick fix (backward compatible)**
|
|
245
|
+
|
|
246
|
+
- [ ] Replace `<AvatarGlass>` with `<AvatarGlassSimple>`
|
|
247
|
+
- [ ] Update import to include `AvatarGlassSimple`
|
|
248
|
+
|
|
249
|
+
### 3. TooltipGlass
|
|
250
|
+
|
|
251
|
+
**Option A: Full migration (recommended)**
|
|
252
|
+
|
|
253
|
+
- [ ] Wrap tooltips in `<TooltipGlassProvider>`
|
|
254
|
+
- [ ] Use `<TooltipGlassTrigger asChild>` for trigger
|
|
255
|
+
- [ ] Use `<TooltipGlassContent>` for content
|
|
256
|
+
- [ ] Update imports
|
|
257
|
+
|
|
258
|
+
**Option B: Quick fix (backward compatible)**
|
|
259
|
+
|
|
260
|
+
- [ ] Wrap tooltips in `<TooltipGlassProvider>`
|
|
261
|
+
- [ ] Replace `<TooltipGlass>` with `<TooltipGlassSimple>`
|
|
262
|
+
- [ ] Update import to include `TooltipGlassProvider`, `TooltipGlassSimple`
|
|
263
|
+
|
|
264
|
+
### 4. PopoverGlass
|
|
265
|
+
|
|
266
|
+
**Option A: Full migration (recommended)**
|
|
267
|
+
|
|
268
|
+
- [ ] Use `<PopoverGlassTrigger asChild>` for trigger
|
|
269
|
+
- [ ] Use `<PopoverGlassContent>` for content
|
|
270
|
+
- [ ] Update imports
|
|
271
|
+
|
|
272
|
+
**Option B: Quick fix (backward compatible)**
|
|
273
|
+
|
|
274
|
+
- [ ] Replace `<PopoverGlass>` with `<PopoverGlassLegacy>`
|
|
275
|
+
- [ ] Update import to include `PopoverGlassLegacy`
|
|
276
|
+
|
|
277
|
+
### 5. DropdownMenuGlass (New in v2.0)
|
|
278
|
+
|
|
279
|
+
**No migration needed!** DropdownMenuGlass is a **new addition** alongside the existing
|
|
280
|
+
DropdownGlass.
|
|
281
|
+
|
|
282
|
+
**DropdownGlass (simple API)** - Already available, no changes:
|
|
283
|
+
|
|
284
|
+
- [ ] Continue using `items` prop for simple dropdowns
|
|
285
|
+
- [ ] No migration required
|
|
286
|
+
|
|
287
|
+
**DropdownMenuGlass (compound API)** - New compound component for complex menus:
|
|
288
|
+
|
|
289
|
+
- [ ] Use for advanced features (checkboxes, radio groups, submenus, labels)
|
|
290
|
+
- [ ] Follow shadcn/ui DropdownMenu pattern
|
|
291
|
+
- [ ] Import from `@/components/glass/ui/dropdown-menu-glass`
|
|
292
|
+
|
|
293
|
+
**When to use which:**
|
|
294
|
+
|
|
295
|
+
- Use `DropdownGlass` for simple menus with basic items (quick setup)
|
|
296
|
+
- Use `DropdownMenuGlass` for complex menus with advanced features (full control)
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Automated Migration Scripts
|
|
301
|
+
|
|
302
|
+
### Find all AlertGlass with old API
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# macOS/Linux
|
|
306
|
+
grep -r "title=" src/ | grep "AlertGlass"
|
|
307
|
+
|
|
308
|
+
# Find and update (use with caution!)
|
|
309
|
+
find src/ -type f \( -name "*.tsx" -o -name "*.ts" \) -exec sed -i '' \
|
|
310
|
+
's/<AlertGlass variant="\([^"]*\)" title="\([^"]*\)">\s*\([^<]*\)\s*<\/AlertGlass>/<AlertGlass variant="\1">\n <AlertGlassTitle>\2<\/AlertGlassTitle>\n <AlertGlassDescription>\3<\/AlertGlassDescription>\n<\/AlertGlass>/g' \
|
|
311
|
+
{} +
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Find all AvatarGlass with old API
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# macOS/Linux
|
|
318
|
+
grep -r "name=" src/ | grep "AvatarGlass"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Find all TooltipGlass with old API
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# macOS/Linux
|
|
325
|
+
grep -r "content=" src/ | grep "TooltipGlass"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Find all PopoverGlass with old API
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# macOS/Linux
|
|
332
|
+
grep -r "trigger=" src/ | grep "PopoverGlass"
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Breaking Changes Summary
|
|
338
|
+
|
|
339
|
+
**None!** All components maintain 100% backward compatibility through wrapper components:
|
|
340
|
+
|
|
341
|
+
- `AlertGlass` - No wrapper needed, but `title` prop removed
|
|
342
|
+
- `AvatarGlass` → `AvatarGlassSimple` (wrapper available)
|
|
343
|
+
- `TooltipGlass` → `TooltipGlassSimple` (wrapper available)
|
|
344
|
+
- `PopoverGlass` → `PopoverGlassLegacy` (wrapper available)
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## TypeScript Support
|
|
349
|
+
|
|
350
|
+
All compound components have full TypeScript support with:
|
|
351
|
+
|
|
352
|
+
- ✅ Strict type checking
|
|
353
|
+
- ✅ Autocomplete for props
|
|
354
|
+
- ✅ Generic constraints for `asChild` pattern
|
|
355
|
+
- ✅ Proper `React.forwardRef` types
|
|
356
|
+
|
|
357
|
+
Example:
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import type { ComponentPropsWithoutRef, ElementRef } from 'react';
|
|
361
|
+
|
|
362
|
+
// Type inference works automatically
|
|
363
|
+
const ref = useRef<ElementRef<typeof AlertGlass>>(null);
|
|
364
|
+
|
|
365
|
+
// Props are fully typed
|
|
366
|
+
const props: ComponentPropsWithoutRef<typeof AlertGlass> = {
|
|
367
|
+
variant: 'success',
|
|
368
|
+
dismissible: true,
|
|
369
|
+
onDismiss: () => {},
|
|
370
|
+
};
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Further Reading
|
|
376
|
+
|
|
377
|
+
- [shadcn/ui Alert](https://ui.shadcn.com/docs/components/alert) - Reference implementation
|
|
378
|
+
- [Radix UI Primitives](https://www.radix-ui.com/primitives) - Base components
|
|
379
|
+
- [Compound Component Pattern](https://kentcdodds.com/blog/compound-components-with-react-hooks) -
|
|
380
|
+
Pattern explanation
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
**Questions?** Open an issue at [github.com/your-repo/issues](https://github.com/your-repo/issues)
|
|
@@ -4,9 +4,12 @@ This guide explains how to work with visual regression tests in this project.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The project uses **Vitest 4.0 browser mode** with **Playwright** for visual regression testing. All
|
|
7
|
+
The project uses **Vitest 4.0 browser mode** with **Playwright** for visual regression testing. All
|
|
8
|
+
reference screenshots are generated on **Linux (ubuntu-latest)** in CI to ensure consistency across
|
|
9
|
+
environments.
|
|
8
10
|
|
|
9
11
|
**Key Points:**
|
|
12
|
+
|
|
10
13
|
- ✅ Reference screenshots are generated on Linux
|
|
11
14
|
- ✅ All platforms use the same screenshot files (no `-darwin`/`-linux` suffixes)
|
|
12
15
|
- ✅ Automatic workflow for updating screenshots
|
|
@@ -15,27 +18,34 @@ The project uses **Vitest 4.0 browser mode** with **Playwright** for visual regr
|
|
|
15
18
|
## Running Tests Locally
|
|
16
19
|
|
|
17
20
|
### Run all visual tests
|
|
21
|
+
|
|
18
22
|
```bash
|
|
19
23
|
npm run test:visual:ci
|
|
20
24
|
```
|
|
21
25
|
|
|
22
26
|
### Update screenshots locally (for reference only)
|
|
27
|
+
|
|
23
28
|
```bash
|
|
24
29
|
npm run test:visual:update
|
|
25
30
|
```
|
|
26
31
|
|
|
27
|
-
**⚠️ IMPORTANT:** Do NOT commit screenshots generated on macOS. Always use the GitHub Actions
|
|
32
|
+
**⚠️ IMPORTANT:** Do NOT commit screenshots generated on macOS. Always use the GitHub Actions
|
|
33
|
+
workflow to generate production screenshots.
|
|
34
|
+
|
|
35
|
+
**Cleaning local screenshots:** If you accidentally generated screenshots locally, use this script
|
|
36
|
+
to remove them:
|
|
28
37
|
|
|
29
|
-
**Cleaning local screenshots:**
|
|
30
|
-
If you accidentally generated screenshots locally, use this script to remove them:
|
|
31
38
|
```bash
|
|
32
39
|
./scripts/clean-local-screenshots.sh
|
|
33
40
|
```
|
|
34
41
|
|
|
35
|
-
**Protection:** A pre-commit hook automatically blocks commits of new screenshots from non-Linux
|
|
42
|
+
**Protection:** A pre-commit hook automatically blocks commits of new screenshots from non-Linux
|
|
43
|
+
platforms. If you see this error:
|
|
44
|
+
|
|
36
45
|
```
|
|
37
46
|
❌ ERROR: Cannot commit visual test screenshots from Darwin
|
|
38
47
|
```
|
|
48
|
+
|
|
39
49
|
This is working as intended. Use the GitHub Actions workflow instead.
|
|
40
50
|
|
|
41
51
|
## Updating Reference Screenshots
|
|
@@ -43,23 +53,27 @@ This is working as intended. Use the GitHub Actions workflow instead.
|
|
|
43
53
|
When you modify UI components and need to update screenshots:
|
|
44
54
|
|
|
45
55
|
### Option 1: Using GitHub CLI (Recommended)
|
|
56
|
+
|
|
46
57
|
```bash
|
|
47
58
|
gh workflow run update-screenshots.yml
|
|
48
59
|
```
|
|
49
60
|
|
|
50
61
|
### Option 2: Via GitHub Web UI
|
|
62
|
+
|
|
51
63
|
1. Go to **Actions** → **Update Visual Test Screenshots**
|
|
52
64
|
2. Click **Run workflow**
|
|
53
65
|
3. Optionally provide a custom commit message
|
|
54
66
|
4. Click **Run workflow**
|
|
55
67
|
|
|
56
68
|
The workflow will:
|
|
69
|
+
|
|
57
70
|
1. Delete existing screenshots
|
|
58
71
|
2. Generate new screenshots on Linux (ubuntu-latest)
|
|
59
72
|
3. Commit and push changes automatically
|
|
60
73
|
4. Upload screenshots as artifacts
|
|
61
74
|
|
|
62
75
|
### Monitoring Workflow Progress
|
|
76
|
+
|
|
63
77
|
```bash
|
|
64
78
|
# Check workflow status
|
|
65
79
|
gh run list --workflow=update-screenshots.yml --limit 1
|
|
@@ -82,6 +96,7 @@ comparatorOptions: {
|
|
|
82
96
|
```
|
|
83
97
|
|
|
84
98
|
These tolerances account for:
|
|
99
|
+
|
|
85
100
|
- Font rendering differences between macOS and Linux
|
|
86
101
|
- Subpixel anti-aliasing variations
|
|
87
102
|
- Minor layout shifts (1-2px height differences)
|
|
@@ -89,6 +104,7 @@ These tolerances account for:
|
|
|
89
104
|
### Screenshot Naming
|
|
90
105
|
|
|
91
106
|
Screenshots use a unified naming pattern:
|
|
107
|
+
|
|
92
108
|
```
|
|
93
109
|
{test-name}-{theme}-chromium.png
|
|
94
110
|
```
|
|
@@ -100,6 +116,7 @@ Example: `button-primary-glass-chromium.png`
|
|
|
100
116
|
## Test Structure
|
|
101
117
|
|
|
102
118
|
### Visual Test Locations
|
|
119
|
+
|
|
103
120
|
```
|
|
104
121
|
src/components/__visual__/
|
|
105
122
|
├── components.visual.test.tsx # Individual component tests
|
|
@@ -112,6 +129,7 @@ src/components/__visual__/
|
|
|
112
129
|
```
|
|
113
130
|
|
|
114
131
|
### Screenshot Organization
|
|
132
|
+
|
|
115
133
|
```
|
|
116
134
|
src/components/__visual__/__screenshots__/
|
|
117
135
|
├── components.visual.test.tsx/
|
|
@@ -125,11 +143,13 @@ src/components/__visual__/__screenshots__/
|
|
|
125
143
|
|
|
126
144
|
## Git Pre-commit Hook
|
|
127
145
|
|
|
128
|
-
The repository includes a pre-commit hook that prevents accidentally committing screenshots
|
|
146
|
+
The repository includes a pre-commit hook that prevents accidentally committing screenshots
|
|
147
|
+
generated on non-Linux platforms.
|
|
129
148
|
|
|
130
149
|
**Setup (Automatic):**
|
|
131
150
|
|
|
132
|
-
The hook is located at `.git/hooks/pre-commit` and should work automatically. If you encounter
|
|
151
|
+
The hook is located at `.git/hooks/pre-commit` and should work automatically. If you encounter
|
|
152
|
+
issues:
|
|
133
153
|
|
|
134
154
|
```bash
|
|
135
155
|
# Ensure Git is using the correct hooks directory
|
|
@@ -140,11 +160,13 @@ chmod +x .git/hooks/pre-commit
|
|
|
140
160
|
```
|
|
141
161
|
|
|
142
162
|
**How it works:**
|
|
163
|
+
|
|
143
164
|
- Blocks commits of new `.png` files in `__screenshots__/` directories
|
|
144
165
|
- Only allows commits from Linux (CI environment)
|
|
145
166
|
- Shows helpful error message with instructions
|
|
146
167
|
|
|
147
168
|
**Bypassing the hook (not recommended):**
|
|
169
|
+
|
|
148
170
|
```bash
|
|
149
171
|
git commit --no-verify # Skip all git hooks
|
|
150
172
|
```
|
|
@@ -169,6 +191,7 @@ Visual tests run automatically on every push/PR:
|
|
|
169
191
|
**File:** `.github/workflows/update-screenshots.yml`
|
|
170
192
|
|
|
171
193
|
Workflow features:
|
|
194
|
+
|
|
172
195
|
- ✅ Manual trigger only (`workflow_dispatch`)
|
|
173
196
|
- ✅ Runs on Linux (ubuntu-latest)
|
|
174
197
|
- ✅ Deletes old screenshots before regeneration
|
|
@@ -181,11 +204,13 @@ Workflow features:
|
|
|
181
204
|
### Tests Fail After Component Changes
|
|
182
205
|
|
|
183
206
|
**Symptoms:**
|
|
207
|
+
|
|
184
208
|
- CI shows screenshot mismatches
|
|
185
209
|
- Error: "Screenshot does not match the stored reference"
|
|
186
210
|
- Pixel ratio > 0.08 or dimension differences
|
|
187
211
|
|
|
188
212
|
**Solution:**
|
|
213
|
+
|
|
189
214
|
1. Review the changes to ensure they're intentional
|
|
190
215
|
2. Run the screenshot update workflow:
|
|
191
216
|
```bash
|
|
@@ -201,15 +226,18 @@ Workflow features:
|
|
|
201
226
|
### Local Screenshots Don't Match CI
|
|
202
227
|
|
|
203
228
|
**Symptoms:**
|
|
229
|
+
|
|
204
230
|
- Tests pass locally but fail in CI
|
|
205
231
|
- Opposite: tests fail locally but pass in CI
|
|
206
232
|
|
|
207
233
|
**Cause:**
|
|
234
|
+
|
|
208
235
|
- Platform differences (macOS vs Linux)
|
|
209
236
|
- Font rendering variations
|
|
210
237
|
- Different screenshot generation environment
|
|
211
238
|
|
|
212
239
|
**Solution:**
|
|
240
|
+
|
|
213
241
|
- **DO NOT** commit local screenshots from macOS
|
|
214
242
|
- Always use the GitHub Actions workflow
|
|
215
243
|
- Linux screenshots are the source of truth
|
|
@@ -217,13 +245,16 @@ Workflow features:
|
|
|
217
245
|
### Workflow Fails to Commit
|
|
218
246
|
|
|
219
247
|
**Symptoms:**
|
|
248
|
+
|
|
220
249
|
- Workflow succeeds but no new commit appears
|
|
221
250
|
- Error: "Updates were rejected"
|
|
222
251
|
|
|
223
252
|
**Cause:**
|
|
253
|
+
|
|
224
254
|
- Race condition: new commits pushed while workflow was running
|
|
225
255
|
|
|
226
256
|
**Solution:**
|
|
257
|
+
|
|
227
258
|
- Workflow includes `git pull --rebase` before push
|
|
228
259
|
- If still failing, manually re-run the workflow
|
|
229
260
|
|
|
@@ -232,12 +263,14 @@ Workflow features:
|
|
|
232
263
|
### When to Update Screenshots
|
|
233
264
|
|
|
234
265
|
Update screenshots when:
|
|
266
|
+
|
|
235
267
|
- ✅ Intentional UI changes (new features, design updates)
|
|
236
268
|
- ✅ Component refactoring that affects rendering
|
|
237
269
|
- ✅ Theme or styling changes
|
|
238
270
|
- ✅ Breaking changes in dependencies (React, Tailwind, etc.)
|
|
239
271
|
|
|
240
272
|
Do NOT update screenshots for:
|
|
273
|
+
|
|
241
274
|
- ❌ Random pixel differences without code changes
|
|
242
275
|
- ❌ Flaky tests (investigate root cause first)
|
|
243
276
|
- ❌ Local development screenshots
|
|
@@ -268,6 +301,7 @@ test('MyComponent - default state - glass theme', async () => {
|
|
|
268
301
|
```
|
|
269
302
|
|
|
270
303
|
**Naming convention:**
|
|
304
|
+
|
|
271
305
|
- Use descriptive names: `{component}-{variant}-{theme}`
|
|
272
306
|
- Include theme: `glass`, `light`, `aurora`
|
|
273
307
|
- Use kebab-case
|
|
@@ -275,6 +309,7 @@ test('MyComponent - default state - glass theme', async () => {
|
|
|
275
309
|
## Statistics
|
|
276
310
|
|
|
277
311
|
**Current Coverage:**
|
|
312
|
+
|
|
278
313
|
- **582 visual tests** (as of v1.0.0)
|
|
279
314
|
- **603 reference screenshots**
|
|
280
315
|
- **99.5% pass rate** in CI
|
|
@@ -283,7 +318,7 @@ test('MyComponent - default state - glass theme', async () => {
|
|
|
283
318
|
## Related Documentation
|
|
284
319
|
|
|
285
320
|
- [CLAUDE.md](../CLAUDE.md) - Project overview and architecture
|
|
286
|
-
- [DEPENDENCIES.md](
|
|
321
|
+
- [DEPENDENCIES.md](technical/DEPENDENCIES.md) - Tech stack details
|
|
287
322
|
- [visual-tests-audit.md](./visual-tests-audit.md) - Detailed test audit
|
|
288
323
|
- [Migration Guides](./migration/) - Component API changes
|
|
289
324
|
|
|
@@ -292,33 +327,36 @@ test('MyComponent - default state - glass theme', async () => {
|
|
|
292
327
|
### Update Screenshots Workflow
|
|
293
328
|
|
|
294
329
|
**Trigger:**
|
|
330
|
+
|
|
295
331
|
```bash
|
|
296
332
|
gh workflow run update-screenshots.yml
|
|
297
333
|
```
|
|
298
334
|
|
|
299
335
|
**With custom message:**
|
|
336
|
+
|
|
300
337
|
```bash
|
|
301
338
|
gh workflow run update-screenshots.yml -f commit_message="chore: update screenshots after AlertGlass refactor"
|
|
302
339
|
```
|
|
303
340
|
|
|
304
341
|
**Check status:**
|
|
342
|
+
|
|
305
343
|
```bash
|
|
306
344
|
gh run list --workflow=update-screenshots.yml --limit 1
|
|
307
345
|
```
|
|
308
346
|
|
|
309
347
|
**View logs:**
|
|
348
|
+
|
|
310
349
|
```bash
|
|
311
350
|
gh run view <run-id> --log
|
|
312
351
|
```
|
|
313
352
|
|
|
314
353
|
**Download artifacts:**
|
|
354
|
+
|
|
315
355
|
```bash
|
|
316
356
|
gh run download <run-id> -n visual-test-screenshots
|
|
317
357
|
```
|
|
318
358
|
|
|
319
359
|
---
|
|
320
360
|
|
|
321
|
-
**Last Updated:** 2025-12-05
|
|
322
|
-
|
|
323
|
-
**Playwright Version:** Latest
|
|
324
|
-
**Node Version:** 20.16+, 22.19+, or 24+
|
|
361
|
+
**Last Updated:** 2025-12-05 **Vitest Version:** 4.0 **Playwright Version:** Latest **Node
|
|
362
|
+
Version:** 20.16+, 22.19+, or 24+
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shadcn-glass-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.0.
|
|
5
|
-
"description": "Glassmorphism UI
|
|
4
|
+
"version": "2.0.10",
|
|
5
|
+
"description": "Glassmorphism UI library for React - AI-friendly with 58 components, strict TypeScript, and comprehensive docs",
|
|
6
6
|
"author": "Yhooi2",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"keywords": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme-context-BZoCplcU.js","names":["THEMES: readonly Theme[]","THEME_CONFIG: Record<Theme, ThemeConfig>","DEFAULT_THEME: Theme","value: ThemeContextValue"],"sources":["../src/lib/theme-context.tsx"],"sourcesContent":["// ========================================\n// THEME SYSTEM\n// Multi-theme support with CSS variables\n// ========================================\n\nimport {\n createContext,\n useContext,\n useState,\n useEffect,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport { Sun, Moon, Palette, type LucideIcon } from \"lucide-react\";\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type Theme = \"light\" | \"aurora\" | \"glass\";\n\n/** @deprecated Use Theme instead */\nexport type ThemeName = Theme;\n\nexport interface ThemeConfig {\n readonly label: string;\n readonly icon: LucideIcon;\n}\n\nexport interface ThemeContextValue {\n readonly theme: Theme;\n readonly setTheme: (theme: Theme) => void;\n readonly cycleTheme: () => void;\n}\n\n// ========================================\n// CONSTANTS\n// ========================================\n\nexport const THEMES: readonly Theme[] = [\"light\", \"aurora\", \"glass\"] as const;\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport const THEME_CONFIG: Record<Theme, ThemeConfig> = {\n light: { label: \"Light\", icon: Sun },\n aurora: { label: \"Aurora\", icon: Moon },\n glass: { label: \"Glass\", icon: Palette },\n} as const;\n\nconst STORAGE_KEY = \"glass-ui-theme\";\nconst DEFAULT_THEME: Theme = \"glass\";\n\n// ========================================\n// CONTEXT\n// ========================================\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\n// ========================================\n// PROVIDER\n// ========================================\n\ninterface ThemeProviderProps {\n readonly children: ReactNode;\n readonly defaultTheme?: Theme;\n readonly storageKey?: string;\n}\n\nexport function ThemeProvider({\n children,\n defaultTheme = DEFAULT_THEME,\n storageKey = STORAGE_KEY,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<Theme>(() => {\n if (typeof window === \"undefined\") {\n return defaultTheme;\n }\n\n const stored = localStorage.getItem(storageKey);\n if (stored && THEMES.includes(stored as Theme)) {\n return stored as Theme;\n }\n\n return defaultTheme;\n });\n\n // Apply theme to document\n useEffect(() => {\n const root = document.documentElement;\n root.setAttribute(\"data-theme\", theme);\n localStorage.setItem(storageKey, theme);\n }, [theme, storageKey]);\n\n const setTheme = useCallback((newTheme: Theme) => {\n if (THEMES.includes(newTheme)) {\n setThemeState(newTheme);\n }\n }, []);\n\n const cycleTheme = useCallback(() => {\n setThemeState((current) => {\n const currentIndex = THEMES.indexOf(current);\n const nextIndex = (currentIndex + 1) % THEMES.length;\n return THEMES[nextIndex];\n });\n }, []);\n\n const value: ThemeContextValue = {\n theme,\n setTheme,\n cycleTheme,\n };\n\n return (\n <ThemeContext.Provider value={value}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n// ========================================\n// HOOK\n// ========================================\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useTheme(): ThemeContextValue {\n const context = useContext(ThemeContext);\n\n if (!context) {\n throw new Error(\"useTheme must be used within a ThemeProvider\");\n }\n\n return context;\n}\n\n// ========================================\n// UTILITIES\n// ========================================\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function getNextTheme(current: Theme): Theme {\n const currentIndex = THEMES.indexOf(current);\n const nextIndex = (currentIndex + 1) % THEMES.length;\n return THEMES[nextIndex];\n}\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function getThemeConfig(theme: Theme): ThemeConfig {\n return THEME_CONFIG[theme];\n}\n"],"mappings":";;;AAuCA,MAAaA,SAA2B;CAAC;CAAS;CAAU;CAAQ;AAGpE,MAAaC,eAA2C;CACtD,OAAO;EAAE,OAAO;EAAS,MAAM;EAAK;CACpC,QAAQ;EAAE,OAAO;EAAU,MAAM;EAAM;CACvC,OAAO;EAAE,OAAO;EAAS,MAAM;EAAS;CACzC;AAED,IAAM,cAAc;AACpB,IAAMC,gBAAuB;AAM7B,IAAM,eAAe,cAAwC,KAAK;AAYlE,SAAgB,cAAc,EAC5B,UACA,eAAe,eACf,aAAa,eACQ;CACrB,MAAM,CAAC,OAAO,iBAAiB,eAAsB;AACnD,MAAI,OAAO,WAAW,YACpB,QAAO;EAGT,MAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,MAAI,UAAU,OAAO,SAAS,OAAgB,CAC5C,QAAO;AAGT,SAAO;GACP;AAGF,iBAAgB;AACD,WAAS,gBACjB,aAAa,cAAc,MAAM;AACtC,eAAa,QAAQ,YAAY,MAAM;IACtC,CAAC,OAAO,WAAW,CAAC;CAgBvB,MAAMC,QAA2B;EAC/B;EACA,UAhBe,aAAa,aAAoB;AAChD,OAAI,OAAO,SAAS,SAAS,CAC3B,eAAc,SAAS;KAExB,EAAE,CAAC;EAaJ,YAXiB,kBAAkB;AACnC,kBAAe,YAAY;AAGzB,WAAO,QAFc,OAAO,QAAQ,QAAQ,GACV,KAAK,OAAO;KAE9C;KACD,EAAE,CAAC;EAML;AAED,QACE,oBAAC,aAAa,UAAA;EAAgB;EAC3B;GACqB;;AAS5B,SAAgB,WAA8B;CAC5C,MAAM,UAAU,WAAW,aAAa;AAExC,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,+CAA+C;AAGjE,QAAO;;AAQT,SAAgB,aAAa,SAAuB;AAGlD,QAAO,QAFc,OAAO,QAAQ,QAAQ,GACV,KAAK,OAAO;;AAKhD,SAAgB,eAAe,OAA2B;AACxD,QAAO,aAAa"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme-context-CVW50BKW.cjs","names":["THEMES: readonly Theme[]","THEME_CONFIG: Record<Theme, ThemeConfig>","DEFAULT_THEME: Theme","value: ThemeContextValue"],"sources":["../src/lib/theme-context.tsx"],"sourcesContent":["// ========================================\n// THEME SYSTEM\n// Multi-theme support with CSS variables\n// ========================================\n\nimport {\n createContext,\n useContext,\n useState,\n useEffect,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport { Sun, Moon, Palette, type LucideIcon } from \"lucide-react\";\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type Theme = \"light\" | \"aurora\" | \"glass\";\n\n/** @deprecated Use Theme instead */\nexport type ThemeName = Theme;\n\nexport interface ThemeConfig {\n readonly label: string;\n readonly icon: LucideIcon;\n}\n\nexport interface ThemeContextValue {\n readonly theme: Theme;\n readonly setTheme: (theme: Theme) => void;\n readonly cycleTheme: () => void;\n}\n\n// ========================================\n// CONSTANTS\n// ========================================\n\nexport const THEMES: readonly Theme[] = [\"light\", \"aurora\", \"glass\"] as const;\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport const THEME_CONFIG: Record<Theme, ThemeConfig> = {\n light: { label: \"Light\", icon: Sun },\n aurora: { label: \"Aurora\", icon: Moon },\n glass: { label: \"Glass\", icon: Palette },\n} as const;\n\nconst STORAGE_KEY = \"glass-ui-theme\";\nconst DEFAULT_THEME: Theme = \"glass\";\n\n// ========================================\n// CONTEXT\n// ========================================\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\n// ========================================\n// PROVIDER\n// ========================================\n\ninterface ThemeProviderProps {\n readonly children: ReactNode;\n readonly defaultTheme?: Theme;\n readonly storageKey?: string;\n}\n\nexport function ThemeProvider({\n children,\n defaultTheme = DEFAULT_THEME,\n storageKey = STORAGE_KEY,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<Theme>(() => {\n if (typeof window === \"undefined\") {\n return defaultTheme;\n }\n\n const stored = localStorage.getItem(storageKey);\n if (stored && THEMES.includes(stored as Theme)) {\n return stored as Theme;\n }\n\n return defaultTheme;\n });\n\n // Apply theme to document\n useEffect(() => {\n const root = document.documentElement;\n root.setAttribute(\"data-theme\", theme);\n localStorage.setItem(storageKey, theme);\n }, [theme, storageKey]);\n\n const setTheme = useCallback((newTheme: Theme) => {\n if (THEMES.includes(newTheme)) {\n setThemeState(newTheme);\n }\n }, []);\n\n const cycleTheme = useCallback(() => {\n setThemeState((current) => {\n const currentIndex = THEMES.indexOf(current);\n const nextIndex = (currentIndex + 1) % THEMES.length;\n return THEMES[nextIndex];\n });\n }, []);\n\n const value: ThemeContextValue = {\n theme,\n setTheme,\n cycleTheme,\n };\n\n return (\n <ThemeContext.Provider value={value}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n// ========================================\n// HOOK\n// ========================================\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useTheme(): ThemeContextValue {\n const context = useContext(ThemeContext);\n\n if (!context) {\n throw new Error(\"useTheme must be used within a ThemeProvider\");\n }\n\n return context;\n}\n\n// ========================================\n// UTILITIES\n// ========================================\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function getNextTheme(current: Theme): Theme {\n const currentIndex = THEMES.indexOf(current);\n const nextIndex = (currentIndex + 1) % THEMES.length;\n return THEMES[nextIndex];\n}\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function getThemeConfig(theme: Theme): ThemeConfig {\n return THEME_CONFIG[theme];\n}\n"],"mappings":";;;;AAuCA,MAAaA,SAA2B;CAAC;CAAS;CAAU;CAAQ;AAGpE,MAAaC,eAA2C;CACtD,OAAO;EAAE,OAAO;EAAS,MAAM,aAAA;EAAK;CACpC,QAAQ;EAAE,OAAO;EAAU,MAAM,aAAA;EAAM;CACvC,OAAO;EAAE,OAAO;EAAS,MAAM,aAAA;EAAS;CACzC;AAED,IAAM,cAAc;AACpB,IAAMC,gBAAuB;AAM7B,IAAM,gBAAA,GAAA,MAAA,eAAuD,KAAK;AAYlE,SAAgB,cAAc,EAC5B,UACA,eAAe,eACf,aAAa,eACQ;CACrB,MAAM,CAAC,OAAO,kBAAA,GAAA,MAAA,gBAAuC;AACnD,MAAI,OAAO,WAAW,YACpB,QAAO;EAGT,MAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,MAAI,UAAU,OAAO,SAAS,OAAgB,CAC5C,QAAO;AAGT,SAAO;GACP;AAGF,EAAA,GAAA,MAAA,iBAAgB;AACD,WAAS,gBACjB,aAAa,cAAc,MAAM;AACtC,eAAa,QAAQ,YAAY,MAAM;IACtC,CAAC,OAAO,WAAW,CAAC;CAgBvB,MAAMC,QAA2B;EAC/B;EACA,WAAA,GAAA,MAAA,cAhB4B,aAAoB;AAChD,OAAI,OAAO,SAAS,SAAS,CAC3B,eAAc,SAAS;KAExB,EAAE,CAAC;EAaJ,aAAA,GAAA,MAAA,mBAXmC;AACnC,kBAAe,YAAY;AAGzB,WAAO,QAFc,OAAO,QAAQ,QAAQ,GACV,KAAK,OAAO;KAE9C;KACD,EAAE,CAAC;EAML;AAED,QACE,iBAAA,GAAA,kBAAA,KAAC,aAAa,UAAA;EAAgB;EAC3B;GACqB;;AAS5B,SAAgB,WAA8B;CAC5C,MAAM,WAAA,GAAA,MAAA,YAAqB,aAAa;AAExC,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,+CAA+C;AAGjE,QAAO;;AAQT,SAAgB,aAAa,SAAuB;AAGlD,QAAO,QAFc,OAAO,QAAQ,QAAQ,GACV,KAAK,OAAO;;AAKhD,SAAgB,eAAe,OAA2B;AACxD,QAAO,aAAa"}
|