@djangocfg/layouts 2.1.249 → 2.1.252

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,15 +92,17 @@ export default function RootLayout({ children }) {
92
92
  <html lang="en" suppressHydrationWarning>
93
93
  <body>
94
94
  <AppLayout
95
- project="my-app"
96
- theme={{ defaultTheme: 'system' }}
97
- auth={{ apiUrl: process.env.NEXT_PUBLIC_API_URL }}
98
-
99
- publicLayout={{ component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] }}
100
- privateLayout={{ component: PrivateLayout, enabledPath: ['/dashboard', '/profile'] }}
101
- adminLayout={{ component: AdminLayout, enabledPath: '/admin' }}
102
-
103
- noLayoutPaths={['/private/terminal', '/embed']}
95
+ layouts={{
96
+ public: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
97
+ private: { component: PrivateLayout, enabledPath: ['/dashboard', '/profile'] },
98
+ admin: { component: AdminLayout, enabledPath: '/admin' },
99
+ noLayoutPaths: ['/private/terminal', '/embed'],
100
+ }}
101
+ baseApp={{
102
+ project: 'my-app',
103
+ theme: { defaultTheme: 'system' },
104
+ auth: { apiUrl: process.env.NEXT_PUBLIC_API_URL },
105
+ }}
104
106
  >
105
107
  {children}
106
108
  </AppLayout>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.249",
3
+ "version": "2.1.252",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,14 +74,14 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.249",
78
- "@djangocfg/centrifugo": "^2.1.249",
79
- "@djangocfg/i18n": "^2.1.249",
80
- "@djangocfg/monitor": "^2.1.249",
81
- "@djangocfg/debuger": "^2.1.249",
82
- "@djangocfg/ui-core": "^2.1.249",
83
- "@djangocfg/ui-nextjs": "^2.1.249",
84
- "@djangocfg/ui-tools": "^2.1.249",
77
+ "@djangocfg/api": "^2.1.252",
78
+ "@djangocfg/centrifugo": "^2.1.252",
79
+ "@djangocfg/i18n": "^2.1.252",
80
+ "@djangocfg/monitor": "^2.1.252",
81
+ "@djangocfg/debuger": "^2.1.252",
82
+ "@djangocfg/ui-core": "^2.1.252",
83
+ "@djangocfg/ui-nextjs": "^2.1.252",
84
+ "@djangocfg/ui-tools": "^2.1.252",
85
85
  "@hookform/resolvers": "^5.2.2",
86
86
  "consola": "^3.4.2",
87
87
  "lucide-react": "^0.545.0",
@@ -109,15 +109,15 @@
109
109
  "uuid": "^11.1.0"
110
110
  },
111
111
  "devDependencies": {
112
- "@djangocfg/api": "^2.1.249",
113
- "@djangocfg/i18n": "^2.1.249",
114
- "@djangocfg/centrifugo": "^2.1.249",
115
- "@djangocfg/monitor": "^2.1.249",
116
- "@djangocfg/debuger": "^2.1.249",
117
- "@djangocfg/typescript-config": "^2.1.249",
118
- "@djangocfg/ui-core": "^2.1.249",
119
- "@djangocfg/ui-nextjs": "^2.1.249",
120
- "@djangocfg/ui-tools": "^2.1.249",
112
+ "@djangocfg/api": "^2.1.252",
113
+ "@djangocfg/i18n": "^2.1.252",
114
+ "@djangocfg/centrifugo": "^2.1.252",
115
+ "@djangocfg/monitor": "^2.1.252",
116
+ "@djangocfg/debuger": "^2.1.252",
117
+ "@djangocfg/typescript-config": "^2.1.252",
118
+ "@djangocfg/ui-core": "^2.1.252",
119
+ "@djangocfg/ui-nextjs": "^2.1.252",
120
+ "@djangocfg/ui-tools": "^2.1.252",
121
121
  "@types/node": "^24.7.2",
122
122
  "@types/react": "^19.1.0",
123
123
  "@types/react-dom": "^19.1.0",
package/src/index.ts CHANGED
@@ -6,10 +6,12 @@
6
6
  *
7
7
  * @example
8
8
  * ```tsx
9
- * import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
9
+ * import { PublicLayout, PublicNavbar, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
10
10
  *
11
11
  * // Public page
12
- * <PublicLayout logo="/logo.svg" siteName="My App" navigation={navItems}>
12
+ * <PublicLayout
13
+ * navbar={<PublicNavbar config={{ logo: "/logo.svg", siteName: "My App", navigation: navItems }} />}
14
+ * >
13
15
  * {children}
14
16
  * </PublicLayout>
15
17
  *
@@ -88,9 +88,36 @@ interface LayoutConfig {
88
88
  enabledPath?: string | string[];
89
89
  }
90
90
 
91
+ export interface AppLayoutLayoutsConfig {
92
+ public?: LayoutConfig;
93
+ private?: LayoutConfig;
94
+ admin?: LayoutConfig;
95
+ noLayoutPaths?: string | string[];
96
+ authPath?: string;
97
+ }
98
+
99
+ export interface AppLayoutBaseAppConfig {
100
+ project?: string;
101
+ theme?: ThemeConfig;
102
+ auth?: AuthConfig;
103
+ analytics?: AnalyticsConfig;
104
+ centrifugo?: CentrifugoConfig;
105
+ errorTracking?: ErrorTrackingConfig;
106
+ swr?: SWRConfigOptions;
107
+ errorBoundary?: ErrorBoundaryConfig;
108
+ pwaInstall?: PwaInstallConfig;
109
+ monitor?: MonitorConfig;
110
+ debug?: DebugConfig;
111
+ }
112
+
91
113
  export interface AppLayoutProps {
92
114
  children: ReactNode;
93
115
 
116
+ /** Compact routing/layout config */
117
+ layouts?: AppLayoutLayoutsConfig;
118
+ /** Compact providers/base-app config */
119
+ baseApp?: AppLayoutBaseAppConfig;
120
+
94
121
  /** Project name — used as default for monitor.project and debug panel title */
95
122
  project?: string;
96
123
 
@@ -152,6 +179,16 @@ export interface AppLayoutProps {
152
179
  debug?: DebugConfig;
153
180
  }
154
181
 
182
+ interface AppLayoutContentProps {
183
+ children: ReactNode;
184
+ publicLayout?: LayoutConfig;
185
+ privateLayout?: LayoutConfig;
186
+ adminLayout?: LayoutConfig;
187
+ noLayoutPaths?: string | string[];
188
+ authPath?: string;
189
+ i18n?: I18nLayoutConfig;
190
+ }
191
+
155
192
  /**
156
193
  * AppLayout Content - Renders layout with all providers
157
194
  *
@@ -166,7 +203,7 @@ function AppLayoutContent({
166
203
  noLayoutPaths,
167
204
  authPath = '/auth',
168
205
  i18n,
169
- }: AppLayoutProps) {
206
+ }: AppLayoutContentProps) {
170
207
  // Use pathname without locale prefix for route matching
171
208
  const pathname = usePathnameWithoutLocale();
172
209
 
@@ -270,20 +307,32 @@ function AppLayoutContent({
270
307
  * AppLayout - Main Component with All Providers
271
308
  */
272
309
  export function AppLayout(props: AppLayoutProps) {
310
+ const layoutsConfig = props.layouts;
311
+ const baseAppConfig = props.baseApp;
312
+
313
+ const publicLayout = layoutsConfig?.public ?? props.publicLayout;
314
+ const privateLayout = layoutsConfig?.private ?? props.privateLayout;
315
+ const adminLayout = layoutsConfig?.admin ?? props.adminLayout;
316
+ const noLayoutPaths = layoutsConfig?.noLayoutPaths ?? props.noLayoutPaths;
317
+ const authPath = layoutsConfig?.authPath ?? props.authPath;
318
+
273
319
  const {
274
- project,
275
- theme,
276
- auth,
277
- analytics,
278
- centrifugo,
279
- errorTracking,
280
- errorBoundary,
281
- swr,
282
- pwaInstall,
283
- monitor,
284
- debug,
320
+ i18n,
321
+ children,
285
322
  } = props;
286
323
 
324
+ const project = baseAppConfig?.project ?? props.project;
325
+ const theme = baseAppConfig?.theme ?? props.theme;
326
+ const auth = baseAppConfig?.auth ?? props.auth;
327
+ const analytics = baseAppConfig?.analytics ?? props.analytics;
328
+ const centrifugo = baseAppConfig?.centrifugo ?? props.centrifugo;
329
+ const errorTracking = baseAppConfig?.errorTracking ?? props.errorTracking;
330
+ const errorBoundary = baseAppConfig?.errorBoundary ?? props.errorBoundary;
331
+ const swr = baseAppConfig?.swr ?? props.swr;
332
+ const pwaInstall = baseAppConfig?.pwaInstall ?? props.pwaInstall;
333
+ const monitor = baseAppConfig?.monitor ?? props.monitor;
334
+ const debug = baseAppConfig?.debug ?? props.debug;
335
+
287
336
  return (
288
337
  <BaseApp
289
338
  project={project}
@@ -298,7 +347,15 @@ export function AppLayout(props: AppLayoutProps) {
298
347
  monitor={monitor}
299
348
  debug={debug}
300
349
  >
301
- <AppLayoutContent {...props} />
350
+ <AppLayoutContent
351
+ children={children}
352
+ publicLayout={publicLayout}
353
+ privateLayout={privateLayout}
354
+ adminLayout={adminLayout}
355
+ noLayoutPaths={noLayoutPaths}
356
+ authPath={authPath}
357
+ i18n={i18n}
358
+ />
302
359
  </BaseApp>
303
360
  );
304
361
  }
@@ -3,7 +3,13 @@
3
3
  */
4
4
 
5
5
  export { AppLayout } from './AppLayout';
6
- export type { AppLayoutProps, LayoutMode, I18nLayoutConfig } from './AppLayout';
6
+ export type {
7
+ AppLayoutProps,
8
+ AppLayoutLayoutsConfig,
9
+ AppLayoutBaseAppConfig,
10
+ LayoutMode,
11
+ I18nLayoutConfig,
12
+ } from './AppLayout';
7
13
 
8
14
  export { BaseApp } from './BaseApp';
9
15
  export type { BaseAppProps } from './BaseApp';
@@ -1,47 +1,9 @@
1
1
  /**
2
2
  * Private Layout
3
3
  *
4
- * Layout for authenticated user pages (dashboard, profile, etc.)
5
- * Import and use directly with props - no complex configs needed!
6
- *
7
- * Features:
8
- * - Responsive sidebar with mobile burger menu
9
- * - Keyboard shortcut (Ctrl/Cmd + B) to toggle sidebar
10
- * - Header with sidebar trigger and user menu
11
- * - Configurable content padding
12
- * - NO SSR - renders only on client to avoid hydration mismatch
13
- *
14
- * @example
15
- * ```tsx
16
- * import { PrivateLayout } from '@djangocfg/layouts';
17
- *
18
- * <PrivateLayout
19
- * sidebar={{
20
- * items: [
21
- * { label: 'Dashboard', href: '/dashboard', icon: 'LayoutDashboard' },
22
- * { label: 'Profile', href: '/profile', icon: 'User' }
23
- * ]
24
- * }}
25
- * header={{
26
- * title: 'Dashboard',
27
- * groups: [
28
- * {
29
- * title: 'Account',
30
- * items: [
31
- * { label: 'Profile', href: '/profile' },
32
- * { label: 'Settings', href: '/settings' }
33
- * ]
34
- * }
35
- * ],
36
- * authPath: '/auth'
37
- * }}
38
- * >
39
- * {children}
40
- * </PrivateLayout>
41
- *
42
- * Note: User data (name, email, avatar) is automatically loaded from useAuth() context
43
- * Keyboard shortcut: Ctrl/Cmd + B to toggle sidebar
44
- * ```
4
+ * Authenticated shell: collapsible sidebar (icon rail vs expanded) + scrollable content.
5
+ * Toggle lives in the sidebar header on desktop; on narrow viewports a `SidebarTrigger` sits in `PrivateContent` (opens the mobile Sheet).
6
+ * Ctrl/Cmd + B still toggles the sidebar width.
45
7
  */
46
8
 
47
9
  'use client';
@@ -50,13 +12,12 @@ import React, { ReactNode, useEffect, useState } from 'react';
50
12
  import { useRouter } from 'next/navigation';
51
13
 
52
14
  import { useAuth } from '@djangocfg/api/auth';
53
- import {
54
- Preloader
55
- } from '@djangocfg/ui-core/components';
15
+ import { Preloader } from '@djangocfg/ui-core/components';
56
16
  import { SidebarInset, SidebarProvider } from '@djangocfg/ui-nextjs/components';
57
17
 
18
+ import type { I18nLayoutConfig } from '../AppLayout/AppLayout';
58
19
  import { UserMenuConfig } from '../types';
59
- import { PrivateContent, PrivateHeader, PrivateSidebar } from './components';
20
+ import { PrivateContent, PrivateSidebar } from './components';
60
21
 
61
22
  import type { LucideIcon as LucideIconType } from 'lucide-react';
62
23
 
@@ -86,21 +47,25 @@ export interface SidebarConfig {
86
47
  }
87
48
 
88
49
  export interface HeaderConfig {
50
+ /** Shown next to the logo when the sidebar is expanded */
89
51
  title?: string;
90
- /** User menu groups */
52
+ /** User menu groups (account panel in the sidebar footer) */
91
53
  groups?: UserMenuConfig['groups'];
92
54
  /** Auth page path (for sign in button) */
93
55
  authPath?: string;
94
56
  }
95
57
 
96
- import type { I18nLayoutConfig } from '../AppLayout/AppLayout';
97
-
98
58
  export interface PrivateLayoutProps {
99
59
  children: ReactNode;
100
60
  /** Sidebar configuration */
101
61
  sidebar?: SidebarConfig;
102
- /** Header configuration */
62
+ /** Title + account links (no top navbar — title is used in the sidebar chrome) */
103
63
  header?: HeaderConfig;
64
+ /**
65
+ * Path for active nav highlighting. With `@djangocfg/nextjs` i18n routing, pass `usePathname()` from
66
+ * `@djangocfg/nextjs/i18n/navigation` (no `/[locale]` segment). If omitted, uses `next/navigation` (includes locale).
67
+ */
68
+ pathname?: string;
104
69
  /** Content padding */
105
70
  contentPadding?: 'none' | 'default';
106
71
  /** i18n configuration for locale switching */
@@ -111,6 +76,7 @@ export function PrivateLayout({
111
76
  children,
112
77
  sidebar,
113
78
  header,
79
+ pathname,
114
80
  contentPadding = 'default',
115
81
  i18n,
116
82
  }: PrivateLayoutProps) {
@@ -120,23 +86,18 @@ export function PrivateLayout({
120
86
 
121
87
  useEffect(() => {
122
88
  if (!isLoading && !isAuthenticated && !isRedirecting) {
123
- // Save current URL (including query params) before redirecting to auth
124
89
  const currentUrl = window.location.pathname + window.location.search;
125
90
  saveRedirectUrl(currentUrl);
126
-
127
- // Set redirecting state to prevent flicker
128
91
  setIsRedirecting(true);
129
92
  router.push(header?.authPath || '/auth');
130
93
  }
131
94
  }, [isAuthenticated, isLoading, isRedirecting, router, saveRedirectUrl, header?.authPath]);
132
95
 
133
- // Show loading state while auth is being checked or redirecting
134
- // Note: SSR hydration is handled by ClientOnly wrapper in AppLayout
135
96
  if (isLoading || isRedirecting || !isAuthenticated) {
136
97
  return (
137
98
  <Preloader
138
99
  variant="fullscreen"
139
- text={isRedirecting ? "Redirecting to login..." : "Authenticating..."}
100
+ text={isRedirecting ? 'Redirecting to login...' : 'Authenticating...'}
140
101
  size="lg"
141
102
  backdrop={true}
142
103
  backdropOpacity={80}
@@ -146,20 +107,15 @@ export function PrivateLayout({
146
107
 
147
108
  return (
148
109
  <SidebarProvider defaultOpen={true}>
149
- {/* Sidebar */}
150
- {sidebar && <PrivateSidebar sidebar={sidebar} />}
110
+ {sidebar && (
111
+ <PrivateSidebar sidebar={sidebar} header={header} i18n={i18n} pathname={pathname} />
112
+ )}
151
113
 
152
- {/* Main content area */}
153
114
  <SidebarInset className="flex flex-col">
154
- {/* Header with sidebar trigger */}
155
- {(header || isAuthenticated) && (
156
- <PrivateHeader header={header} i18n={i18n} />
157
- )}
158
-
159
- {/* Page content */}
160
- <PrivateContent padding={contentPadding}>{children}</PrivateContent>
115
+ <PrivateContent padding={contentPadding} hasSidebar={Boolean(sidebar)}>
116
+ {children}
117
+ </PrivateContent>
161
118
  </SidebarInset>
162
119
  </SidebarProvider>
163
120
  );
164
121
  }
165
-
@@ -1,33 +1,50 @@
1
1
  /**
2
- * Private Layout Content
3
- *
4
- * Main content wrapper for PrivateLayout
2
+ * Private layout main column — optional mobile menu strip (`SidebarTrigger`) + scrollable area.
3
+ * On viewports below `md`, the desktop sidebar is off-canvas; the trigger opens the `Sheet` from ui-nextjs sidebar.
5
4
  */
6
5
 
7
6
  'use client';
8
7
 
9
8
  import React, { ReactNode } from 'react';
10
9
 
10
+ import { SidebarTrigger } from '@djangocfg/ui-nextjs/components';
11
11
  import { cn } from '@djangocfg/ui-core/lib';
12
12
 
13
13
  interface PrivateContentProps {
14
14
  children: ReactNode;
15
15
  padding?: 'none' | 'default';
16
+ /** When false, no mobile hamburger (e.g. layout without a sidebar). Default true. */
17
+ hasSidebar?: boolean;
16
18
  }
17
19
 
18
20
  export function PrivateContent({
19
21
  children,
20
22
  padding = 'default',
23
+ hasSidebar = true,
21
24
  }: PrivateContentProps) {
22
25
  return (
23
- <main
24
- className={cn(
25
- 'flex-1 overflow-y-auto',
26
- padding === 'default' && 'p-4 sm:p-6 lg:p-8'
26
+ <div className="flex min-h-0 min-w-0 flex-1 flex-col">
27
+ {hasSidebar && (
28
+ <div
29
+ className={cn(
30
+ 'sticky top-0 z-40 flex shrink-0 items-center gap-2 border-b border-border/50 bg-background/95 py-2 pl-2 pr-3 backdrop-blur-md supports-[backdrop-filter]:bg-background/80',
31
+ 'md:hidden',
32
+ )}
33
+ >
34
+ <SidebarTrigger
35
+ className="shrink-0"
36
+ aria-label="Open menu"
37
+ />
38
+ </div>
27
39
  )}
28
- >
29
- {children}
30
- </main>
40
+ <div
41
+ className={cn(
42
+ 'min-h-0 flex-1 overflow-y-auto',
43
+ padding === 'default' && 'p-4 sm:p-6 lg:p-8',
44
+ )}
45
+ >
46
+ {children}
47
+ </div>
48
+ </div>
31
49
  );
32
50
  }
33
-