@djangocfg/layouts 2.1.249 → 2.1.251

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.251",
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.251",
78
+ "@djangocfg/centrifugo": "^2.1.251",
79
+ "@djangocfg/i18n": "^2.1.251",
80
+ "@djangocfg/monitor": "^2.1.251",
81
+ "@djangocfg/debuger": "^2.1.251",
82
+ "@djangocfg/ui-core": "^2.1.251",
83
+ "@djangocfg/ui-nextjs": "^2.1.251",
84
+ "@djangocfg/ui-tools": "^2.1.251",
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.251",
113
+ "@djangocfg/i18n": "^2.1.251",
114
+ "@djangocfg/centrifugo": "^2.1.251",
115
+ "@djangocfg/monitor": "^2.1.251",
116
+ "@djangocfg/debuger": "^2.1.251",
117
+ "@djangocfg/typescript-config": "^2.1.251",
118
+ "@djangocfg/ui-core": "^2.1.251",
119
+ "@djangocfg/ui-nextjs": "^2.1.251",
120
+ "@djangocfg/ui-tools": "^2.1.251",
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';
@@ -11,15 +11,19 @@
11
11
  *
12
12
  * @example
13
13
  * ```tsx
14
- * import { PublicLayout } from '@djangocfg/layouts';
14
+ * import { PublicLayout, PublicNavbar } from '@djangocfg/layouts';
15
15
  *
16
16
  * <PublicLayout
17
- * logo="/logo.svg"
18
- * siteName="My App"
19
- * navigation={[
20
- * { label: 'Home', href: '/' },
21
- * { label: 'Docs', href: '/docs' }
22
- * ]}
17
+ * navbar={
18
+ * <PublicNavbar
19
+ * logo="/logo.svg"
20
+ * siteName="My App"
21
+ * navigation={[
22
+ * { label: 'Home', href: '/' },
23
+ * { label: 'Docs', href: '/docs' }
24
+ * ]}
25
+ * />
26
+ * }
23
27
  * >
24
28
  * {children}
25
29
  * </PublicLayout>
@@ -31,36 +35,25 @@
31
35
  import { usePathname } from 'next/navigation';
32
36
  import { ReactNode, useEffect, useMemo, useState } from 'react';
33
37
 
34
- import { PublicMobileDrawer, PublicNavigation } from './components';
35
38
  import { PublicLayoutProvider } from './context';
36
39
 
37
- import type { NavigationItem, UserMenuConfig } from '../types';
38
- import type { I18nLayoutConfig } from '../AppLayout/AppLayout';
39
-
40
40
  export interface PublicLayoutProps {
41
41
  children: ReactNode;
42
- /** Logo path or URL */
43
- logo?: string;
44
- /** Site name */
45
- siteName?: string;
46
- /** Navigation items */
47
- navigation?: NavigationItem[];
48
- /** User menu configuration (optional, uses useAuth() for authentication state) */
49
- userMenu?: UserMenuConfig;
50
- /** i18n configuration for locale switching */
51
- i18n?: I18nLayoutConfig;
52
- /** Custom className for navbar container (e.g. "max-w-7xl mx-auto") */
53
- navbarContainerClassName?: string;
42
+
43
+ /**
44
+ * Slots (advanced).
45
+ *
46
+ * Slots-only API: this layout does not render any default navbar/drawer/footer.
47
+ * Pass `navbar`, `mobileDrawer`, `footer` explicitly (or keep them empty).
48
+ */
49
+ navbar?: ReactNode;
50
+ footer?: ReactNode;
54
51
  }
55
52
 
56
53
  export function PublicLayout({
57
54
  children,
58
- logo,
59
- siteName = 'App',
60
- navigation = [],
61
- userMenu,
62
- i18n,
63
- navbarContainerClassName,
55
+ navbar,
56
+ footer,
64
57
  }: PublicLayoutProps) {
65
58
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
66
59
  const pathname = usePathname();
@@ -71,30 +64,22 @@ export function PublicLayout({
71
64
  }, [pathname]);
72
65
 
73
66
  const contextValue = useMemo(() => ({
74
- logo,
75
- siteName,
76
- navigation,
77
- userMenu,
78
- i18n,
79
- containerClassName: navbarContainerClassName,
80
67
  mobileMenuOpen,
81
68
  toggleMobileMenu: () => setMobileMenuOpen((prev) => !prev),
82
69
  closeMobileMenu: () => setMobileMenuOpen(false),
83
- }), [logo, siteName, navigation, userMenu, i18n, navbarContainerClassName, mobileMenuOpen]);
70
+ }), [
71
+ mobileMenuOpen,
72
+ ]);
84
73
 
85
74
  return (
86
75
  <PublicLayoutProvider value={contextValue}>
87
76
  <div className="min-h-screen flex flex-col">
88
- {/* Navigation */}
89
- <PublicNavigation />
90
-
91
- {/* Mobile Drawer */}
92
- <PublicMobileDrawer />
77
+ {navbar ?? null}
93
78
 
94
79
  {/* Main Content */}
95
80
  <main className="flex-1">{children}</main>
96
81
 
97
- {/* Footer - Add your own custom footer component here if needed */}
82
+ {footer ?? null}
98
83
  </div>
99
84
  </PublicLayoutProvider>
100
85
  );
@@ -11,13 +11,24 @@ import type { FooterMenuSection } from './types';
11
11
 
12
12
  export interface FooterMenuSectionsProps {
13
13
  menuSections: FooterMenuSection[];
14
+ minColumnWidth?: number;
15
+ maxColumns?: number;
14
16
  }
15
17
 
16
- export function FooterMenuSections({ menuSections }: FooterMenuSectionsProps) {
18
+ export function FooterMenuSections({
19
+ menuSections,
20
+ minColumnWidth = 180,
21
+ maxColumns = 5,
22
+ }: FooterMenuSectionsProps) {
17
23
  if (menuSections.length === 0) return null;
18
24
 
25
+ const effectiveColumns = Math.max(1, Math.min(maxColumns, menuSections.length));
26
+
19
27
  return (
20
- <div className="w-full grid grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-x-12">
28
+ <div
29
+ className="w-full grid gap-8 lg:gap-x-12"
30
+ style={{ gridTemplateColumns: `repeat(${effectiveColumns}, minmax(${minColumnWidth}px, 1fr))` }}
31
+ >
21
32
  {menuSections.map((section) => (
22
33
  <div key={section.title} className="min-w-0">
23
34
  <h3 className="text-xs font-medium text-muted-foreground mb-3">
@@ -13,7 +13,7 @@ import type { LucideIcon } from 'lucide-react';
13
13
  import type { FooterSocialLinks } from './types';
14
14
 
15
15
  export interface FooterProjectInfoProps {
16
- siteName: string;
16
+ siteName?: string;
17
17
  description?: string;
18
18
  logo?: string;
19
19
  badge?: {
@@ -41,16 +41,18 @@ export function FooterProjectInfo({
41
41
  <div className={isMobile ? 'w-6 h-6 flex items-center justify-center' : 'w-8 h-8 flex items-center justify-center'}>
42
42
  <img
43
43
  src={logo}
44
- alt={`${siteName} Logo`}
44
+ alt={`${siteName || 'Project'} Logo`}
45
45
  className="w-full h-full object-contain"
46
46
  />
47
47
  </div>
48
48
  ) : (
49
49
  <DjangoCFGLogo size={isMobile ? 24 : 32} className="text-foreground" />
50
50
  )}
51
- <span className={isMobile ? 'text-lg font-bold text-foreground' : 'text-lg font-semibold text-foreground'}>
52
- {siteName}
53
- </span>
51
+ {siteName && (
52
+ <span className={isMobile ? 'text-lg font-bold text-foreground' : 'text-lg font-semibold text-foreground'}>
53
+ {siteName}
54
+ </span>
55
+ )}
54
56
  </div>
55
57
 
56
58
  {description && (