@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.2.4",
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.4",
57
- "@djangocfg/og-image": "^1.2.4",
58
- "@djangocfg/ui": "^1.2.4",
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.4",
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
- const isAuthenticated = !!userRef.current && api.isAuthenticated();
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
- isAuthenticated: !!user && api.isAuthenticated(),
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
- // Determine layout mode based on route (synchronous - works with SSR)
94
- const getLayoutMode = (): 'public' | 'private' | 'auth' => {
95
- // If forceLayout is specified, use it
96
- if (forceLayout) return forceLayout;
97
-
98
- const isAuthRoute = config.routes.detectors.isAuthRoute(router.pathname);
99
- const isPrivateRoute = config.routes.detectors.isPrivateRoute(router.pathname);
100
- // const isPublicRoute = config.routes.detectors.isPublicRoute(router.pathname);
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
- if (isAuthRoute) return 'auth';
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
- if (isPrivateRoute) {
105
- if (isAuthenticated) {
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
- {/* Don't render children if redirected from private route */}
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-24T04:54:59.911Z
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.4"
24
+ "version": "1.2.6"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.4"
28
+ "version": "1.2.6"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.4"
32
+ "version": "1.2.6"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.4"
36
+ "version": "1.2.6"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.4"
40
+ "version": "1.2.6"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.4"
44
+ "version": "1.2.6"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.4"
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
  ];
@@ -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: process.env.NODE_ENV === 'production' ? 3 : 4,
18
+ level: isDevelopment ? 4 : 1, // dev: debug, production: errors only
17
19
  }).withTag('layouts');
18
20
 
19
21
  // ─────────────────────────────────────────────────────────────────────────