@djangocfg/layouts 1.2.4 → 1.2.6
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/package.json +5 -5
- package/src/auth/context/AuthContext.tsx +24 -18
- package/src/auth/context/types.ts +4 -0
- package/src/layouts/AppLayout/AppLayout.tsx +34 -31
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/UILayout/config/components/data.config.tsx +125 -0
- package/src/layouts/UILayout/config/components/feedback.config.tsx +44 -0
- package/src/layouts/UILayout/config/components/forms.config.tsx +194 -0
- package/src/layouts/UILayout/config/components/layout.config.tsx +95 -0
- package/src/layouts/UILayout/config/components/navigation.config.tsx +50 -0
- package/src/layouts/UILayout/config/components/specialized.config.tsx +250 -0
- package/src/utils/logger.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@djangocfg/api": "^1.2.
|
|
57
|
-
"@djangocfg/og-image": "^1.2.
|
|
58
|
-
"@djangocfg/ui": "^1.2.
|
|
56
|
+
"@djangocfg/api": "^1.2.6",
|
|
57
|
+
"@djangocfg/og-image": "^1.2.6",
|
|
58
|
+
"@djangocfg/ui": "^1.2.6",
|
|
59
59
|
"@hookform/resolvers": "^5.2.0",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
79
|
+
"@djangocfg/typescript-config": "^1.2.6",
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "19.2.2",
|
|
82
82
|
"@types/react-dom": "19.2.1",
|
|
@@ -101,14 +101,16 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
101
101
|
|
|
102
102
|
// Refresh profile from AccountsContext
|
|
103
103
|
const refreshedProfile = await accounts.refreshProfile();
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
if (refreshedProfile) {
|
|
106
|
-
setInitialized(true);
|
|
107
106
|
authLogger.info('Profile loaded successfully:', refreshedProfile.id);
|
|
108
107
|
} else {
|
|
109
|
-
authLogger.warn('Profile refresh returned undefined');
|
|
110
|
-
clearAuthState('loadCurrentProfile:noProfile');
|
|
108
|
+
authLogger.warn('Profile refresh returned undefined - but keeping tokens');
|
|
111
109
|
}
|
|
110
|
+
|
|
111
|
+
// Always mark as initialized if we have valid tokens
|
|
112
|
+
// Don't clear tokens just because profile fetch failed
|
|
113
|
+
setInitialized(true);
|
|
112
114
|
} catch (error) {
|
|
113
115
|
authLogger.error('Failed to load profile:', error);
|
|
114
116
|
// Use global error handler first, fallback to clearing state
|
|
@@ -166,7 +168,8 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
166
168
|
useEffect(() => {
|
|
167
169
|
if (!initialized) return;
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
// Consider authenticated if we have valid tokens, even without profile
|
|
172
|
+
const isAuthenticated = api.isAuthenticated();
|
|
170
173
|
const authRoute = config?.routes?.auth || defaultRoutes.auth;
|
|
171
174
|
const isAuthPage = router.pathname === authRoute;
|
|
172
175
|
|
|
@@ -408,9 +411,12 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
408
411
|
() => ({
|
|
409
412
|
user,
|
|
410
413
|
isLoading,
|
|
411
|
-
|
|
414
|
+
// Consider authenticated if we have valid tokens, even without user profile
|
|
415
|
+
isAuthenticated: api.isAuthenticated(),
|
|
412
416
|
loadCurrentProfile,
|
|
413
417
|
checkAuthAndRedirect,
|
|
418
|
+
getToken: () => api.getToken(),
|
|
419
|
+
getRefreshToken: () => api.getRefreshToken(),
|
|
414
420
|
getSavedEmail: () => storedEmail,
|
|
415
421
|
saveEmail: setStoredEmail,
|
|
416
422
|
clearSavedEmail: clearStoredEmail,
|
|
@@ -429,24 +435,24 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
429
435
|
saveCurrentUrlForRedirect,
|
|
430
436
|
}),
|
|
431
437
|
[
|
|
432
|
-
user,
|
|
433
|
-
isLoading,
|
|
434
|
-
loadCurrentProfile,
|
|
435
|
-
checkAuthAndRedirect,
|
|
438
|
+
user,
|
|
439
|
+
isLoading,
|
|
440
|
+
loadCurrentProfile,
|
|
441
|
+
checkAuthAndRedirect,
|
|
436
442
|
storedEmail,
|
|
437
443
|
setStoredEmail,
|
|
438
444
|
clearStoredEmail,
|
|
439
445
|
storedPhone,
|
|
440
446
|
setStoredPhone,
|
|
441
447
|
clearStoredPhone,
|
|
442
|
-
requestOTP,
|
|
443
|
-
verifyOTP,
|
|
444
|
-
refreshToken,
|
|
445
|
-
logout,
|
|
446
|
-
getSavedRedirectUrl,
|
|
447
|
-
saveRedirectUrl,
|
|
448
|
-
clearSavedRedirectUrl,
|
|
449
|
-
getFinalRedirectUrl,
|
|
448
|
+
requestOTP,
|
|
449
|
+
verifyOTP,
|
|
450
|
+
refreshToken,
|
|
451
|
+
logout,
|
|
452
|
+
getSavedRedirectUrl,
|
|
453
|
+
saveRedirectUrl,
|
|
454
|
+
clearSavedRedirectUrl,
|
|
455
|
+
getFinalRedirectUrl,
|
|
450
456
|
useAndClearRedirectUrl,
|
|
451
457
|
saveCurrentUrlForRedirect,
|
|
452
458
|
],
|
|
@@ -31,6 +31,10 @@ export interface AuthContextType {
|
|
|
31
31
|
loadCurrentProfile: () => Promise<void>;
|
|
32
32
|
checkAuthAndRedirect: () => Promise<void>;
|
|
33
33
|
|
|
34
|
+
// Token Methods
|
|
35
|
+
getToken: () => string | null;
|
|
36
|
+
getRefreshToken: () => string | null;
|
|
37
|
+
|
|
34
38
|
// Email Methods
|
|
35
39
|
getSavedEmail: () => string | null;
|
|
36
40
|
saveEmail: (email: string) => void;
|
|
@@ -90,24 +90,42 @@ function LayoutRouter({
|
|
|
90
90
|
return <>{children}</>;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
// Check route type (synchronous - works with SSR)
|
|
94
|
+
const isAuthRoute = config.routes.detectors.isAuthRoute(router.pathname);
|
|
95
|
+
const isPrivateRoute = config.routes.detectors.isPrivateRoute(router.pathname);
|
|
96
|
+
|
|
97
|
+
// Private routes: Always show loading during SSR and initial client render
|
|
98
|
+
// This prevents hydration mismatch when isAuthenticated differs between server/client
|
|
99
|
+
if (isPrivateRoute && !forceLayout) {
|
|
100
|
+
if (!isMounted || isLoading) {
|
|
101
|
+
return (
|
|
102
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
103
|
+
<div className="text-muted-foreground">Loading...</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
// After mount: check authentication
|
|
109
|
+
if (!isAuthenticated) {
|
|
110
|
+
// Redirect to auth (handled in PrivateLayout)
|
|
111
|
+
return (
|
|
112
|
+
<AuthLayout
|
|
113
|
+
termsUrl={config.auth?.termsUrl}
|
|
114
|
+
privacyUrl={config.auth?.privacyUrl}
|
|
115
|
+
supportUrl={config.auth?.supportUrl}
|
|
116
|
+
enablePhoneAuth={config.auth?.enablePhoneAuth}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
103
120
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return 'private';
|
|
107
|
-
};
|
|
108
|
-
return 'auth';
|
|
109
|
-
};
|
|
121
|
+
return <PrivateLayout>{children}</PrivateLayout>;
|
|
122
|
+
}
|
|
110
123
|
|
|
124
|
+
// Determine layout mode for non-private routes
|
|
125
|
+
const getLayoutMode = (): 'public' | 'auth' => {
|
|
126
|
+
if (forceLayout === 'auth') return 'auth';
|
|
127
|
+
if (forceLayout === 'public') return 'public';
|
|
128
|
+
if (isAuthRoute) return 'auth';
|
|
111
129
|
return 'public';
|
|
112
130
|
};
|
|
113
131
|
|
|
@@ -121,9 +139,6 @@ function LayoutRouter({
|
|
|
121
139
|
|
|
122
140
|
// Auth routes: render inside AuthLayout
|
|
123
141
|
case 'auth':
|
|
124
|
-
// Check if we're on a private route that requires auth
|
|
125
|
-
const isPrivateRoute = config.routes.detectors.isPrivateRoute(router.pathname);
|
|
126
|
-
|
|
127
142
|
return (
|
|
128
143
|
<AuthLayout
|
|
129
144
|
termsUrl={config.auth?.termsUrl}
|
|
@@ -131,22 +146,10 @@ function LayoutRouter({
|
|
|
131
146
|
supportUrl={config.auth?.supportUrl}
|
|
132
147
|
enablePhoneAuth={config.auth?.enablePhoneAuth}
|
|
133
148
|
>
|
|
134
|
-
{
|
|
135
|
-
{!isPrivateRoute && children}
|
|
149
|
+
{children}
|
|
136
150
|
</AuthLayout>
|
|
137
151
|
);
|
|
138
152
|
|
|
139
|
-
// Private routes: wait for client-side hydration and auth check
|
|
140
|
-
case 'private':
|
|
141
|
-
if (!isMounted || isLoading) {
|
|
142
|
-
return (
|
|
143
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
144
|
-
<div className="text-muted-foreground">Loading...</div>
|
|
145
|
-
</div>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return <PrivateLayout>{children}</PrivateLayout>;
|
|
149
|
-
|
|
150
153
|
default:
|
|
151
154
|
return <PublicLayout>{children}</PublicLayout>;
|
|
152
155
|
}
|
|
@@ -16,36 +16,36 @@ export interface PackageInfo {
|
|
|
16
16
|
/**
|
|
17
17
|
* Package versions registry
|
|
18
18
|
* Auto-synced from package.json files
|
|
19
|
-
* Last updated: 2025-10-
|
|
19
|
+
* Last updated: 2025-10-27T16:32:06.128Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.6"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.6"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.6"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.6"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.6"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.6"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.6"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -21,6 +21,17 @@ import {
|
|
|
21
21
|
Toggle,
|
|
22
22
|
ToggleGroup,
|
|
23
23
|
ToggleGroupItem,
|
|
24
|
+
Calendar,
|
|
25
|
+
Carousel,
|
|
26
|
+
CarouselContent,
|
|
27
|
+
CarouselItem,
|
|
28
|
+
CarouselNext,
|
|
29
|
+
CarouselPrevious,
|
|
30
|
+
ChartContainer,
|
|
31
|
+
ChartTooltip,
|
|
32
|
+
ChartTooltipContent,
|
|
33
|
+
ChartLegend,
|
|
34
|
+
ChartLegendContent,
|
|
24
35
|
} from '@djangocfg/ui';
|
|
25
36
|
import type { ComponentConfig } from './types';
|
|
26
37
|
|
|
@@ -305,4 +316,118 @@ export const DATA_COMPONENTS: ComponentConfig[] = [
|
|
|
305
316
|
</div>
|
|
306
317
|
),
|
|
307
318
|
},
|
|
319
|
+
{
|
|
320
|
+
name: 'Calendar',
|
|
321
|
+
category: 'data',
|
|
322
|
+
description: 'Date picker calendar component',
|
|
323
|
+
importPath: `import { Calendar } from '@djangocfg/ui';`,
|
|
324
|
+
example: `<Calendar
|
|
325
|
+
mode="single"
|
|
326
|
+
selected={date}
|
|
327
|
+
onSelect={setDate}
|
|
328
|
+
className="rounded-md border"
|
|
329
|
+
/>`,
|
|
330
|
+
preview: (
|
|
331
|
+
<Calendar
|
|
332
|
+
mode="single"
|
|
333
|
+
className="rounded-md border"
|
|
334
|
+
/>
|
|
335
|
+
),
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'Carousel',
|
|
339
|
+
category: 'data',
|
|
340
|
+
description: 'Image and content carousel with navigation',
|
|
341
|
+
importPath: `import {
|
|
342
|
+
Carousel,
|
|
343
|
+
CarouselContent,
|
|
344
|
+
CarouselItem,
|
|
345
|
+
CarouselNext,
|
|
346
|
+
CarouselPrevious,
|
|
347
|
+
} from '@djangocfg/ui';`,
|
|
348
|
+
example: `<Carousel className="w-full max-w-xs">
|
|
349
|
+
<CarouselContent>
|
|
350
|
+
<CarouselItem>
|
|
351
|
+
<div className="p-6 border rounded-md">
|
|
352
|
+
<span className="text-4xl font-semibold">1</span>
|
|
353
|
+
</div>
|
|
354
|
+
</CarouselItem>
|
|
355
|
+
<CarouselItem>
|
|
356
|
+
<div className="p-6 border rounded-md">
|
|
357
|
+
<span className="text-4xl font-semibold">2</span>
|
|
358
|
+
</div>
|
|
359
|
+
</CarouselItem>
|
|
360
|
+
<CarouselItem>
|
|
361
|
+
<div className="p-6 border rounded-md">
|
|
362
|
+
<span className="text-4xl font-semibold">3</span>
|
|
363
|
+
</div>
|
|
364
|
+
</CarouselItem>
|
|
365
|
+
</CarouselContent>
|
|
366
|
+
<CarouselPrevious />
|
|
367
|
+
<CarouselNext />
|
|
368
|
+
</Carousel>`,
|
|
369
|
+
preview: (
|
|
370
|
+
<Carousel className="w-full max-w-xs">
|
|
371
|
+
<CarouselContent>
|
|
372
|
+
{Array.from({ length: 5 }).map((_, index) => (
|
|
373
|
+
<CarouselItem key={index}>
|
|
374
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
375
|
+
<div className="flex aspect-square items-center justify-center">
|
|
376
|
+
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</CarouselItem>
|
|
380
|
+
))}
|
|
381
|
+
</CarouselContent>
|
|
382
|
+
<CarouselPrevious />
|
|
383
|
+
<CarouselNext />
|
|
384
|
+
</Carousel>
|
|
385
|
+
),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'Chart',
|
|
389
|
+
category: 'data',
|
|
390
|
+
description: 'Data visualization charts powered by Recharts',
|
|
391
|
+
importPath: `import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from '@djangocfg/ui';`,
|
|
392
|
+
example: `import { Bar, BarChart, XAxis, YAxis } from 'recharts';
|
|
393
|
+
|
|
394
|
+
const chartConfig = {
|
|
395
|
+
sales: { label: "Sales", color: "hsl(var(--chart-1))" },
|
|
396
|
+
profit: { label: "Profit", color: "hsl(var(--chart-2))" },
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const chartData = [
|
|
400
|
+
{ month: "Jan", sales: 400, profit: 240 },
|
|
401
|
+
{ month: "Feb", sales: 300, profit: 180 },
|
|
402
|
+
{ month: "Mar", sales: 500, profit: 300 },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
406
|
+
<BarChart data={chartData}>
|
|
407
|
+
<XAxis dataKey="month" />
|
|
408
|
+
<YAxis />
|
|
409
|
+
<ChartTooltip content={<ChartTooltipContent />} />
|
|
410
|
+
<ChartLegend content={<ChartLegendContent />} />
|
|
411
|
+
<Bar dataKey="sales" fill="var(--color-sales)" />
|
|
412
|
+
<Bar dataKey="profit" fill="var(--color-profit)" />
|
|
413
|
+
</BarChart>
|
|
414
|
+
</ChartContainer>`,
|
|
415
|
+
preview: (
|
|
416
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
417
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
418
|
+
Chart components built on Recharts with:
|
|
419
|
+
</p>
|
|
420
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
421
|
+
<li>• Bar, Line, Area, Pie charts</li>
|
|
422
|
+
<li>• Responsive container</li>
|
|
423
|
+
<li>• Theme-aware colors</li>
|
|
424
|
+
<li>• Tooltips and legends</li>
|
|
425
|
+
<li>• Customizable styling</li>
|
|
426
|
+
</ul>
|
|
427
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
428
|
+
See <strong>Recharts documentation</strong> for all chart types
|
|
429
|
+
</p>
|
|
430
|
+
</div>
|
|
431
|
+
),
|
|
432
|
+
},
|
|
308
433
|
];
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AvatarImage,
|
|
15
15
|
Button,
|
|
16
16
|
useToast,
|
|
17
|
+
Toaster,
|
|
17
18
|
} from '@djangocfg/ui';
|
|
18
19
|
import type { ComponentConfig } from './types';
|
|
19
20
|
|
|
@@ -243,4 +244,47 @@ export const FEEDBACK_COMPONENTS: ComponentConfig[] = [
|
|
|
243
244
|
</div>
|
|
244
245
|
),
|
|
245
246
|
},
|
|
247
|
+
{
|
|
248
|
+
name: 'Toaster',
|
|
249
|
+
category: 'feedback',
|
|
250
|
+
description: 'Global toast notification container (works with Toast component)',
|
|
251
|
+
importPath: `import { Toaster, useToast } from '@djangocfg/ui';`,
|
|
252
|
+
example: `// Add Toaster once in your app layout
|
|
253
|
+
<Toaster />
|
|
254
|
+
|
|
255
|
+
// Then use the useToast hook anywhere
|
|
256
|
+
function MyComponent() {
|
|
257
|
+
const { toast } = useToast();
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<Button
|
|
261
|
+
onClick={() => {
|
|
262
|
+
toast({
|
|
263
|
+
title: "Scheduled: Catch up",
|
|
264
|
+
description: "Friday, February 10, 2023 at 5:57 PM",
|
|
265
|
+
});
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
Show Toast
|
|
269
|
+
</Button>
|
|
270
|
+
);
|
|
271
|
+
}`,
|
|
272
|
+
preview: (
|
|
273
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
274
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
275
|
+
Toaster is the global container for Toast notifications:
|
|
276
|
+
</p>
|
|
277
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
278
|
+
<li>• Add once to your app layout</li>
|
|
279
|
+
<li>• Use with useToast hook</li>
|
|
280
|
+
<li>• Manages toast queue and positioning</li>
|
|
281
|
+
<li>• Accessible and keyboard navigable</li>
|
|
282
|
+
<li>• Works with Toast component</li>
|
|
283
|
+
</ul>
|
|
284
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
285
|
+
ℹ️ Different from Sonner - this is the built-in Toast system
|
|
286
|
+
</p>
|
|
287
|
+
</div>
|
|
288
|
+
),
|
|
289
|
+
},
|
|
246
290
|
];
|
|
@@ -18,10 +18,39 @@ import {
|
|
|
18
18
|
Textarea,
|
|
19
19
|
Switch,
|
|
20
20
|
Slider,
|
|
21
|
+
Combobox,
|
|
22
|
+
InputOTP,
|
|
23
|
+
InputOTPGroup,
|
|
24
|
+
InputOTPSlot,
|
|
25
|
+
PhoneInput,
|
|
26
|
+
Form,
|
|
27
|
+
FormControl,
|
|
28
|
+
FormDescription,
|
|
29
|
+
FormField,
|
|
30
|
+
FormItem,
|
|
31
|
+
FormLabel,
|
|
32
|
+
FormMessage,
|
|
33
|
+
Field,
|
|
21
34
|
} from '@djangocfg/ui';
|
|
22
35
|
import type { ComponentConfig } from './types';
|
|
23
36
|
|
|
24
37
|
export const FORM_COMPONENTS: ComponentConfig[] = [
|
|
38
|
+
{
|
|
39
|
+
name: 'Label',
|
|
40
|
+
category: 'forms',
|
|
41
|
+
description: 'Accessible label component for form inputs',
|
|
42
|
+
importPath: "import { Label } from '@djangocfg/ui';",
|
|
43
|
+
example: `<div className="space-y-2">
|
|
44
|
+
<Label htmlFor="email">Email address</Label>
|
|
45
|
+
<Input id="email" type="email" placeholder="Enter your email" />
|
|
46
|
+
</div>`,
|
|
47
|
+
preview: (
|
|
48
|
+
<div className="space-y-2 max-w-sm">
|
|
49
|
+
<Label htmlFor="demo-email">Email address</Label>
|
|
50
|
+
<Input id="demo-email" type="email" placeholder="Enter your email" />
|
|
51
|
+
</div>
|
|
52
|
+
),
|
|
53
|
+
},
|
|
25
54
|
{
|
|
26
55
|
name: 'Button',
|
|
27
56
|
category: 'forms',
|
|
@@ -168,4 +197,169 @@ export const FORM_COMPONENTS: ComponentConfig[] = [
|
|
|
168
197
|
<Slider defaultValue={[50]} max={100} step={1} className="w-[200px]" />
|
|
169
198
|
),
|
|
170
199
|
},
|
|
200
|
+
{
|
|
201
|
+
name: 'Combobox',
|
|
202
|
+
category: 'forms',
|
|
203
|
+
description: 'Searchable dropdown with autocomplete',
|
|
204
|
+
importPath: "import { Combobox } from '@djangocfg/ui';",
|
|
205
|
+
example: `<Combobox
|
|
206
|
+
options={[
|
|
207
|
+
{ value: "javascript", label: "JavaScript" },
|
|
208
|
+
{ value: "typescript", label: "TypeScript" },
|
|
209
|
+
{ value: "python", label: "Python" },
|
|
210
|
+
{ value: "rust", label: "Rust" },
|
|
211
|
+
]}
|
|
212
|
+
placeholder="Select language..."
|
|
213
|
+
searchPlaceholder="Search language..."
|
|
214
|
+
emptyText="No language found."
|
|
215
|
+
/>`,
|
|
216
|
+
preview: (
|
|
217
|
+
<Combobox
|
|
218
|
+
options={[
|
|
219
|
+
{ value: "javascript", label: "JavaScript" },
|
|
220
|
+
{ value: "typescript", label: "TypeScript" },
|
|
221
|
+
{ value: "python", label: "Python" },
|
|
222
|
+
{ value: "rust", label: "Rust" },
|
|
223
|
+
]}
|
|
224
|
+
placeholder="Select language..."
|
|
225
|
+
searchPlaceholder="Search language..."
|
|
226
|
+
emptyText="No language found."
|
|
227
|
+
className="w-[200px]"
|
|
228
|
+
/>
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'InputOTP',
|
|
233
|
+
category: 'forms',
|
|
234
|
+
description: 'One-time password input component',
|
|
235
|
+
importPath: "import { InputOTP, InputOTPGroup, InputOTPSlot } from '@djangocfg/ui';",
|
|
236
|
+
example: `<InputOTP maxLength={6}>
|
|
237
|
+
<InputOTPGroup>
|
|
238
|
+
<InputOTPSlot index={0} />
|
|
239
|
+
<InputOTPSlot index={1} />
|
|
240
|
+
<InputOTPSlot index={2} />
|
|
241
|
+
<InputOTPSlot index={3} />
|
|
242
|
+
<InputOTPSlot index={4} />
|
|
243
|
+
<InputOTPSlot index={5} />
|
|
244
|
+
</InputOTPGroup>
|
|
245
|
+
</InputOTP>`,
|
|
246
|
+
preview: (
|
|
247
|
+
<InputOTP maxLength={6}>
|
|
248
|
+
<InputOTPGroup>
|
|
249
|
+
<InputOTPSlot index={0} />
|
|
250
|
+
<InputOTPSlot index={1} />
|
|
251
|
+
<InputOTPSlot index={2} />
|
|
252
|
+
<InputOTPSlot index={3} />
|
|
253
|
+
<InputOTPSlot index={4} />
|
|
254
|
+
<InputOTPSlot index={5} />
|
|
255
|
+
</InputOTPGroup>
|
|
256
|
+
</InputOTP>
|
|
257
|
+
),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'PhoneInput',
|
|
261
|
+
category: 'forms',
|
|
262
|
+
description: 'International phone number input with country selector',
|
|
263
|
+
importPath: "import { PhoneInput } from '@djangocfg/ui';",
|
|
264
|
+
example: `<PhoneInput
|
|
265
|
+
defaultCountry="US"
|
|
266
|
+
placeholder="Enter phone number"
|
|
267
|
+
className="max-w-sm"
|
|
268
|
+
/>`,
|
|
269
|
+
preview: (
|
|
270
|
+
<PhoneInput
|
|
271
|
+
defaultCountry="US"
|
|
272
|
+
placeholder="Enter phone number"
|
|
273
|
+
className="max-w-sm"
|
|
274
|
+
/>
|
|
275
|
+
),
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'Form',
|
|
279
|
+
category: 'forms',
|
|
280
|
+
description: 'React Hook Form wrapper with form validation',
|
|
281
|
+
importPath: "import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@djangocfg/ui';",
|
|
282
|
+
example: `// Requires react-hook-form
|
|
283
|
+
import { useForm } from 'react-hook-form';
|
|
284
|
+
|
|
285
|
+
function MyForm() {
|
|
286
|
+
const form = useForm();
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<Form {...form}>
|
|
290
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
291
|
+
<FormField
|
|
292
|
+
control={form.control}
|
|
293
|
+
name="username"
|
|
294
|
+
render={({ field }) => (
|
|
295
|
+
<FormItem>
|
|
296
|
+
<FormLabel>Username</FormLabel>
|
|
297
|
+
<FormControl>
|
|
298
|
+
<Input placeholder="Enter username" {...field} />
|
|
299
|
+
</FormControl>
|
|
300
|
+
<FormDescription>
|
|
301
|
+
Your public display name
|
|
302
|
+
</FormDescription>
|
|
303
|
+
<FormMessage />
|
|
304
|
+
</FormItem>
|
|
305
|
+
)}
|
|
306
|
+
/>
|
|
307
|
+
</form>
|
|
308
|
+
</Form>
|
|
309
|
+
);
|
|
310
|
+
}`,
|
|
311
|
+
preview: (
|
|
312
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
313
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
314
|
+
The Form component is a wrapper around React Hook Form with:
|
|
315
|
+
</p>
|
|
316
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
317
|
+
<li>• Integrated form validation</li>
|
|
318
|
+
<li>• Accessible form fields</li>
|
|
319
|
+
<li>• Error message handling</li>
|
|
320
|
+
<li>• Field descriptions and labels</li>
|
|
321
|
+
<li>• Type-safe with TypeScript</li>
|
|
322
|
+
</ul>
|
|
323
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
324
|
+
See the <strong>react-hook-form</strong> documentation for usage examples
|
|
325
|
+
</p>
|
|
326
|
+
</div>
|
|
327
|
+
),
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: 'Field',
|
|
331
|
+
category: 'forms',
|
|
332
|
+
description: 'Advanced field component with label, description and validation',
|
|
333
|
+
importPath: "import { Field, FieldGroup, FieldSet, FieldLegend } from '@djangocfg/ui';",
|
|
334
|
+
example: `<FieldSet>
|
|
335
|
+
<FieldLegend>Account Information</FieldLegend>
|
|
336
|
+
<FieldGroup>
|
|
337
|
+
<Field>
|
|
338
|
+
<FieldLabel>Username</FieldLabel>
|
|
339
|
+
<Input placeholder="Enter username" />
|
|
340
|
+
<FieldDescription>
|
|
341
|
+
Your unique username for the platform
|
|
342
|
+
</FieldDescription>
|
|
343
|
+
<FieldError>Username is required</FieldError>
|
|
344
|
+
</Field>
|
|
345
|
+
</FieldGroup>
|
|
346
|
+
</FieldSet>`,
|
|
347
|
+
preview: (
|
|
348
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
349
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
350
|
+
Field component provides structured form fields with:
|
|
351
|
+
</p>
|
|
352
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
353
|
+
<li>• Label and description support</li>
|
|
354
|
+
<li>• Error message handling</li>
|
|
355
|
+
<li>• Field grouping (FieldSet, FieldGroup)</li>
|
|
356
|
+
<li>• Accessible by default</li>
|
|
357
|
+
<li>• Consistent styling</li>
|
|
358
|
+
</ul>
|
|
359
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
360
|
+
Perfect for complex forms with validation
|
|
361
|
+
</p>
|
|
362
|
+
</div>
|
|
363
|
+
),
|
|
364
|
+
},
|
|
171
365
|
];
|
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
Skeleton,
|
|
16
16
|
AspectRatio,
|
|
17
17
|
Sticky,
|
|
18
|
+
ScrollArea,
|
|
19
|
+
ScrollBar,
|
|
20
|
+
ResizableHandle,
|
|
21
|
+
ResizablePanel,
|
|
22
|
+
ResizablePanelGroup,
|
|
23
|
+
Section,
|
|
24
|
+
SectionHeader,
|
|
18
25
|
} from '@djangocfg/ui';
|
|
19
26
|
import type { ComponentConfig } from './types';
|
|
20
27
|
|
|
@@ -130,4 +137,92 @@ export const LAYOUT_COMPONENTS: ComponentConfig[] = [
|
|
|
130
137
|
</div>
|
|
131
138
|
),
|
|
132
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: 'ScrollArea',
|
|
142
|
+
category: 'layout',
|
|
143
|
+
description: 'Custom scrollable area with styled scrollbar',
|
|
144
|
+
importPath: "import { ScrollArea, ScrollBar } from '@djangocfg/ui';",
|
|
145
|
+
example: `<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
|
|
146
|
+
<div className="space-y-4">
|
|
147
|
+
{Array.from({ length: 20 }).map((_, i) => (
|
|
148
|
+
<div key={i} className="text-sm">
|
|
149
|
+
Content item {i + 1}
|
|
150
|
+
</div>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
<ScrollBar orientation="vertical" />
|
|
154
|
+
</ScrollArea>`,
|
|
155
|
+
preview: (
|
|
156
|
+
<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
|
|
157
|
+
<div className="space-y-4">
|
|
158
|
+
{Array.from({ length: 20 }).map((_, i) => (
|
|
159
|
+
<div key={i} className="text-sm">
|
|
160
|
+
Content item {i + 1} - Scroll to see more content
|
|
161
|
+
</div>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
<ScrollBar orientation="vertical" />
|
|
165
|
+
</ScrollArea>
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'Resizable',
|
|
170
|
+
category: 'layout',
|
|
171
|
+
description: 'Resizable panel layout with draggable handles',
|
|
172
|
+
importPath: "import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@djangocfg/ui';",
|
|
173
|
+
example: `<ResizablePanelGroup direction="horizontal" className="max-w-md rounded-lg border">
|
|
174
|
+
<ResizablePanel defaultSize={50}>
|
|
175
|
+
<div className="flex h-[200px] items-center justify-center p-6">
|
|
176
|
+
<span className="font-semibold">Panel One</span>
|
|
177
|
+
</div>
|
|
178
|
+
</ResizablePanel>
|
|
179
|
+
<ResizableHandle />
|
|
180
|
+
<ResizablePanel defaultSize={50}>
|
|
181
|
+
<div className="flex h-[200px] items-center justify-center p-6">
|
|
182
|
+
<span className="font-semibold">Panel Two</span>
|
|
183
|
+
</div>
|
|
184
|
+
</ResizablePanel>
|
|
185
|
+
</ResizablePanelGroup>`,
|
|
186
|
+
preview: (
|
|
187
|
+
<ResizablePanelGroup direction="horizontal" className="max-w-md rounded-lg border">
|
|
188
|
+
<ResizablePanel defaultSize={50}>
|
|
189
|
+
<div className="flex h-[200px] items-center justify-center p-6">
|
|
190
|
+
<span className="font-semibold">Panel One</span>
|
|
191
|
+
</div>
|
|
192
|
+
</ResizablePanel>
|
|
193
|
+
<ResizableHandle />
|
|
194
|
+
<ResizablePanel defaultSize={50}>
|
|
195
|
+
<div className="flex h-[200px] items-center justify-center p-6">
|
|
196
|
+
<span className="font-semibold">Panel Two</span>
|
|
197
|
+
</div>
|
|
198
|
+
</ResizablePanel>
|
|
199
|
+
</ResizablePanelGroup>
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Section',
|
|
204
|
+
category: 'layout',
|
|
205
|
+
description: 'Semantic section container with header',
|
|
206
|
+
importPath: "import { Section, SectionHeader } from '@djangocfg/ui';",
|
|
207
|
+
example: `<Section>
|
|
208
|
+
<SectionHeader
|
|
209
|
+
title="Section Title"
|
|
210
|
+
description="Section description goes here"
|
|
211
|
+
/>
|
|
212
|
+
<div className="p-4">
|
|
213
|
+
Section content goes here...
|
|
214
|
+
</div>
|
|
215
|
+
</Section>`,
|
|
216
|
+
preview: (
|
|
217
|
+
<Section>
|
|
218
|
+
<SectionHeader
|
|
219
|
+
title="Section Title"
|
|
220
|
+
subtitle="Section description goes here"
|
|
221
|
+
/>
|
|
222
|
+
<div className="p-4 border-t">
|
|
223
|
+
<p className="text-sm">Section content goes here...</p>
|
|
224
|
+
</div>
|
|
225
|
+
</Section>
|
|
226
|
+
),
|
|
227
|
+
},
|
|
133
228
|
];
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
BreadcrumbList,
|
|
17
17
|
BreadcrumbPage,
|
|
18
18
|
BreadcrumbSeparator,
|
|
19
|
+
BreadcrumbNavigation,
|
|
19
20
|
Tabs,
|
|
20
21
|
TabsContent,
|
|
21
22
|
TabsList,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
PaginationLink,
|
|
28
29
|
PaginationNext,
|
|
29
30
|
PaginationPrevious,
|
|
31
|
+
SSRPagination,
|
|
30
32
|
} from '@djangocfg/ui';
|
|
31
33
|
import type { ComponentConfig } from './types';
|
|
32
34
|
|
|
@@ -241,4 +243,52 @@ export const NAVIGATION_COMPONENTS: ComponentConfig[] = [
|
|
|
241
243
|
</Pagination>
|
|
242
244
|
),
|
|
243
245
|
},
|
|
246
|
+
{
|
|
247
|
+
name: 'BreadcrumbNavigation',
|
|
248
|
+
category: 'navigation',
|
|
249
|
+
description: 'Enhanced breadcrumb component with automatic path generation',
|
|
250
|
+
importPath: `import { BreadcrumbNavigation } from '@djangocfg/ui';`,
|
|
251
|
+
example: `<BreadcrumbNavigation
|
|
252
|
+
items={[
|
|
253
|
+
{ label: "Home", href: "/" },
|
|
254
|
+
{ label: "Products", href: "/products" },
|
|
255
|
+
{ label: "Category", href: "/products/category" },
|
|
256
|
+
{ label: "Item", href: "/products/category/item" },
|
|
257
|
+
]}
|
|
258
|
+
/>`,
|
|
259
|
+
preview: (
|
|
260
|
+
<BreadcrumbNavigation
|
|
261
|
+
items={[
|
|
262
|
+
{ label: "Home", href: "/" },
|
|
263
|
+
{ label: "Products", href: "/products" },
|
|
264
|
+
{ label: "Category", href: "/products/category" },
|
|
265
|
+
{ label: "Item", href: "/products/category/item" },
|
|
266
|
+
]}
|
|
267
|
+
/>
|
|
268
|
+
),
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'SSRPagination',
|
|
272
|
+
category: 'navigation',
|
|
273
|
+
description: 'Server-side rendered pagination component',
|
|
274
|
+
importPath: `import { SSRPagination } from '@djangocfg/ui';`,
|
|
275
|
+
example: `<SSRPagination
|
|
276
|
+
currentPage={2}
|
|
277
|
+
totalPages={10}
|
|
278
|
+
totalItems={100}
|
|
279
|
+
itemsPerPage={10}
|
|
280
|
+
hasNextPage={true}
|
|
281
|
+
hasPreviousPage={true}
|
|
282
|
+
/>`,
|
|
283
|
+
preview: (
|
|
284
|
+
<SSRPagination
|
|
285
|
+
currentPage={2}
|
|
286
|
+
totalPages={10}
|
|
287
|
+
totalItems={100}
|
|
288
|
+
itemsPerPage={10}
|
|
289
|
+
hasNextPage={true}
|
|
290
|
+
hasPreviousPage={true}
|
|
291
|
+
/>
|
|
292
|
+
),
|
|
293
|
+
},
|
|
244
294
|
];
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
ButtonGroup,
|
|
9
|
+
Empty,
|
|
10
|
+
EmptyHeader,
|
|
11
|
+
EmptyTitle,
|
|
12
|
+
EmptyDescription,
|
|
13
|
+
EmptyContent,
|
|
14
|
+
EmptyMedia,
|
|
15
|
+
Spinner,
|
|
16
|
+
Kbd,
|
|
17
|
+
TokenIcon,
|
|
18
|
+
Toaster,
|
|
19
|
+
InputGroup,
|
|
20
|
+
Item,
|
|
21
|
+
} from '@djangocfg/ui';
|
|
6
22
|
import type { ComponentConfig } from './types';
|
|
7
23
|
|
|
8
24
|
export const SPECIALIZED_COMPONENTS: ComponentConfig[] = [
|
|
@@ -122,4 +138,238 @@ export const SPECIALIZED_COMPONENTS: ComponentConfig[] = [
|
|
|
122
138
|
</div>
|
|
123
139
|
),
|
|
124
140
|
},
|
|
141
|
+
{
|
|
142
|
+
name: 'ButtonGroup',
|
|
143
|
+
category: 'specialized',
|
|
144
|
+
description: 'Group buttons together with shared borders',
|
|
145
|
+
importPath: `import { ButtonGroup, Button } from '@djangocfg/ui';`,
|
|
146
|
+
example: `<ButtonGroup orientation="horizontal">
|
|
147
|
+
<Button variant="outline">Left</Button>
|
|
148
|
+
<Button variant="outline">Center</Button>
|
|
149
|
+
<Button variant="outline">Right</Button>
|
|
150
|
+
</ButtonGroup>`,
|
|
151
|
+
preview: (
|
|
152
|
+
<div className="space-y-4">
|
|
153
|
+
<ButtonGroup orientation="horizontal">
|
|
154
|
+
<Button variant="outline">Left</Button>
|
|
155
|
+
<Button variant="outline">Center</Button>
|
|
156
|
+
<Button variant="outline">Right</Button>
|
|
157
|
+
</ButtonGroup>
|
|
158
|
+
<ButtonGroup orientation="vertical">
|
|
159
|
+
<Button variant="outline">Top</Button>
|
|
160
|
+
<Button variant="outline">Middle</Button>
|
|
161
|
+
<Button variant="outline">Bottom</Button>
|
|
162
|
+
</ButtonGroup>
|
|
163
|
+
</div>
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'Empty',
|
|
168
|
+
category: 'specialized',
|
|
169
|
+
description: 'Empty state component for no data scenarios',
|
|
170
|
+
importPath: `import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from '@djangocfg/ui';`,
|
|
171
|
+
example: `<Empty>
|
|
172
|
+
<EmptyHeader>
|
|
173
|
+
<EmptyMedia>
|
|
174
|
+
<svg>...</svg>
|
|
175
|
+
</EmptyMedia>
|
|
176
|
+
<EmptyTitle>No results found</EmptyTitle>
|
|
177
|
+
<EmptyDescription>
|
|
178
|
+
Try adjusting your search or filter to find what you're looking for.
|
|
179
|
+
</EmptyDescription>
|
|
180
|
+
</EmptyHeader>
|
|
181
|
+
<EmptyContent>
|
|
182
|
+
<Button>Clear filters</Button>
|
|
183
|
+
</EmptyContent>
|
|
184
|
+
</Empty>`,
|
|
185
|
+
preview: (
|
|
186
|
+
<Empty className="border">
|
|
187
|
+
<EmptyHeader>
|
|
188
|
+
<EmptyMedia variant="icon">
|
|
189
|
+
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
190
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
|
191
|
+
</svg>
|
|
192
|
+
</EmptyMedia>
|
|
193
|
+
<EmptyTitle>No results found</EmptyTitle>
|
|
194
|
+
<EmptyDescription>
|
|
195
|
+
Try adjusting your search or filter to find what you're looking for.
|
|
196
|
+
</EmptyDescription>
|
|
197
|
+
</EmptyHeader>
|
|
198
|
+
<EmptyContent>
|
|
199
|
+
<Button>Clear filters</Button>
|
|
200
|
+
</EmptyContent>
|
|
201
|
+
</Empty>
|
|
202
|
+
),
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'Spinner',
|
|
206
|
+
category: 'specialized',
|
|
207
|
+
description: 'Loading spinner indicator',
|
|
208
|
+
importPath: `import { Spinner } from '@djangocfg/ui';`,
|
|
209
|
+
example: `<div className="flex gap-4 items-center">
|
|
210
|
+
<Spinner />
|
|
211
|
+
<Spinner className="size-6" />
|
|
212
|
+
<Spinner className="size-8" />
|
|
213
|
+
</div>`,
|
|
214
|
+
preview: (
|
|
215
|
+
<div className="flex gap-4 items-center">
|
|
216
|
+
<Spinner />
|
|
217
|
+
<Spinner className="size-6" />
|
|
218
|
+
<Spinner className="size-8" />
|
|
219
|
+
</div>
|
|
220
|
+
),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'Kbd',
|
|
224
|
+
category: 'specialized',
|
|
225
|
+
description: 'Keyboard key display component',
|
|
226
|
+
importPath: `import { Kbd } from '@djangocfg/ui';`,
|
|
227
|
+
example: `<div className="flex gap-2">
|
|
228
|
+
<Kbd>⌘</Kbd>
|
|
229
|
+
<Kbd>K</Kbd>
|
|
230
|
+
</div>`,
|
|
231
|
+
preview: (
|
|
232
|
+
<div className="space-y-4">
|
|
233
|
+
<div className="flex gap-2 items-center">
|
|
234
|
+
<span className="text-sm">Press</span>
|
|
235
|
+
<Kbd>⌘</Kbd>
|
|
236
|
+
<Kbd>K</Kbd>
|
|
237
|
+
<span className="text-sm">to open</span>
|
|
238
|
+
</div>
|
|
239
|
+
<div className="flex gap-2 items-center">
|
|
240
|
+
<span className="text-sm">Press</span>
|
|
241
|
+
<Kbd>Ctrl</Kbd>
|
|
242
|
+
<span className="text-sm">+</span>
|
|
243
|
+
<Kbd>C</Kbd>
|
|
244
|
+
<span className="text-sm">to copy</span>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'TokenIcon',
|
|
251
|
+
category: 'specialized',
|
|
252
|
+
description: 'Cryptocurrency token icon component',
|
|
253
|
+
importPath: `import { TokenIcon } from '@djangocfg/ui';`,
|
|
254
|
+
example: `<div className="flex gap-4">
|
|
255
|
+
<TokenIcon symbol="btc" size={32} />
|
|
256
|
+
<TokenIcon symbol="eth" size={32} />
|
|
257
|
+
<TokenIcon symbol="usdt" size={32} />
|
|
258
|
+
</div>`,
|
|
259
|
+
preview: (
|
|
260
|
+
<div className="flex gap-4 items-center">
|
|
261
|
+
<TokenIcon symbol="btc" size={32} />
|
|
262
|
+
<TokenIcon symbol="eth" size={32} />
|
|
263
|
+
<TokenIcon symbol="usdt" size={32} />
|
|
264
|
+
<TokenIcon symbol="bnb" size={32} />
|
|
265
|
+
</div>
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'Sonner (Toaster)',
|
|
270
|
+
category: 'specialized',
|
|
271
|
+
description: 'Toast notifications powered by Sonner library',
|
|
272
|
+
importPath: `import { Toaster } from '@djangocfg/ui';
|
|
273
|
+
import { toast } from 'sonner';`,
|
|
274
|
+
example: `// Add Toaster to your app layout
|
|
275
|
+
<Toaster />
|
|
276
|
+
|
|
277
|
+
// Then use toast anywhere in your app
|
|
278
|
+
toast.success('Operation completed!');
|
|
279
|
+
toast.error('Something went wrong');
|
|
280
|
+
toast.info('New message received');
|
|
281
|
+
toast.promise(
|
|
282
|
+
fetchData(),
|
|
283
|
+
{
|
|
284
|
+
loading: 'Loading...',
|
|
285
|
+
success: 'Data loaded!',
|
|
286
|
+
error: 'Failed to load',
|
|
287
|
+
}
|
|
288
|
+
);`,
|
|
289
|
+
preview: (
|
|
290
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
291
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
292
|
+
Sonner is a powerful toast notification system with:
|
|
293
|
+
</p>
|
|
294
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
295
|
+
<li>• Promise-based toasts</li>
|
|
296
|
+
<li>• Multiple variants (success, error, info, warning)</li>
|
|
297
|
+
<li>• Custom duration and position</li>
|
|
298
|
+
<li>• Action buttons support</li>
|
|
299
|
+
<li>• Dark mode support</li>
|
|
300
|
+
</ul>
|
|
301
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
302
|
+
See <strong>sonner</strong> documentation for API details
|
|
303
|
+
</p>
|
|
304
|
+
</div>
|
|
305
|
+
),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'InputGroup',
|
|
309
|
+
category: 'specialized',
|
|
310
|
+
description: 'Enhanced input with prefix/suffix addons',
|
|
311
|
+
importPath: `import { InputGroup, Input } from '@djangocfg/ui';`,
|
|
312
|
+
example: `<InputGroup>
|
|
313
|
+
<InputGroupAddon align="inline-start">
|
|
314
|
+
<SearchIcon className="size-4" />
|
|
315
|
+
</InputGroupAddon>
|
|
316
|
+
<Input placeholder="Search..." />
|
|
317
|
+
<InputGroupAddon align="inline-end">
|
|
318
|
+
<Kbd>⌘K</Kbd>
|
|
319
|
+
</InputGroupAddon>
|
|
320
|
+
</InputGroup>`,
|
|
321
|
+
preview: (
|
|
322
|
+
<div className="p-6 border rounded-md bg-muted/50 space-y-4">
|
|
323
|
+
<p className="text-sm text-muted-foreground">
|
|
324
|
+
InputGroup allows you to add prefixes and suffixes to inputs:
|
|
325
|
+
</p>
|
|
326
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
327
|
+
<li>• Icons and text addons</li>
|
|
328
|
+
<li>• Button addons</li>
|
|
329
|
+
<li>• Keyboard shortcuts display</li>
|
|
330
|
+
<li>• Currency and units</li>
|
|
331
|
+
<li>• Inline and block alignment</li>
|
|
332
|
+
</ul>
|
|
333
|
+
<InputGroup className="max-w-sm">
|
|
334
|
+
<div className="px-3 text-sm text-muted-foreground">$</div>
|
|
335
|
+
<input type="text" placeholder="0.00" className="flex-1 bg-transparent border-0 outline-none px-2" />
|
|
336
|
+
<div className="px-3 text-sm text-muted-foreground">USD</div>
|
|
337
|
+
</InputGroup>
|
|
338
|
+
</div>
|
|
339
|
+
),
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'Item',
|
|
343
|
+
category: 'specialized',
|
|
344
|
+
description: 'List item component with variants and layouts',
|
|
345
|
+
importPath: `import { Item, ItemGroup } from '@djangocfg/ui';`,
|
|
346
|
+
example: `<ItemGroup>
|
|
347
|
+
<Item variant="outline" size="default">
|
|
348
|
+
<ItemIcon>
|
|
349
|
+
<FileIcon />
|
|
350
|
+
</ItemIcon>
|
|
351
|
+
<ItemContent>
|
|
352
|
+
<ItemTitle>Document.pdf</ItemTitle>
|
|
353
|
+
<ItemDescription>Updated 2 hours ago</ItemDescription>
|
|
354
|
+
</ItemContent>
|
|
355
|
+
<ItemAction>
|
|
356
|
+
<Button variant="ghost" size="sm">View</Button>
|
|
357
|
+
</ItemAction>
|
|
358
|
+
</Item>
|
|
359
|
+
</ItemGroup>`,
|
|
360
|
+
preview: (
|
|
361
|
+
<div className="p-6 border rounded-md bg-muted/50">
|
|
362
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
363
|
+
Item component for building lists with:
|
|
364
|
+
</p>
|
|
365
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
366
|
+
<li>• Multiple variants (default, outline, muted)</li>
|
|
367
|
+
<li>• Icon, content, and action slots</li>
|
|
368
|
+
<li>• Flexible layouts</li>
|
|
369
|
+
<li>• Separator support</li>
|
|
370
|
+
<li>• Perfect for file lists, notifications, etc.</li>
|
|
371
|
+
</ul>
|
|
372
|
+
</div>
|
|
373
|
+
),
|
|
374
|
+
},
|
|
125
375
|
];
|
package/src/utils/logger.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createConsola } from 'consola';
|
|
|
3
3
|
/**
|
|
4
4
|
* Universal logger for @djangocfg/layouts
|
|
5
5
|
* Uses consola for beautiful console logging
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Log levels:
|
|
8
8
|
* - 0: silent
|
|
9
9
|
* - 1: fatal, error
|
|
@@ -12,8 +12,10 @@ import { createConsola } from 'consola';
|
|
|
12
12
|
* - 4: debug
|
|
13
13
|
* - 5: trace, verbose
|
|
14
14
|
*/
|
|
15
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
16
|
+
|
|
15
17
|
export const logger = createConsola({
|
|
16
|
-
level:
|
|
18
|
+
level: isDevelopment ? 4 : 1, // dev: debug, production: errors only
|
|
17
19
|
}).withTag('layouts');
|
|
18
20
|
|
|
19
21
|
// ─────────────────────────────────────────────────────────────────────────
|