@djangocfg/layouts 2.1.4 → 2.1.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/README.md +75 -20
- package/package.json +5 -5
- package/src/layouts/AppLayout/AppLayout.tsx +10 -50
- package/src/layouts/AppLayout/BaseApp.tsx +103 -0
- package/src/layouts/AppLayout/index.ts +3 -0
- package/src/snippets/McpChat/components/AIChatWidget.tsx +12 -1
- package/src/snippets/McpChat/components/ChatPanel.tsx +9 -2
package/README.md
CHANGED
|
@@ -10,6 +10,72 @@ Simple, straightforward layout components for Next.js - import and use with prop
|
|
|
10
10
|
pnpm add @djangocfg/layouts
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Core Components
|
|
14
|
+
|
|
15
|
+
### BaseApp
|
|
16
|
+
|
|
17
|
+
Core providers wrapper - use when you need providers without layout routing:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { BaseApp } from '@djangocfg/layouts';
|
|
21
|
+
|
|
22
|
+
// app/layout.tsx
|
|
23
|
+
export default function RootLayout({ children }) {
|
|
24
|
+
return (
|
|
25
|
+
<html lang="en" suppressHydrationWarning>
|
|
26
|
+
<body>
|
|
27
|
+
<BaseApp theme={{ defaultTheme: 'dark' }}>
|
|
28
|
+
{children}
|
|
29
|
+
</BaseApp>
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Included providers:**
|
|
37
|
+
- ThemeProvider (light/dark/system)
|
|
38
|
+
- TooltipProvider
|
|
39
|
+
- SWRConfig
|
|
40
|
+
- AuthProvider
|
|
41
|
+
- ErrorTrackingProvider
|
|
42
|
+
- Toaster + PageProgress
|
|
43
|
+
|
|
44
|
+
### AppLayout
|
|
45
|
+
|
|
46
|
+
Smart layout router built on BaseApp - automatically selects layout based on route:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { AppLayout } from '@djangocfg/layouts';
|
|
50
|
+
import { PublicLayout } from './_layouts/PublicLayout';
|
|
51
|
+
import { PrivateLayout } from './_layouts/PrivateLayout';
|
|
52
|
+
|
|
53
|
+
// app/layout.tsx
|
|
54
|
+
export default function RootLayout({ children }) {
|
|
55
|
+
return (
|
|
56
|
+
<html lang="en" suppressHydrationWarning>
|
|
57
|
+
<body>
|
|
58
|
+
<AppLayout
|
|
59
|
+
publicLayout={{
|
|
60
|
+
component: PublicLayout,
|
|
61
|
+
enabledPath: ['/', '/legal', '/contact']
|
|
62
|
+
}}
|
|
63
|
+
privateLayout={{
|
|
64
|
+
component: PrivateLayout,
|
|
65
|
+
enabledPath: ['/dashboard', '/profile']
|
|
66
|
+
}}
|
|
67
|
+
theme={{ defaultTheme: 'system' }}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</AppLayout>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Layout priority:** Admin → Private → Public → Fallback
|
|
78
|
+
|
|
13
79
|
## Layouts
|
|
14
80
|
|
|
15
81
|
Simple, props-based layout components. No complex configs needed!
|
|
@@ -20,25 +86,25 @@ Simple, props-based layout components. No complex configs needed!
|
|
|
20
86
|
import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
|
|
21
87
|
|
|
22
88
|
// Public page
|
|
23
|
-
<PublicLayout
|
|
24
|
-
logo="/logo.svg"
|
|
25
|
-
siteName="My App"
|
|
89
|
+
<PublicLayout
|
|
90
|
+
logo="/logo.svg"
|
|
91
|
+
siteName="My App"
|
|
26
92
|
navigation={navItems}
|
|
27
93
|
>
|
|
28
94
|
{children}
|
|
29
95
|
</PublicLayout>
|
|
30
96
|
|
|
31
97
|
// Private page
|
|
32
|
-
<PrivateLayout
|
|
33
|
-
sidebar={{ items: menuItems }}
|
|
98
|
+
<PrivateLayout
|
|
99
|
+
sidebar={{ items: menuItems }}
|
|
34
100
|
header={{ title: 'Dashboard' }}
|
|
35
101
|
>
|
|
36
102
|
{children}
|
|
37
103
|
</PrivateLayout>
|
|
38
104
|
|
|
39
105
|
// Auth page
|
|
40
|
-
<AuthLayout
|
|
41
|
-
logo="/logo.svg"
|
|
106
|
+
<AuthLayout
|
|
107
|
+
logo="/logo.svg"
|
|
42
108
|
title="Sign In"
|
|
43
109
|
subtitle="Welcome back"
|
|
44
110
|
>
|
|
@@ -48,27 +114,16 @@ import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
|
|
|
48
114
|
|
|
49
115
|
| Layout | Description |
|
|
50
116
|
|--------|-------------|
|
|
117
|
+
| `BaseApp` | Core providers wrapper (Theme, Auth, SWR, ErrorTracking, Toaster) |
|
|
118
|
+
| `AppLayout` | Smart layout router with route-based layout switching |
|
|
51
119
|
| `PublicLayout` | Public pages (home, docs, contact, legal) |
|
|
52
120
|
| `PrivateLayout` | Authenticated user pages (dashboard, profile) |
|
|
53
121
|
| `AuthLayout` | Authentication pages (login, signup, password reset) |
|
|
54
122
|
| `AdminLayout` | Admin panel layout |
|
|
55
|
-
| `AppLayout` | Smart layout router with config-based setup |
|
|
56
123
|
| `ProfileLayout` | User profile pages |
|
|
57
124
|
| `SupportLayout` | Support/help pages with ticket system |
|
|
58
125
|
| `PaymentsLayout` | Payment flows and billing |
|
|
59
126
|
|
|
60
|
-
### AppLayout
|
|
61
|
-
|
|
62
|
-
Smart layout component that automatically selects the right layout based on config:
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
import { AppLayout } from '@djangocfg/layouts';
|
|
66
|
-
|
|
67
|
-
<AppLayout config={appLayoutConfig}>
|
|
68
|
-
{children}
|
|
69
|
-
</AppLayout>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
127
|
## Auth
|
|
73
128
|
|
|
74
129
|
Complete authentication system with OTP and OAuth support.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
"check": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@djangocfg/api": "^2.1.
|
|
96
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
97
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
95
|
+
"@djangocfg/api": "^2.1.6",
|
|
96
|
+
"@djangocfg/centrifugo": "^2.1.6",
|
|
97
|
+
"@djangocfg/ui-nextjs": "^2.1.6",
|
|
98
98
|
"@hookform/resolvers": "^5.2.0",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"uuid": "^11.1.0"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
117
|
+
"@djangocfg/typescript-config": "^2.1.6",
|
|
118
118
|
"@types/node": "^24.7.2",
|
|
119
119
|
"@types/react": "^19.1.0",
|
|
120
120
|
"@types/react-dom": "^19.1.0",
|
|
@@ -38,15 +38,13 @@
|
|
|
38
38
|
|
|
39
39
|
import React, { ReactNode, useMemo } from 'react';
|
|
40
40
|
import { usePathname } from 'next/navigation';
|
|
41
|
-
import { SWRConfig } from 'swr';
|
|
42
|
-
import { ThemeProvider, Toaster, TooltipProvider } from '@djangocfg/ui-nextjs';
|
|
43
41
|
import { CentrifugoProvider } from '@djangocfg/centrifugo';
|
|
44
42
|
import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
43
|
+
import { type AuthConfig } from '../../auth/context';
|
|
44
|
+
import { type ValidationErrorConfig, type CORSErrorConfig, type NetworkErrorConfig } from '../../components/errors/ErrorsTracker';
|
|
47
45
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
48
|
-
import { PageProgress } from '../../components/core/PageProgress';
|
|
49
46
|
import { Suspense, ClientOnly } from '../../components/core';
|
|
47
|
+
import { BaseApp } from './BaseApp';
|
|
50
48
|
|
|
51
49
|
export type LayoutMode = 'public' | 'private' | 'admin';
|
|
52
50
|
|
|
@@ -243,14 +241,6 @@ function AppLayoutContent({
|
|
|
243
241
|
</CentrifugoProvider>
|
|
244
242
|
);
|
|
245
243
|
|
|
246
|
-
// Global toast notifications (always rendered)
|
|
247
|
-
content = (
|
|
248
|
-
<>
|
|
249
|
-
{content}
|
|
250
|
-
<PageProgress />
|
|
251
|
-
<Toaster />
|
|
252
|
-
</>
|
|
253
|
-
);
|
|
254
244
|
|
|
255
245
|
// Wrap with ErrorBoundary if enabled
|
|
256
246
|
if (enableErrorBoundary) {
|
|
@@ -272,44 +262,14 @@ function AppLayoutContent({
|
|
|
272
262
|
*/
|
|
273
263
|
export function AppLayout(props: AppLayoutProps) {
|
|
274
264
|
const { theme, auth, errorTracking } = props;
|
|
275
|
-
|
|
276
|
-
// Convert 'system' to actual theme based on system preference
|
|
277
|
-
const getResolvedTheme = (): 'light' | 'dark' => {
|
|
278
|
-
if (theme?.defaultTheme === 'system') {
|
|
279
|
-
if (typeof window !== 'undefined') {
|
|
280
|
-
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
281
|
-
}
|
|
282
|
-
return 'light'; // Default fallback for SSR
|
|
283
|
-
}
|
|
284
|
-
return theme?.defaultTheme || 'light';
|
|
285
|
-
};
|
|
286
|
-
|
|
265
|
+
|
|
287
266
|
return (
|
|
288
|
-
<
|
|
289
|
-
|
|
290
|
-
|
|
267
|
+
<BaseApp
|
|
268
|
+
theme={theme}
|
|
269
|
+
auth={auth}
|
|
270
|
+
errorTracking={errorTracking}
|
|
291
271
|
>
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
value={{
|
|
295
|
-
// Default SWR configuration
|
|
296
|
-
revalidateOnFocus: true,
|
|
297
|
-
revalidateOnReconnect: true,
|
|
298
|
-
dedupingInterval: 2000,
|
|
299
|
-
}}
|
|
300
|
-
>
|
|
301
|
-
<AuthProvider config={auth}>
|
|
302
|
-
<ErrorTrackingProvider
|
|
303
|
-
validation={errorTracking?.validation}
|
|
304
|
-
cors={errorTracking?.cors}
|
|
305
|
-
network={errorTracking?.network}
|
|
306
|
-
onError={errorTracking?.onError}
|
|
307
|
-
>
|
|
308
|
-
<AppLayoutContent {...props} />
|
|
309
|
-
</ErrorTrackingProvider>
|
|
310
|
-
</AuthProvider>
|
|
311
|
-
</SWRConfig>
|
|
312
|
-
</TooltipProvider>
|
|
313
|
-
</ThemeProvider>
|
|
272
|
+
<AppLayoutContent {...props} />
|
|
273
|
+
</BaseApp>
|
|
314
274
|
);
|
|
315
275
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseApp - Core Providers Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Provides essential app-wide providers:
|
|
5
|
+
* - ThemeProvider (light/dark/system theme)
|
|
6
|
+
* - TooltipProvider (tooltip positioning)
|
|
7
|
+
* - SWRConfig (data fetching)
|
|
8
|
+
* - AuthProvider (authentication context)
|
|
9
|
+
* - ErrorTrackingProvider (error handling)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { BaseApp } from '@djangocfg/layouts';
|
|
14
|
+
*
|
|
15
|
+
* <BaseApp
|
|
16
|
+
* theme={{ defaultTheme: 'system' }}
|
|
17
|
+
* auth={{ loginPath: '/auth/login' }}
|
|
18
|
+
* >
|
|
19
|
+
* {children}
|
|
20
|
+
* </BaseApp>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use client';
|
|
25
|
+
|
|
26
|
+
import { ReactNode } from 'react';
|
|
27
|
+
import { SWRConfig } from 'swr';
|
|
28
|
+
import { ThemeProvider, Toaster, TooltipProvider } from '@djangocfg/ui-nextjs';
|
|
29
|
+
import { AuthProvider, type AuthConfig } from '../../auth/context';
|
|
30
|
+
import { ErrorTrackingProvider, type ValidationErrorConfig, type CORSErrorConfig, type NetworkErrorConfig } from '../../components/errors/ErrorsTracker';
|
|
31
|
+
import { PageProgress } from '../../components/core/PageProgress';
|
|
32
|
+
|
|
33
|
+
export interface BaseAppProps {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
|
|
36
|
+
/** Theme configuration */
|
|
37
|
+
theme?: {
|
|
38
|
+
defaultTheme?: 'light' | 'dark' | 'system';
|
|
39
|
+
storageKey?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Auth configuration */
|
|
43
|
+
auth?: AuthConfig;
|
|
44
|
+
|
|
45
|
+
/** Error tracking configuration */
|
|
46
|
+
errorTracking?: {
|
|
47
|
+
validation?: Partial<ValidationErrorConfig>;
|
|
48
|
+
cors?: Partial<CORSErrorConfig>;
|
|
49
|
+
network?: Partial<NetworkErrorConfig>;
|
|
50
|
+
onError?: (error: any) => boolean | void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** SWR configuration */
|
|
54
|
+
swr?: {
|
|
55
|
+
revalidateOnFocus?: boolean;
|
|
56
|
+
revalidateOnReconnect?: boolean;
|
|
57
|
+
dedupingInterval?: number;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* BaseApp - Core providers wrapper for any React/Next.js app
|
|
63
|
+
*
|
|
64
|
+
* Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, ErrorTrackingProvider
|
|
65
|
+
* Also renders global Toaster and PageProgress components
|
|
66
|
+
*/
|
|
67
|
+
export function BaseApp({
|
|
68
|
+
children,
|
|
69
|
+
theme,
|
|
70
|
+
auth,
|
|
71
|
+
errorTracking,
|
|
72
|
+
swr,
|
|
73
|
+
}: BaseAppProps) {
|
|
74
|
+
return (
|
|
75
|
+
<ThemeProvider
|
|
76
|
+
defaultTheme={theme?.defaultTheme || 'system'}
|
|
77
|
+
storageKey={theme?.storageKey}
|
|
78
|
+
>
|
|
79
|
+
<TooltipProvider>
|
|
80
|
+
<SWRConfig
|
|
81
|
+
value={{
|
|
82
|
+
revalidateOnFocus: swr?.revalidateOnFocus ?? true,
|
|
83
|
+
revalidateOnReconnect: swr?.revalidateOnReconnect ?? true,
|
|
84
|
+
dedupingInterval: swr?.dedupingInterval ?? 2000,
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<AuthProvider config={auth}>
|
|
88
|
+
<ErrorTrackingProvider
|
|
89
|
+
validation={errorTracking?.validation}
|
|
90
|
+
cors={errorTracking?.cors}
|
|
91
|
+
network={errorTracking?.network}
|
|
92
|
+
onError={errorTracking?.onError}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
<PageProgress />
|
|
96
|
+
<Toaster />
|
|
97
|
+
</ErrorTrackingProvider>
|
|
98
|
+
</AuthProvider>
|
|
99
|
+
</SWRConfig>
|
|
100
|
+
</TooltipProvider>
|
|
101
|
+
</ThemeProvider>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -169,7 +169,18 @@ const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className })
|
|
|
169
169
|
if (isMobile) {
|
|
170
170
|
return (
|
|
171
171
|
<Portal>
|
|
172
|
-
<div
|
|
172
|
+
<div
|
|
173
|
+
className="z-[400] overflow-hidden"
|
|
174
|
+
style={{
|
|
175
|
+
position: 'fixed',
|
|
176
|
+
top: 0,
|
|
177
|
+
left: 0,
|
|
178
|
+
right: 0,
|
|
179
|
+
bottom: 0,
|
|
180
|
+
width: '100vw',
|
|
181
|
+
height: '100dvh',
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
173
184
|
<ChatPanel />
|
|
174
185
|
</div>
|
|
175
186
|
</Portal>
|
|
@@ -23,12 +23,19 @@ export const ChatPanel = React.memo(() => {
|
|
|
23
23
|
// Mobile: fullscreen, Desktop: floating panel
|
|
24
24
|
const panelStyles: React.CSSProperties = isMobile
|
|
25
25
|
? {
|
|
26
|
+
position: 'absolute',
|
|
27
|
+
top: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
right: 0,
|
|
30
|
+
bottom: 0,
|
|
26
31
|
width: '100%',
|
|
27
|
-
height: '
|
|
32
|
+
height: '100%',
|
|
28
33
|
maxHeight: '100dvh',
|
|
29
34
|
borderRadius: 0,
|
|
30
35
|
display: 'flex',
|
|
31
36
|
flexDirection: 'column',
|
|
37
|
+
margin: 0,
|
|
38
|
+
border: 'none',
|
|
32
39
|
}
|
|
33
40
|
: {
|
|
34
41
|
width: '380px',
|
|
@@ -38,7 +45,7 @@ export const ChatPanel = React.memo(() => {
|
|
|
38
45
|
|
|
39
46
|
return (
|
|
40
47
|
<Card
|
|
41
|
-
className={`flex flex-col
|
|
48
|
+
className={`flex flex-col ${isMobile ? 'rounded-none border-0 shadow-none' : 'shadow-2xl border-border/50'}`}
|
|
42
49
|
style={panelStyles}
|
|
43
50
|
>
|
|
44
51
|
{/* Header */}
|