@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 +5 -5
- package/src/index.ts +3 -0
- package/src/layouts/AppLayout/AppLayout.tsx +46 -49
- package/src/layouts/AppLayout/index.ts +2 -1
- package/src/layouts/AppLayout/types/index.ts +1 -0
- package/src/layouts/AppLayout/types/page.ts +80 -0
- package/src/layouts/AppLayout/types/routes.ts +0 -5
- package/src/types/index.ts +2 -1
- package/src/types/pageConfig.ts +5 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.4.
|
|
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.
|
|
67
|
-
"@djangocfg/og-image": "^1.4.
|
|
68
|
-
"@djangocfg/ui": "^1.4.
|
|
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.
|
|
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
|
@@ -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?:
|
|
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
|
-
*
|
|
114
|
-
*
|
|
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
|
-
|
|
119
|
-
forceLayout,
|
|
104
|
+
component,
|
|
120
105
|
config
|
|
121
106
|
}: {
|
|
122
107
|
children: ReactNode;
|
|
123
|
-
|
|
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
|
-
//
|
|
137
|
-
|
|
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 && !
|
|
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 && !
|
|
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
|
-
|
|
213
|
-
if (
|
|
214
|
-
if (
|
|
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
|
-
* //
|
|
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={
|
|
260
|
+
* <AppLayout config={appLayoutConfig} component={Component} fontFamily={inter.style.fontFamily}>
|
|
266
261
|
* <Component {...pageProps} />
|
|
267
262
|
* </AppLayout>
|
|
268
263
|
*
|
|
269
|
-
* //
|
|
270
|
-
*
|
|
271
|
-
*
|
|
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
|
-
* //
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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
|
|
@@ -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
|
+
};
|
package/src/types/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export type { PageConfig
|
|
1
|
+
export type { PageConfig } from "./pageConfig";
|
|
2
|
+
export { determinePageConfig } from "./pageConfig";
|
package/src/types/pageConfig.ts
CHANGED
|
@@ -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:
|
|
66
|
-
pageProps: Record<string, any>,
|
|
62
|
+
Component: any,
|
|
63
|
+
pageProps: Record<string, any>,
|
|
67
64
|
defaultTitle?: string,
|
|
68
65
|
defaultDescription?: string,
|
|
69
66
|
): PageConfig => {
|