@djangocfg/layouts 1.4.0 → 1.4.1

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.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Layout system and components for Unrealon applications",
5
5
  "author": {
6
6
  "name": "DjangoCFG",
@@ -63,9 +63,9 @@
63
63
  "check": "tsc --noEmit"
64
64
  },
65
65
  "peerDependencies": {
66
- "@djangocfg/api": "^1.4.0",
67
- "@djangocfg/og-image": "^1.4.0",
68
- "@djangocfg/ui": "^1.4.0",
66
+ "@djangocfg/api": "^1.4.1",
67
+ "@djangocfg/og-image": "^1.4.1",
68
+ "@djangocfg/ui": "^1.4.1",
69
69
  "@hookform/resolvers": "^5.2.0",
70
70
  "consola": "^3.4.2",
71
71
  "lucide-react": "^0.468.0",
@@ -86,7 +86,7 @@
86
86
  "vidstack": "0.6.15"
87
87
  },
88
88
  "devDependencies": {
89
- "@djangocfg/typescript-config": "^1.4.0",
89
+ "@djangocfg/typescript-config": "^1.4.1",
90
90
  "@types/node": "^24.7.2",
91
91
  "@types/react": "19.2.2",
92
92
  "@types/react-dom": "19.2.1",
package/src/index.ts CHANGED
@@ -10,6 +10,9 @@ export * from './auth';
10
10
  // Layout components
11
11
  export * from './layouts';
12
12
 
13
+ // Types
14
+ export * from './types';
15
+
13
16
  // Snippets - Reusable UI components
14
17
  export * from './snippets';
15
18
 
@@ -36,9 +36,8 @@ import { AuthLayout } from './layouts/AuthLayout';
36
36
  import { PagePreloader } from './layouts/AdminLayout/components';
37
37
  import { determineLayoutMode, getRedirectUrl } from './utils';
38
38
  import { useAuth } from '../../auth';
39
- import type { AppLayoutConfig } from './types';
39
+ import type { AppLayoutConfig, PageWithLayout, LayoutMode } from './types';
40
40
  import type { ValidationErrorConfig, CORSErrorConfig, NetworkErrorConfig } from '../../validation';
41
- import type { PageWithConfig } from '../../types/pageConfig';
42
41
  import { determinePageConfig } from '../../types/pageConfig';
43
42
  import packageJson from '../../../package.json';
44
43
 
@@ -52,31 +51,16 @@ export interface AppLayoutProps {
52
51
  children: ReactNode;
53
52
  config: AppLayoutConfig;
54
53
  /**
55
- * Next.js page component (for reading pageConfig)
54
+ * Next.js page component (for reading pageConfig and layout preferences)
55
+ * Pass Component from _app.tsx to enable smart layout detection
56
56
  * @example component={Component}
57
57
  */
58
- component?: PageWithConfig | any;
58
+ component?: PageWithLayout | any;
59
59
  /**
60
60
  * Next.js page props (for reading dynamic pageConfig from SSR)
61
61
  * @example pageProps={pageProps}
62
62
  */
63
63
  pageProps?: Record<string, any>;
64
- /**
65
- * Disable layout rendering (Navigation, Sidebar, Footer)
66
- * Only providers and SEO remain active
67
- * Useful for custom layouts like landing pages
68
- */
69
- disableLayout?: boolean;
70
- /**
71
- * Force a specific layout regardless of route
72
- * Overrides automatic layout detection
73
- *
74
- * @example forceLayout="public" - always use PublicLayout
75
- * @example forceLayout="private" - always use PrivateLayout
76
- * @example forceLayout="auth" - always use AuthLayout
77
- * @example forceLayout="admin" - Django CFG admin mode with iframe integration
78
- */
79
- forceLayout?: 'public' | 'private' | 'auth' | 'admin';
80
64
  /**
81
65
  * Font family to apply globally
82
66
  * Accepts Next.js font object or CSS font-family string
@@ -110,18 +94,18 @@ export interface AppLayoutProps {
110
94
  /**
111
95
  * Layout Router Component
112
96
  *
113
- * Determines which layout to use based on route
114
- * Uses AppContext - no props passed down!
97
+ * Smart layout detection with priority:
98
+ * 1. component.getLayout (custom layout function)
99
+ * 2. component.layoutMode ('none' | 'public' | 'private' | 'auth' | 'admin')
100
+ * 3. Automatic route-based detection
115
101
  */
116
102
  function LayoutRouter({
117
103
  children,
118
- disableLayout,
119
- forceLayout,
104
+ component,
120
105
  config
121
106
  }: {
122
107
  children: ReactNode;
123
- disableLayout?: boolean;
124
- forceLayout?: 'public' | 'private' | 'auth' | 'admin';
108
+ component?: PageWithLayout | any;
125
109
  config: AppLayoutConfig;
126
110
  }) {
127
111
  const router = useRouter();
@@ -133,8 +117,18 @@ function LayoutRouter({
133
117
  setIsMounted(true);
134
118
  }, []);
135
119
 
136
- // If layout is disabled, render children directly (providers still active!)
137
- if (disableLayout) {
120
+ // Priority 1: Check if page has custom getLayout function
121
+ const hasCustomLayout = component && typeof component.getLayout === 'function';
122
+ if (hasCustomLayout) {
123
+ // Use custom layout - render children directly (getLayout applied in _app.tsx)
124
+ return <>{children}</>;
125
+ }
126
+
127
+ // Priority 2: Check component.layoutMode
128
+ const componentLayoutMode = component?.layoutMode;
129
+
130
+ // If layoutMode is 'none', render children directly
131
+ if (componentLayoutMode === 'none') {
138
132
  return <>{children}</>;
139
133
  }
140
134
 
@@ -145,7 +139,7 @@ function LayoutRouter({
145
139
 
146
140
  // Admin routes: Always show loading during SSR and initial client render
147
141
  // This prevents hydration mismatch when isAuthenticated differs between server/client
148
- if (isAdminRoute && !forceLayout) {
142
+ if ((isAdminRoute && !componentLayoutMode) || componentLayoutMode === 'admin') {
149
143
  // In embedded mode (iframe), render AdminLayout immediately to receive postMessage
150
144
  const isEmbedded = typeof window !== 'undefined' && window !== window.parent;
151
145
 
@@ -186,7 +180,7 @@ function LayoutRouter({
186
180
 
187
181
  // Private routes: Always show loading during SSR and initial client render
188
182
  // This prevents hydration mismatch when isAuthenticated differs between server/client
189
- if (isPrivateRoute && !forceLayout) {
183
+ if ((isPrivateRoute && !componentLayoutMode) || componentLayoutMode === 'private') {
190
184
  if (!isMounted || isLoading) {
191
185
  return <PagePreloader />;
192
186
  }
@@ -207,11 +201,12 @@ function LayoutRouter({
207
201
  return <PrivateLayout>{children}</PrivateLayout>;
208
202
  }
209
203
 
210
- // Determine layout mode for non-private routes
204
+ // Determine layout mode for non-private/admin routes
211
205
  const getLayoutMode = (): 'public' | 'auth' | 'admin' => {
212
- if (forceLayout === 'auth') return 'auth';
213
- if (forceLayout === 'public') return 'public';
214
- if (forceLayout === 'admin') return 'admin';
206
+ // Priority: componentLayoutMode > auto-detect
207
+ if (componentLayoutMode === 'auth') return 'auth';
208
+ if (componentLayoutMode === 'public') return 'public';
209
+ if (componentLayoutMode === 'admin') return 'admin';
215
210
  if (isAuthRoute) return 'auth';
216
211
  return 'public';
217
212
  };
@@ -251,33 +246,35 @@ function LayoutRouter({
251
246
  /**
252
247
  * AppLayout - Main Component
253
248
  *
254
- * Single entry point for all layout logic
249
+ * Single entry point for all layout logic with smart layout detection
255
250
  * Wrap your app once in _app.tsx
256
251
  *
257
252
  * @example
258
253
  * ```tsx
259
- * // With layout (default - auto-detect)
260
- * <AppLayout config={appLayoutConfig}>
261
- * <Component {...pageProps} />
254
+ * // Smart auto-detection (recommended)
255
+ * <AppLayout config={appLayoutConfig} component={Component} pageProps={pageProps}>
256
+ * {Component.getLayout ? Component.getLayout(<Component {...pageProps} />) : <Component {...pageProps} />}
262
257
  * </AppLayout>
263
258
  *
264
259
  * // With custom font
265
- * <AppLayout config={appLayoutConfig} fontFamily={manrope.style.fontFamily}>
260
+ * <AppLayout config={appLayoutConfig} component={Component} fontFamily={inter.style.fontFamily}>
266
261
  * <Component {...pageProps} />
267
262
  * </AppLayout>
268
263
  *
269
- * // Without layout (providers still active)
270
- * <AppLayout config={appLayoutConfig} disableLayout>
271
- * <CustomLandingPage />
272
- * </AppLayout>
264
+ * // Page with custom layout (in page file)
265
+ * const Page: PageWithLayout = () => <div>Content</div>;
266
+ * Page.getLayout = (page) => <CustomLayout>{page}</CustomLayout>;
273
267
  *
274
- * // Force public layout for all pages
275
- * <AppLayout config={appLayoutConfig} forceLayout="public">
276
- * <Component {...pageProps} />
277
- * </AppLayout>
268
+ * // Page with forced layout mode (in page file)
269
+ * const DashboardPage: PageWithLayout = () => <div>Dashboard</div>;
270
+ * DashboardPage.layoutMode = 'private';
271
+ *
272
+ * // Page without layout (in page file)
273
+ * const LandingPage: PageWithLayout = () => <FullPageDesign />;
274
+ * LandingPage.layoutMode = 'none';
278
275
  * ```
279
276
  */
280
- export function AppLayout({ children, config, component, pageProps, disableLayout = false, forceLayout, fontFamily, showUpdateNotifier, validation, cors, network }: AppLayoutProps) {
277
+ export function AppLayout({ children, config, component, pageProps, fontFamily, showUpdateNotifier, validation, cors, network }: AppLayoutProps) {
281
278
  const router = useRouter();
282
279
 
283
280
  // Check if ErrorBoundary is enabled (default: true)
@@ -318,7 +315,7 @@ export function AppLayout({ children, config, component, pageProps, disableLayou
318
315
  <PageProgress />
319
316
 
320
317
  {/* Smart Layout Router */}
321
- <LayoutRouter disableLayout={disableLayout} forceLayout={forceLayout} config={config}>
318
+ <LayoutRouter component={component} config={config}>
322
319
  {children}
323
320
  </LayoutRouter>
324
321
  </AppContextProvider>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AppLayout - Unified Application Layout System
3
3
  *
4
- * Single self-sufficient component for all layout needs
4
+ * Smart layout system with automatic detection
5
5
  */
6
6
 
7
7
  // Main component
@@ -19,6 +19,7 @@ export type {
19
19
  NavigationSection,
20
20
  DashboardMenuItem,
21
21
  DashboardMenuGroup,
22
+ PageWithLayout,
22
23
  } from './types';
23
24
 
24
25
  // Context and hooks
@@ -8,3 +8,4 @@ export * from './config';
8
8
  export * from './layout';
9
9
  export * from './navigation';
10
10
  export * from './routes';
11
+ export * from './page';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Page Type Definitions
3
+ *
4
+ * Universal types for Next.js pages with layout and SEO support
5
+ */
6
+
7
+ import type { ReactElement, ReactNode } from 'react';
8
+ import type { NextPage } from 'next';
9
+ import type { PageConfig } from '../../../types/pageConfig';
10
+
11
+ /**
12
+ * Layout mode types
13
+ *
14
+ * - 'public': Public layout (landing, docs, etc.)
15
+ * - 'private': Private layout (dashboard, authenticated pages)
16
+ * - 'auth': Auth layout (login, register, OTP)
17
+ * - 'admin': Admin layout (Django CFG iframe integration)
18
+ * - 'none': No layout (fully custom page)
19
+ */
20
+ export type LayoutMode = 'public' | 'private' | 'auth' | 'admin' | 'none';
21
+
22
+ /**
23
+ * Universal Page Type - combines layout and SEO configuration
24
+ *
25
+ * Extends NextPage with:
26
+ * - Layout control (getLayout, layoutMode)
27
+ * - SEO metadata (pageConfig)
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * // Custom layout using getLayout
32
+ * const Page: PageWithLayout = () => <div>Content</div>;
33
+ * Page.getLayout = (page) => <CustomLayout>{page}</CustomLayout>;
34
+ *
35
+ * // Force specific layout mode
36
+ * const DashboardPage: PageWithLayout = () => <div>Dashboard</div>;
37
+ * DashboardPage.layoutMode = 'private';
38
+ *
39
+ * // With SEO config
40
+ * const HomePage: PageWithLayout = () => <div>Home</div>;
41
+ * HomePage.pageConfig = {
42
+ * title: 'Home Page',
43
+ * description: 'Welcome to our site',
44
+ * ogImage: { title: 'Home', subtitle: 'Welcome' }
45
+ * };
46
+ *
47
+ * // Disable all layouts
48
+ * const LandingPage: PageWithLayout = () => <FullPageDesign />;
49
+ * LandingPage.layoutMode = 'none';
50
+ * ```
51
+ */
52
+ export type PageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
53
+ /**
54
+ * Custom layout function
55
+ * When provided, AppLayout will use this instead of automatic layout detection
56
+ *
57
+ * @param page - The page component wrapped in React element
58
+ * @returns Layout-wrapped page
59
+ */
60
+ getLayout?: (page: ReactElement) => ReactNode;
61
+
62
+ /**
63
+ * Force specific layout mode
64
+ * Overrides automatic route detection
65
+ *
66
+ * - undefined: Auto-detect based on route
67
+ * - 'public': Always use PublicLayout
68
+ * - 'private': Always use PrivateLayout
69
+ * - 'auth': Always use AuthLayout
70
+ * - 'admin': Always use AdminLayout
71
+ * - 'none': No layout (page renders directly)
72
+ */
73
+ layoutMode?: LayoutMode;
74
+
75
+ /**
76
+ * Page SEO and metadata configuration
77
+ * Used for <head> tags, OpenGraph, Twitter cards, etc.
78
+ */
79
+ pageConfig?: PageConfig;
80
+ };
@@ -41,8 +41,3 @@ export interface RouteDetectors {
41
41
  /** Get page title for route */
42
42
  getPageTitle: (path: string) => string;
43
43
  }
44
-
45
- /**
46
- * Layout mode based on route
47
- */
48
- export type LayoutMode = 'public' | 'private' | 'admin' | 'auth';
@@ -1 +1,2 @@
1
- export type { PageConfig, PageWithConfig } from "./pageConfig";
1
+ export type { PageConfig } from "./pageConfig";
2
+ export { determinePageConfig } from "./pageConfig";
@@ -54,16 +54,13 @@ export interface PageConfig {
54
54
  twitter?: TwitterConfig;
55
55
  }
56
56
 
57
- // Type for a Page component that includes page configuration
58
- export type PageWithConfig<T = {}> = FC<T> & {
59
- pageConfig?: PageConfig;
60
- [key: string]: any;
61
- };
62
-
63
57
  // --- Helper Function ---
58
+ /**
59
+ * Determine final page config by merging static and dynamic configs
60
+ */
64
61
  export const determinePageConfig = (
65
- Component: PageWithConfig,
66
- pageProps: Record<string, any>, // Use a general type for pageProps
62
+ Component: any,
63
+ pageProps: Record<string, any>,
67
64
  defaultTitle?: string,
68
65
  defaultDescription?: string,
69
66
  ): PageConfig => {