@pattern-stack/frontend-patterns 0.0.3 → 0.0.4
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/dist/index.es.js +1 -1
- package/dist/index.js +1 -0
- package/package.json +5 -3
- package/src/App.css +42 -0
- package/src/App.tsx +54 -0
- package/src/__tests__/README.md +221 -0
- package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
- package/src/__tests__/atoms/ui/button.test.tsx +68 -0
- package/src/__tests__/atoms/utils/simple.test.ts +18 -0
- package/src/__tests__/atoms/utils/utils.test.ts +77 -0
- package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
- package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
- package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
- package/src/__tests__/setup.ts +51 -0
- package/src/__tests__/utils.tsx +123 -0
- package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
- package/src/atoms/composed/Accordion/index.ts +1 -0
- package/src/atoms/composed/Alert/Alert.tsx +132 -0
- package/src/atoms/composed/Alert/index.ts +1 -0
- package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
- package/src/atoms/composed/Breadcrumb/index.ts +1 -0
- package/src/atoms/composed/Chart/Chart.tsx +425 -0
- package/src/atoms/composed/Chart/index.ts +2 -0
- package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
- package/src/atoms/composed/ColorSwatch/index.ts +1 -0
- package/src/atoms/composed/DarkModeToggle.tsx +66 -0
- package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
- package/src/atoms/composed/DataBadge/index.ts +1 -0
- package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
- package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
- package/src/atoms/composed/DataTable/index.ts +2 -0
- package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
- package/src/atoms/composed/DateTimePicker/index.ts +2 -0
- package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
- package/src/atoms/composed/DetailedCard/index.ts +2 -0
- package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
- package/src/atoms/composed/EmptyState/index.ts +1 -0
- package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
- package/src/atoms/composed/FileUpload/index.ts +2 -0
- package/src/atoms/composed/FormField/FormField.tsx +92 -0
- package/src/atoms/composed/FormField/index.ts +1 -0
- package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
- package/src/atoms/composed/GlobalSearch/index.ts +1 -0
- package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
- package/src/atoms/composed/IconBadge/index.ts +2 -0
- package/src/atoms/composed/Modal/Modal.tsx +223 -0
- package/src/atoms/composed/Modal/index.ts +2 -0
- package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
- package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
- package/src/atoms/composed/ProgressBar/index.ts +1 -0
- package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
- package/src/atoms/composed/StatCard/index.ts +1 -0
- package/src/atoms/composed/StyleGuide.tsx +717 -0
- package/src/atoms/composed/Toast/Toast.tsx +219 -0
- package/src/atoms/composed/Toast/index.ts +1 -0
- package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
- package/src/atoms/composed/Tooltip/index.ts +1 -0
- package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
- package/src/atoms/composed/UserAvatar/index.ts +1 -0
- package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
- package/src/atoms/composed/UserMenu/index.ts +1 -0
- package/src/atoms/composed/index.ts +29 -0
- package/src/atoms/hooks/useApi.ts +80 -0
- package/src/atoms/hooks/useHealth.ts +17 -0
- package/src/atoms/index.ts +13 -0
- package/src/atoms/services/api/client.ts +134 -0
- package/src/atoms/services/auth-service.ts +248 -0
- package/src/atoms/services/health.ts +15 -0
- package/src/atoms/services/index.ts +3 -0
- package/src/atoms/shared/config/constants.ts +17 -0
- package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
- package/src/atoms/shared/config/environment.ts +10 -0
- package/src/atoms/shared/index.ts +4 -0
- package/src/atoms/shared/styles/color-palettes.css +566 -0
- package/src/atoms/types/auth.ts +62 -0
- package/src/atoms/types/generated.ts +1469 -0
- package/src/atoms/types/index.ts +4 -0
- package/src/atoms/types/loading.ts +28 -0
- package/src/atoms/ui/Badge.tsx +30 -0
- package/src/atoms/ui/ErrorBoundary.tsx +59 -0
- package/src/atoms/ui/Select.tsx +53 -0
- package/src/atoms/ui/Switch.tsx +42 -0
- package/src/atoms/ui/Tabs.tsx +118 -0
- package/src/atoms/ui/avatar.tsx +48 -0
- package/src/atoms/ui/button.tsx +70 -0
- package/src/atoms/ui/card.tsx +76 -0
- package/src/atoms/ui/dropdown-menu.tsx +199 -0
- package/src/atoms/ui/index.ts +39 -0
- package/src/atoms/ui/input.tsx +23 -0
- package/src/atoms/ui/label.tsx +23 -0
- package/src/atoms/ui/skeleton.tsx +13 -0
- package/src/atoms/ui/spinner.tsx +49 -0
- package/src/atoms/ui/table.tsx +116 -0
- package/src/atoms/utils/animations.ts +135 -0
- package/src/atoms/utils/tooltip-helpers.ts +140 -0
- package/src/atoms/utils/utils.ts +9 -0
- package/src/features/auth/components/LoginForm.tsx +168 -0
- package/src/features/auth/components/LogoutButton.tsx +19 -0
- package/src/features/auth/components/ProtectedRoute.tsx +60 -0
- package/src/features/auth/components/index.ts +4 -0
- package/src/features/auth/hooks/index.ts +2 -0
- package/src/features/auth/hooks/useAuth.tsx +205 -0
- package/src/features/auth/hooks/usePermissions.ts +35 -0
- package/src/features/auth/index.ts +2 -0
- package/src/features/index.ts +2 -0
- package/src/index.css +704 -0
- package/src/index.ts +13 -0
- package/src/main.tsx +48 -0
- package/src/molecules/.gitkeep +0 -0
- package/src/molecules/forms/FormGroup.tsx +75 -0
- package/src/molecules/forms/SearchInput.tsx +259 -0
- package/src/molecules/forms/index.ts +4 -0
- package/src/molecules/index.ts +4 -0
- package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
- package/src/molecules/layout/AppHeader/index.ts +1 -0
- package/src/molecules/layout/AppLayout.tsx +29 -0
- package/src/molecules/layout/PageTemplate.tsx +87 -0
- package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
- package/src/molecules/layout/SectionHeader/index.ts +1 -0
- package/src/molecules/layout/ShowcaseSection.tsx +57 -0
- package/src/molecules/layout/Sidebar.tsx +144 -0
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
- package/src/molecules/layout/SidebarButton/index.ts +1 -0
- package/src/molecules/layout/SidebarContext.tsx +31 -0
- package/src/molecules/layout/index.ts +7 -0
- package/src/molecules/navigation/NavMenu.tsx +188 -0
- package/src/molecules/navigation/Pagination.tsx +172 -0
- package/src/molecules/navigation/index.ts +4 -0
- package/src/organisms/index.ts +5 -0
- package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
- package/src/organisms/showcase/index.ts +1 -0
- package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +171 -0
- package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
- package/src/pages/AdminShowcase/index.tsx +3 -0
- package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
- package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
- package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
- package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
- package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
- package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
- package/src/pages/ComponentShowcase/index.tsx +188 -0
- package/src/pages/index.ts +2 -0
- package/src/templates/AuthTemplate.tsx +216 -0
- package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
- package/src/templates/DashboardTemplate.tsx +232 -0
- package/src/templates/DataTemplate.tsx +319 -0
- package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
- package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
- package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
- package/src/templates/admin/index.ts +29 -0
- package/src/templates/factory.tsx +169 -0
- package/src/templates/index.ts +37 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { Card } from '../ui/card';
|
|
3
|
+
import { Button } from '../ui/button';
|
|
4
|
+
import { Badge } from '../ui/Badge';
|
|
5
|
+
import { cn } from '../utils/utils';
|
|
6
|
+
import {
|
|
7
|
+
Palette,
|
|
8
|
+
Sun,
|
|
9
|
+
Moon,
|
|
10
|
+
Waves,
|
|
11
|
+
Zap,
|
|
12
|
+
TreePine,
|
|
13
|
+
Sparkles,
|
|
14
|
+
Sunset,
|
|
15
|
+
Square,
|
|
16
|
+
Building2
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
|
|
19
|
+
interface ColorPalette {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
className: string;
|
|
24
|
+
icon: React.ReactNode;
|
|
25
|
+
category: 'vibrant' | 'natural' | 'professional' | 'mystical';
|
|
26
|
+
preview: {
|
|
27
|
+
primary: string;
|
|
28
|
+
secondary: string;
|
|
29
|
+
accent: string;
|
|
30
|
+
background: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colorPalettes: ColorPalette[] = [
|
|
35
|
+
{
|
|
36
|
+
id: 'default',
|
|
37
|
+
name: 'Default',
|
|
38
|
+
description: 'Clean, professional design system',
|
|
39
|
+
className: '',
|
|
40
|
+
icon: <Square className="w-4 h-4" />,
|
|
41
|
+
category: 'professional',
|
|
42
|
+
preview: {
|
|
43
|
+
primary: 'hsl(222.2 47.4% 11.2%)',
|
|
44
|
+
secondary: 'hsl(210 40% 96%)',
|
|
45
|
+
accent: 'hsl(210 40% 96%)',
|
|
46
|
+
background: 'hsl(0 0% 100%)'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'ocean-depth',
|
|
51
|
+
name: 'Ocean Depth',
|
|
52
|
+
description: 'Deep blues with vibrant cyan and coral accents',
|
|
53
|
+
className: 'palette-ocean-depth',
|
|
54
|
+
icon: <Waves className="w-4 h-4" />,
|
|
55
|
+
category: 'vibrant',
|
|
56
|
+
preview: {
|
|
57
|
+
primary: 'hsl(195 100% 65%)',
|
|
58
|
+
secondary: 'hsl(220 20% 25%)',
|
|
59
|
+
accent: 'hsl(280 100% 70%)',
|
|
60
|
+
background: 'hsl(220 26% 14%)'
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'neon-cyber',
|
|
65
|
+
name: 'Neon Cyber',
|
|
66
|
+
description: 'Vibrant cyberpunk aesthetics with electric colors',
|
|
67
|
+
className: 'palette-neon-cyber',
|
|
68
|
+
icon: <Zap className="w-4 h-4" />,
|
|
69
|
+
category: 'vibrant',
|
|
70
|
+
preview: {
|
|
71
|
+
primary: 'hsl(320 100% 70%)',
|
|
72
|
+
secondary: 'hsl(260 20% 18%)',
|
|
73
|
+
accent: 'hsl(180 100% 70%)',
|
|
74
|
+
background: 'hsl(260 15% 8%)'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'earth-tones',
|
|
79
|
+
name: 'Earth Tones',
|
|
80
|
+
description: 'Warm, organic colors inspired by nature',
|
|
81
|
+
className: 'palette-earth-tones',
|
|
82
|
+
icon: <TreePine className="w-4 h-4" />,
|
|
83
|
+
category: 'natural',
|
|
84
|
+
preview: {
|
|
85
|
+
primary: 'hsl(25 70% 45%)',
|
|
86
|
+
secondary: 'hsl(35 20% 88%)',
|
|
87
|
+
accent: 'hsl(200 60% 55%)',
|
|
88
|
+
background: 'hsl(35 25% 96%)'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'aurora',
|
|
93
|
+
name: 'Aurora Borealis',
|
|
94
|
+
description: 'Mystical greens and blues like the northern lights',
|
|
95
|
+
className: 'palette-aurora',
|
|
96
|
+
icon: <Sparkles className="w-4 h-4" />,
|
|
97
|
+
category: 'mystical',
|
|
98
|
+
preview: {
|
|
99
|
+
primary: 'hsl(160 80% 65%)',
|
|
100
|
+
secondary: 'hsl(220 20% 20%)',
|
|
101
|
+
accent: 'hsl(280 90% 75%)',
|
|
102
|
+
background: 'hsl(220 30% 8%)'
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'sunset',
|
|
107
|
+
name: 'Sunset Gradient',
|
|
108
|
+
description: 'Warm oranges, pinks, and purples',
|
|
109
|
+
className: 'palette-sunset',
|
|
110
|
+
icon: <Sunset className="w-4 h-4" />,
|
|
111
|
+
category: 'natural',
|
|
112
|
+
preview: {
|
|
113
|
+
primary: 'hsl(25 85% 60%)',
|
|
114
|
+
secondary: 'hsl(15 25% 88%)',
|
|
115
|
+
accent: 'hsl(320 85% 65%)',
|
|
116
|
+
background: 'hsl(15 30% 95%)'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'monochrome',
|
|
121
|
+
name: 'Monochrome Elite',
|
|
122
|
+
description: 'Sophisticated grayscale with blue accents',
|
|
123
|
+
className: 'palette-monochrome',
|
|
124
|
+
icon: <Square className="w-4 h-4" />,
|
|
125
|
+
category: 'professional',
|
|
126
|
+
preview: {
|
|
127
|
+
primary: 'hsl(240 100% 50%)',
|
|
128
|
+
secondary: 'hsl(0 0% 90%)',
|
|
129
|
+
accent: 'hsl(240 100% 50%)',
|
|
130
|
+
background: 'hsl(0 0% 98%)'
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'corporate-elite',
|
|
135
|
+
name: 'Corporate Elite',
|
|
136
|
+
description: 'Executive boardroom sophistication with navy and gold',
|
|
137
|
+
className: 'palette-corporate-elite',
|
|
138
|
+
icon: <Building2 className="w-4 h-4" />,
|
|
139
|
+
category: 'professional',
|
|
140
|
+
preview: {
|
|
141
|
+
primary: 'hsl(224 65% 25%)',
|
|
142
|
+
secondary: 'hsl(220 10% 88%)',
|
|
143
|
+
accent: 'hsl(45 85% 55%)',
|
|
144
|
+
background: 'hsl(220 15% 97%)'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const categoryColors = {
|
|
150
|
+
vibrant: 'bg-category-1',
|
|
151
|
+
natural: 'bg-category-6',
|
|
152
|
+
professional: 'bg-category-4',
|
|
153
|
+
mystical: 'bg-category-5'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
interface PaletteSwitcherProps {
|
|
157
|
+
className?: string;
|
|
158
|
+
onPaletteChange?: (paletteId: string) => void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const PaletteSwitcher = ({
|
|
162
|
+
className,
|
|
163
|
+
onPaletteChange
|
|
164
|
+
}: PaletteSwitcherProps) => {
|
|
165
|
+
const [activePalette, setActivePalette] = useState('default');
|
|
166
|
+
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
167
|
+
|
|
168
|
+
// Define applyPalette function with useCallback to prevent recreation on each render
|
|
169
|
+
const applyPalette = useCallback((paletteId: string) => {
|
|
170
|
+
const palette = colorPalettes.find(p => p.id === paletteId);
|
|
171
|
+
if (!palette) return;
|
|
172
|
+
|
|
173
|
+
// Remove all existing palette classes
|
|
174
|
+
colorPalettes.forEach(p => {
|
|
175
|
+
if (p.className) {
|
|
176
|
+
document.documentElement.classList.remove(p.className);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Apply new palette class
|
|
181
|
+
if (palette.className) {
|
|
182
|
+
document.documentElement.classList.add(palette.className);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Save to localStorage
|
|
186
|
+
localStorage.setItem('selected-palette', paletteId);
|
|
187
|
+
|
|
188
|
+
// Notify parent component
|
|
189
|
+
onPaletteChange?.(paletteId);
|
|
190
|
+
}, [onPaletteChange]);
|
|
191
|
+
|
|
192
|
+
// Load saved palette from localStorage
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const saved = localStorage.getItem('selected-palette');
|
|
195
|
+
const savedDark = localStorage.getItem('dark-mode') === 'true';
|
|
196
|
+
|
|
197
|
+
if (saved && colorPalettes.find(p => p.id === saved)) {
|
|
198
|
+
setActivePalette(saved);
|
|
199
|
+
applyPalette(saved);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
setIsDarkMode(savedDark);
|
|
203
|
+
if (savedDark) {
|
|
204
|
+
document.documentElement.classList.add('dark');
|
|
205
|
+
}
|
|
206
|
+
}, [applyPalette]);
|
|
207
|
+
|
|
208
|
+
const handlePaletteChange = (paletteId: string) => {
|
|
209
|
+
setActivePalette(paletteId);
|
|
210
|
+
applyPalette(paletteId);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const toggleDarkMode = () => {
|
|
214
|
+
const newDarkMode = !isDarkMode;
|
|
215
|
+
setIsDarkMode(newDarkMode);
|
|
216
|
+
|
|
217
|
+
if (newDarkMode) {
|
|
218
|
+
document.documentElement.classList.add('dark');
|
|
219
|
+
} else {
|
|
220
|
+
document.documentElement.classList.remove('dark');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
localStorage.setItem('dark-mode', newDarkMode.toString());
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const groupedPalettes = colorPalettes.reduce((groups, palette) => {
|
|
227
|
+
const category = palette.category;
|
|
228
|
+
if (!groups[category]) {
|
|
229
|
+
groups[category] = [];
|
|
230
|
+
}
|
|
231
|
+
groups[category].push(palette);
|
|
232
|
+
return groups;
|
|
233
|
+
}, {} as Record<string, ColorPalette[]>);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div className={cn("space-y-6", className)} data-component-name="PaletteSwitcher">
|
|
237
|
+
{/* Header */}
|
|
238
|
+
<div className="flex items-center justify-between">
|
|
239
|
+
<div className="flex items-center gap-2">
|
|
240
|
+
<Palette className="w-5 h-5 text-primary" data-component-name="PaletteSwitcherIcon" />
|
|
241
|
+
<h3 className="text-lg font-semibold">Color Palettes</h3>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<Button
|
|
245
|
+
variant="outline"
|
|
246
|
+
size="sm"
|
|
247
|
+
onClick={toggleDarkMode}
|
|
248
|
+
className="flex items-center gap-2"
|
|
249
|
+
>
|
|
250
|
+
{isDarkMode ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
|
251
|
+
{isDarkMode ? 'Light' : 'Dark'}
|
|
252
|
+
</Button>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Active Palette Info */}
|
|
256
|
+
<Card className="p-4" data-component-name="CategoryColorPreview">
|
|
257
|
+
<div className="flex items-center gap-3">
|
|
258
|
+
<div className="p-2 rounded bg-primary/10 text-primary">
|
|
259
|
+
{colorPalettes.find(p => p.id === activePalette)?.icon}
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex-1">
|
|
262
|
+
<h4 className="font-medium">
|
|
263
|
+
{colorPalettes.find(p => p.id === activePalette)?.name}
|
|
264
|
+
</h4>
|
|
265
|
+
<p className="text-sm text-muted-foreground">
|
|
266
|
+
{colorPalettes.find(p => p.id === activePalette)?.description}
|
|
267
|
+
</p>
|
|
268
|
+
</div>
|
|
269
|
+
<Badge
|
|
270
|
+
variant="secondary"
|
|
271
|
+
className={cn(
|
|
272
|
+
"text-xs",
|
|
273
|
+
categoryColors[colorPalettes.find(p => p.id === activePalette)?.category || 'professional']
|
|
274
|
+
)}
|
|
275
|
+
>
|
|
276
|
+
{colorPalettes.find(p => p.id === activePalette)?.category}
|
|
277
|
+
</Badge>
|
|
278
|
+
</div>
|
|
279
|
+
</Card>
|
|
280
|
+
|
|
281
|
+
{/* Palette Grid by Category */}
|
|
282
|
+
<div className="space-y-6">
|
|
283
|
+
{Object.entries(groupedPalettes).map(([category, palettes]) => (
|
|
284
|
+
<div key={category}>
|
|
285
|
+
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wide mb-3">
|
|
286
|
+
{category}
|
|
287
|
+
</h4>
|
|
288
|
+
|
|
289
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
290
|
+
{palettes.map((palette) => (
|
|
291
|
+
<Card
|
|
292
|
+
key={palette.id}
|
|
293
|
+
className={cn(
|
|
294
|
+
"p-4 cursor-pointer transition-all hover:shadow",
|
|
295
|
+
activePalette === palette.id && "ring-2 ring-primary bg-primary/5"
|
|
296
|
+
)}
|
|
297
|
+
onClick={() => handlePaletteChange(palette.id)}
|
|
298
|
+
data-component-name="PaletteCard"
|
|
299
|
+
data-active={activePalette === palette.id}
|
|
300
|
+
>
|
|
301
|
+
<div className="flex items-center gap-3">
|
|
302
|
+
<div className="p-2 rounded bg-muted text-muted-foreground">
|
|
303
|
+
{palette.icon}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div className="flex-1 min-w-0">
|
|
307
|
+
<div className="font-medium truncate">{palette.name}</div>
|
|
308
|
+
<div className="text-sm text-muted-foreground truncate">
|
|
309
|
+
{palette.description}
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
{/* Color Preview */}
|
|
314
|
+
<div className="flex gap-1">
|
|
315
|
+
<div
|
|
316
|
+
className="w-3 h-3 rounded-full border border-border"
|
|
317
|
+
style={{ backgroundColor: palette.preview.primary }}
|
|
318
|
+
data-component-name="PaletteColorPreview"
|
|
319
|
+
/>
|
|
320
|
+
<div
|
|
321
|
+
className="w-3 h-3 rounded-full border border-border"
|
|
322
|
+
style={{ backgroundColor: palette.preview.accent }}
|
|
323
|
+
data-component-name="PaletteColorPreview"
|
|
324
|
+
/>
|
|
325
|
+
<div
|
|
326
|
+
className="w-3 h-3 rounded-full border border-border"
|
|
327
|
+
style={{ backgroundColor: palette.preview.secondary }}
|
|
328
|
+
data-component-name="PaletteColorPreview"
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
</Card>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
))}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
{/* Category Color Preview */}
|
|
340
|
+
<Card className="p-4" data-component-name="CategoryColorPreview">
|
|
341
|
+
<h4 className="text-sm font-medium mb-3">Category Colors Preview</h4>
|
|
342
|
+
<div className="grid grid-cols-4 md:grid-cols-8 gap-2">
|
|
343
|
+
{[1, 2, 3, 4, 5, 6, 7, 8].map(num => (
|
|
344
|
+
<div key={num} className="text-center">
|
|
345
|
+
<div
|
|
346
|
+
className={cn(
|
|
347
|
+
"w-8 h-8 rounded mx-auto mb-1 border border-border",
|
|
348
|
+
`bg-category-${num}`
|
|
349
|
+
)}
|
|
350
|
+
data-component-name="ColorSwatch"
|
|
351
|
+
data-category={num}
|
|
352
|
+
/>
|
|
353
|
+
<div className="text-xs text-muted-foreground">{num}</div>
|
|
354
|
+
</div>
|
|
355
|
+
))}
|
|
356
|
+
</div>
|
|
357
|
+
</Card>
|
|
358
|
+
|
|
359
|
+
{/* Status Colors Preview */}
|
|
360
|
+
<Card className="p-4" data-component-name="StatusColorPreview">
|
|
361
|
+
<h4 className="text-sm font-medium mb-3">Status Colors Preview</h4>
|
|
362
|
+
<div className="grid grid-cols-5 gap-2">
|
|
363
|
+
{[
|
|
364
|
+
{ name: 'Success', class: 'bg-status-success' },
|
|
365
|
+
{ name: 'Warning', class: 'bg-status-warning' },
|
|
366
|
+
{ name: 'Error', class: 'bg-status-error' },
|
|
367
|
+
{ name: 'Info', class: 'bg-status-info' },
|
|
368
|
+
{ name: 'Neutral', class: 'bg-status-neutral' }
|
|
369
|
+
].map(status => (
|
|
370
|
+
<div key={status.name} className="text-center">
|
|
371
|
+
<div
|
|
372
|
+
className={cn(
|
|
373
|
+
"w-8 h-8 rounded mx-auto mb-1 border border-border",
|
|
374
|
+
status.class
|
|
375
|
+
)}
|
|
376
|
+
data-component-name="StatusSwatch"
|
|
377
|
+
data-status={status.name.toLowerCase()}
|
|
378
|
+
/>
|
|
379
|
+
<div className="text-xs text-muted-foreground">{status.name}</div>
|
|
380
|
+
</div>
|
|
381
|
+
))}
|
|
382
|
+
</div>
|
|
383
|
+
</Card>
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from '../../utils/utils';
|
|
3
|
+
import { getAnimationClasses, animationPresets } from '../../utils/animations';
|
|
4
|
+
|
|
5
|
+
export interface ProgressBarProps {
|
|
6
|
+
/** Progress value (0-100) */
|
|
7
|
+
value: number;
|
|
8
|
+
/** Maximum value */
|
|
9
|
+
max?: number;
|
|
10
|
+
/** Status-based coloring */
|
|
11
|
+
status?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
12
|
+
/** Category-based coloring */
|
|
13
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
14
|
+
/** Size variant */
|
|
15
|
+
size?: 'sm' | 'md' | 'lg';
|
|
16
|
+
/** Show percentage label */
|
|
17
|
+
showLabel?: boolean;
|
|
18
|
+
/** Custom label */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Progress bar variant */
|
|
21
|
+
variant?: 'default' | 'striped' | 'animated';
|
|
22
|
+
/** Additional CSS classes */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Accessible label */
|
|
25
|
+
'aria-label'?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
|
29
|
+
value,
|
|
30
|
+
max = 100,
|
|
31
|
+
status,
|
|
32
|
+
category,
|
|
33
|
+
size = 'md',
|
|
34
|
+
showLabel = false,
|
|
35
|
+
label,
|
|
36
|
+
variant = 'default',
|
|
37
|
+
className,
|
|
38
|
+
'aria-label': ariaLabel
|
|
39
|
+
}) => {
|
|
40
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
41
|
+
|
|
42
|
+
const sizeClasses = {
|
|
43
|
+
sm: 'h-2',
|
|
44
|
+
md: 'h-3',
|
|
45
|
+
lg: 'h-4'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const trackClasses = 'w-full bg-muted rounded-full overflow-hidden';
|
|
49
|
+
|
|
50
|
+
const getBarClasses = () => {
|
|
51
|
+
let baseClasses = 'h-full transition-all duration-500 ease-out';
|
|
52
|
+
|
|
53
|
+
// Color classes
|
|
54
|
+
if (status) {
|
|
55
|
+
baseClasses += ` bg-status-${status}`;
|
|
56
|
+
} else if (category) {
|
|
57
|
+
baseClasses += ` bg-category-${category}`;
|
|
58
|
+
} else {
|
|
59
|
+
baseClasses += ' bg-primary';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Variant classes
|
|
63
|
+
if (variant === 'striped') {
|
|
64
|
+
baseClasses += ' bg-gradient-to-r from-current via-current/80 to-current';
|
|
65
|
+
baseClasses += ' bg-[length:1rem_1rem]';
|
|
66
|
+
baseClasses += ' bg-repeat-x';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (variant === 'animated') {
|
|
70
|
+
baseClasses += ' bg-gradient-to-r from-current via-current/60 to-current';
|
|
71
|
+
baseClasses += ' bg-[length:2rem_100%]';
|
|
72
|
+
baseClasses += ' animate-pulse';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return baseClasses;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const displayLabel = label || (showLabel ? `${Math.round(percentage)}%` : undefined);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={cn('w-full', className)}
|
|
83
|
+
data-component-name="ProgressBar"
|
|
84
|
+
>
|
|
85
|
+
{displayLabel && (
|
|
86
|
+
<div className="flex justify-between items-center mb-2 text-sm">
|
|
87
|
+
<span className="text-foreground font-medium">
|
|
88
|
+
{displayLabel}
|
|
89
|
+
</span>
|
|
90
|
+
{showLabel && !label && (
|
|
91
|
+
<span className="text-muted-foreground">
|
|
92
|
+
{value}/{max}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
<div
|
|
99
|
+
className={cn(trackClasses, sizeClasses[size])}
|
|
100
|
+
role="progressbar"
|
|
101
|
+
aria-valuenow={value}
|
|
102
|
+
aria-valuemin={0}
|
|
103
|
+
aria-valuemax={max}
|
|
104
|
+
aria-label={ariaLabel || `Progress: ${percentage.toFixed(1)}%`}
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
className={cn(
|
|
108
|
+
getBarClasses(),
|
|
109
|
+
getAnimationClasses(animationPresets.subtle)
|
|
110
|
+
)}
|
|
111
|
+
style={{ width: `${percentage}%` }}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProgressBar, type ProgressBarProps } from './ProgressBar';
|