@djangocfg/layouts 2.1.256 → 2.1.259
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 +101 -203
- package/package.json +18 -18
- package/src/index.ts +4 -1
- package/src/layouts/AppLayout/AppLayout.tsx +97 -8
- package/src/layouts/AppLayout/BaseApp.tsx +2 -0
- package/src/layouts/AppLayout/index.ts +6 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +3 -1
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +15 -4
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +3 -3
- package/src/layouts/PublicLayout/PublicLayout.tsx +82 -17
- package/src/layouts/PublicLayout/components/PublicFooter/FooterProjectInfo.tsx +17 -24
- package/src/layouts/PublicLayout/components/PublicFooter/PublicFooter.tsx +79 -95
- package/src/layouts/PublicLayout/components/PublicFooter/index.ts +2 -0
- package/src/layouts/PublicLayout/components/PublicFooter/types.ts +41 -31
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +84 -40
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +22 -35
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +184 -98
- package/src/layouts/PublicLayout/components/ThemeBrandMark.tsx +83 -0
- package/src/layouts/PublicLayout/components/index.ts +2 -0
- package/src/layouts/PublicLayout/context.tsx +5 -0
- package/src/layouts/PublicLayout/hooks/index.ts +1 -1
- package/src/layouts/PublicLayout/hooks/useMobileNavPanel.ts +55 -0
- package/src/layouts/PublicLayout/index.ts +8 -0
- package/src/layouts/PublicLayout/navbarTypes.ts +20 -0
- package/src/layouts/PublicLayout/publicShellShadow.ts +12 -0
- package/src/layouts/_components/PrivateSidebarAccount.tsx +16 -3
- package/src/layouts/_components/UserMenu.tsx +133 -30
- package/src/layouts/types/index.ts +10 -1
- package/src/layouts/types/providers.types.ts +10 -0
- package/src/layouts/types/ui.types.ts +9 -0
- package/src/theme/ThemeStyleBridge.tsx +41 -0
- package/src/theme/buildThemeStyleSheet.ts +71 -0
- package/src/theme/index.ts +16 -0
- package/src/theme/themeStyle.types.ts +89 -0
- package/src/theme/themeStylePresets.ts +202 -0
- package/src/layouts/PublicLayout/hooks/useFloatingPanel.ts +0 -61
package/README.md
CHANGED
|
@@ -6,19 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
# @djangocfg/layouts
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Layouts, app shell, and providers for **Next.js App Router**. Part of **[DjangoCFG](https://djangocfg.com)**.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Install
|
|
11
|
+
## Install & CSS
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
14
|
pnpm add @djangocfg/layouts
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
Add to `globals.css` (before `@import "tailwindcss"`):
|
|
20
|
-
|
|
21
17
|
```css
|
|
18
|
+
/* Before @import "tailwindcss" */
|
|
22
19
|
@import "@djangocfg/ui-nextjs/styles";
|
|
23
20
|
@import "@djangocfg/layouts/styles";
|
|
24
21
|
@import "@djangocfg/ui-tools/styles";
|
|
@@ -26,254 +23,155 @@ Add to `globals.css` (before `@import "tailwindcss"`):
|
|
|
26
23
|
@import "tailwindcss";
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Core providers wrapper. Use directly when you don't need route-based layout switching.
|
|
26
|
+
Peers: `@djangocfg/ui-core`, `@djangocfg/ui-nextjs`, React 19, Next.js 16+, Tailwind CSS 4.
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
import { BaseApp } from '@djangocfg/layouts';
|
|
35
|
-
|
|
36
|
-
export default function RootLayout({ children }) {
|
|
37
|
-
return (
|
|
38
|
-
<html lang="en" suppressHydrationWarning>
|
|
39
|
-
<body>
|
|
40
|
-
<BaseApp
|
|
41
|
-
project="my-app"
|
|
42
|
-
theme={{ defaultTheme: 'dark', storageKey: 'my-theme' }}
|
|
43
|
-
auth={{ apiUrl: process.env.NEXT_PUBLIC_API_URL }}
|
|
44
|
-
analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
|
|
45
|
-
>
|
|
46
|
-
{children}
|
|
47
|
-
</BaseApp>
|
|
48
|
-
</body>
|
|
49
|
-
</html>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
28
|
+
---
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| Prop | Type | Default | Description |
|
|
57
|
-
|------|------|---------|-------------|
|
|
58
|
-
| `project` | `string` | — | App name — auto-passed to `MonitorProvider` and debug panel |
|
|
59
|
-
| `theme` | `ThemeConfig` | — | Theme config (defaultTheme, storageKey) |
|
|
60
|
-
| `auth` | `AuthConfig` | — | Auth provider config |
|
|
61
|
-
| `analytics` | `AnalyticsConfig` | — | Google Analytics config |
|
|
62
|
-
| `centrifugo` | `CentrifugoConfig` | — | WebSocket real-time config |
|
|
63
|
-
| `errorTracking` | `ErrorTrackingConfig` | — | Validation/CORS/network error capture |
|
|
64
|
-
| `errorBoundary` | `ErrorBoundaryConfig` | enabled | React error boundary |
|
|
65
|
-
| `swr` | `SWRConfigOptions` | — | SWR data fetching config |
|
|
66
|
-
| `pwaInstall` | `PwaInstallConfig` | — | PWA install prompt (from `@djangocfg/ui-nextjs/pwa`) |
|
|
67
|
-
| `monitor` | `MonitorConfig` | — | Override monitor config (project/environment come from `project` prop by default) |
|
|
68
|
-
| `debug` | `DebugConfig` | enabled | Debug panel config — see below |
|
|
69
|
-
|
|
70
|
-
**Included automatically:**
|
|
71
|
-
- ThemeProvider, TooltipProvider, SWRConfig, DialogProvider
|
|
72
|
-
- AuthProvider + AuthDialog (via zustand `dialogStore`, triggered by `window.dialog.auth()`)
|
|
73
|
-
- AnalyticsProvider, CentrifugoProvider
|
|
74
|
-
- PwaProvider (from `@djangocfg/ui-nextjs/pwa`)
|
|
75
|
-
- ErrorTrackingProvider, ErrorBoundary
|
|
76
|
-
- MonitorProvider (auto-enabled via `project` prop)
|
|
77
|
-
- DebugButton from `@djangocfg/debuger`
|
|
78
|
-
- NextTopLoader, Toaster
|
|
79
|
-
|
|
80
|
-
## AppLayout
|
|
81
|
-
|
|
82
|
-
Smart layout router — selects layout based on current route. Wraps `BaseApp`, accepts all its props.
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
import { AppLayout } from '@djangocfg/layouts';
|
|
86
|
-
import { PublicLayout } from './_layouts/PublicLayout';
|
|
87
|
-
import { PrivateLayout } from './_layouts/PrivateLayout';
|
|
88
|
-
import { AdminLayout } from './_layouts/AdminLayout';
|
|
89
|
-
|
|
90
|
-
export default function RootLayout({ children }) {
|
|
91
|
-
return (
|
|
92
|
-
<html lang="en" suppressHydrationWarning>
|
|
93
|
-
<body>
|
|
94
|
-
<AppLayout
|
|
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
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
{children}
|
|
108
|
-
</AppLayout>
|
|
109
|
-
</body>
|
|
110
|
-
</html>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
**Layout priority:** Admin → Private → Public → Fallback
|
|
116
|
-
|
|
117
|
-
**`noLayoutPaths`** — render without layout wrapper (providers still active). Useful for fullscreen pages.
|
|
30
|
+
## BaseApp
|
|
118
31
|
|
|
119
|
-
|
|
32
|
+
Root provider (theme, auth, analytics, SWR, toasts, …). Use when you **do not** need route-based layout switching.
|
|
120
33
|
|
|
121
34
|
```tsx
|
|
122
|
-
import {
|
|
123
|
-
|
|
124
|
-
const { locale, locales, changeLocale } = useLocaleSwitcher();
|
|
35
|
+
import { BaseApp } from '@djangocfg/layouts';
|
|
125
36
|
|
|
126
|
-
<
|
|
127
|
-
|
|
37
|
+
<BaseApp
|
|
38
|
+
project="my-app"
|
|
39
|
+
theme={{ defaultTheme: 'system', storageKey: 'my-theme' }}
|
|
40
|
+
auth={{ apiUrl: process.env.NEXT_PUBLIC_API_URL }}
|
|
128
41
|
>
|
|
129
42
|
{children}
|
|
130
|
-
</
|
|
43
|
+
</BaseApp>
|
|
131
44
|
```
|
|
132
45
|
|
|
133
|
-
|
|
46
|
+
| Prop | Role |
|
|
47
|
+
|------|------|
|
|
48
|
+
| `project` | Name for monitor / debug |
|
|
49
|
+
| `theme` | `defaultTheme`, `storageKey`, optional **`style`** (see below) |
|
|
50
|
+
| `auth`, `analytics`, `centrifugo`, `errorTracking`, `errorBoundary`, `swr`, `pwaInstall`, `monitor`, `debug` | Optional integrations |
|
|
134
51
|
|
|
135
|
-
|
|
136
|
-
import { PublicLayout, PrivateLayout, AuthLayout, AdminLayout, ProfileLayout } from '@djangocfg/layouts';
|
|
137
|
-
```
|
|
52
|
+
### Theme: `theme.style` + `ThemeStyleBridge`
|
|
138
53
|
|
|
139
|
-
|
|
140
|
-
|--------|-------------|
|
|
141
|
-
| `PublicLayout` | Public pages with nav (home, docs, contact, legal) |
|
|
142
|
-
| `PrivateLayout` | Authenticated pages with sidebar |
|
|
143
|
-
| `AuthLayout` | OTP + OAuth + 2FA authentication flow |
|
|
144
|
-
| `AdminLayout` | Admin panel |
|
|
145
|
-
| `ProfileLayout` | User profile with 2FA management |
|
|
54
|
+
`BaseApp` passes theme to **next-themes** and mounts **`ThemeStyleBridge`**, which injects `<style id="djangocfg-baseapp-theme-style">` with **`--*`** variables (same naming as `@djangocfg/ui-core` `theme/light.css` & `dark.css`).
|
|
146
55
|
|
|
147
|
-
|
|
56
|
+
| Piece | Meaning |
|
|
57
|
+
|--------|---------|
|
|
58
|
+
| **`preset`** | One of **`THEME_STYLE_PRESET_ORDER`** — curated bundles in **`THEME_STYLE_PRESETS`** |
|
|
59
|
+
| **`vars.light` / `vars.dark`** | Partial **`ThemeCssVarMap`** — HSL triplets (e.g. `192 90% 35%`); **`radius`** accepts any CSS length (`0.75rem`, …) |
|
|
148
60
|
|
|
149
|
-
|
|
150
|
-
<AuthLayout
|
|
151
|
-
sourceUrl="https://example.com"
|
|
152
|
-
redirectUrl="/dashboard"
|
|
153
|
-
logoUrl="/logo.svg"
|
|
154
|
-
enableGithubAuth={true}
|
|
155
|
-
termsUrl="/terms"
|
|
156
|
-
privacyUrl="/privacy"
|
|
157
|
-
/>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### ProfileLayout
|
|
61
|
+
Merge order: **imported globals → preset → `vars`** (strongest).
|
|
161
62
|
|
|
162
|
-
|
|
163
|
-
<ProfileLayout
|
|
164
|
-
enable2FA={true}
|
|
165
|
-
showMemberSince={true}
|
|
166
|
-
/>
|
|
167
|
-
```
|
|
63
|
+
**Presets (summary)**
|
|
168
64
|
|
|
169
|
-
|
|
65
|
+
| ID | Role |
|
|
66
|
+
|----|------|
|
|
67
|
+
| `default` | No extra rules (globals only) |
|
|
68
|
+
| `django-cfg` | Brand cyan primary + sidebar/chart alignment |
|
|
69
|
+
| `ios` | System blue, hairline borders, **0.75rem** radius (Apple-like) |
|
|
70
|
+
| `soft` | **1rem** radius, softer borders / surfaces |
|
|
71
|
+
| `dense` | **0.25rem** radius, stronger borders (data / admin) |
|
|
72
|
+
| `high-contrast` | Stronger borders & text contrast |
|
|
170
73
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
// window.monitor available in DevTools:
|
|
175
|
-
window.monitor.error('Something broke', { context: 'checkout' })
|
|
176
|
-
window.monitor.warn('Slow response', { ms: 2500 })
|
|
177
|
-
window.monitor.flush() // send buffer immediately
|
|
178
|
-
window.monitor.status() // show current state
|
|
179
|
-
```
|
|
74
|
+
**Typing:** keys are grouped in source as **`ThemeCssVarColorKey`**, **`ThemeCssVarChromeKey`** (includes **`radius`**), **`ThemeCssVarSidebarKey`**, **`ThemeCssVarChartKey`** — together **`ThemeCssVarKey`**. Same semantics as the **Theme Configurator** playground’s `ThemeData.colors` / `radius` / `sidebar` / `charts`, mapped to kebab-case **`--`** names. Playground-only buckets (**shadows**, **typography**, **spacing**, …) are **not** injected here — export **full CSS** from the configurator when you need them.
|
|
180
75
|
|
|
181
|
-
|
|
76
|
+
**Exports:** `ThemeStyleConfig`, `ThemeCssVarKey`, `ThemeCssVarMap`, `ThemeStylePresetId`, `THEME_STYLE_PRESETS`, `THEME_STYLE_PRESET_ORDER`, `buildThemeStyleSheet`, `ThemeStyleBridge`.
|
|
182
77
|
|
|
183
78
|
```tsx
|
|
184
79
|
<BaseApp
|
|
185
|
-
|
|
186
|
-
|
|
80
|
+
theme={{
|
|
81
|
+
defaultTheme: 'system',
|
|
82
|
+
storageKey: 'my-app-theme',
|
|
83
|
+
style: {
|
|
84
|
+
preset: 'django-cfg',
|
|
85
|
+
vars: { light: { ring: '200 85% 40%' }, dark: { 'muted-foreground': '0 0% 70%' } },
|
|
86
|
+
},
|
|
87
|
+
}}
|
|
187
88
|
>
|
|
89
|
+
{children}
|
|
90
|
+
</BaseApp>
|
|
188
91
|
```
|
|
189
92
|
|
|
190
|
-
|
|
93
|
+
---
|
|
191
94
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
// enabled by default (omit or pass empty object)
|
|
196
|
-
<BaseApp project="my-app">
|
|
197
|
-
|
|
198
|
-
// disable
|
|
199
|
-
<BaseApp debug={{ enabled: false }}>
|
|
95
|
+
## AppLayout
|
|
200
96
|
|
|
201
|
-
|
|
202
|
-
import type { CustomDebugTab } from '@djangocfg/debuger';
|
|
97
|
+
Wraps **BaseApp** and picks **admin → private → public** layout by path (`matchesPath` / `enabledPath`). **`noLayoutPaths`** skips the shell (fullscreen / embeds). **`publicChrome`** merges navbar/footer/main spacing defaults (see types **`AppLayoutPublicChrome`**, **`mergeAppLayoutPublicChrome`**).
|
|
203
98
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
];
|
|
99
|
+
```tsx
|
|
100
|
+
import { AppLayout } from '@djangocfg/layouts';
|
|
207
101
|
|
|
208
|
-
<AppLayout
|
|
102
|
+
<AppLayout
|
|
103
|
+
layouts={{
|
|
104
|
+
public: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
|
|
105
|
+
private: { component: PrivateLayout, enabledPath: ['/dashboard'] },
|
|
106
|
+
admin: { component: AdminLayout, enabledPath: '/admin' },
|
|
107
|
+
noLayoutPaths: ['/embed'],
|
|
108
|
+
}}
|
|
109
|
+
baseApp={{ project: 'my-app', theme: { defaultTheme: 'system' }, auth: { apiUrl: '…' } }}
|
|
110
|
+
i18n={{ locale, locales, onLocaleChange }}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</AppLayout>
|
|
209
114
|
```
|
|
210
115
|
|
|
211
|
-
|
|
116
|
+
---
|
|
212
117
|
|
|
213
|
-
|
|
214
|
-
import { useErrorEmitter, emitRuntimeError } from '@djangocfg/layouts';
|
|
118
|
+
## Layouts (import from `@djangocfg/layouts`)
|
|
215
119
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
120
|
+
| Component | Use |
|
|
121
|
+
|-----------|-----|
|
|
122
|
+
| **PublicLayout** | Marketing / docs — slots: **`navbar`**, **`footer`**, **`contentTopSpacing`**, **`contentBottomSpacing`** |
|
|
123
|
+
| **PublicNavbar** / **PublicFooter** | **`PublicNavbarConfig`** / **`PublicFooterConfig`** — **`shell.rounding`**, **`navbarVariant`**, **`navbarPosition`**, etc. |
|
|
124
|
+
| **PrivateLayout** | App shell — sidebar + header |
|
|
125
|
+
| **AuthLayout** | Sign-in flows |
|
|
126
|
+
| **AdminLayout** | Admin console |
|
|
127
|
+
| **ProfileLayout** | Profile + 2FA |
|
|
219
128
|
|
|
220
|
-
|
|
221
|
-
emitRuntimeError('MyUtil', 'Operation failed', error);
|
|
222
|
-
```
|
|
129
|
+
**Brand:** `ThemeBrandMark` / **`ThemeBrandMarkImg`** for logo slots.
|
|
223
130
|
|
|
224
|
-
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## i18n on AppLayout
|
|
225
134
|
|
|
226
135
|
```tsx
|
|
227
|
-
import {
|
|
136
|
+
import { useLocaleSwitcher } from '@djangocfg/nextjs/i18n/client';
|
|
228
137
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return <ErrorLayout code={404} supportEmail="support@example.com" />;
|
|
232
|
-
}
|
|
138
|
+
const { locale, locales, changeLocale } = useLocaleSwitcher();
|
|
139
|
+
<AppLayout i18n={{ locale, locales, onLocaleChange: changeLocale }}>{children}</AppLayout>
|
|
233
140
|
```
|
|
234
141
|
|
|
142
|
+
---
|
|
235
143
|
|
|
236
|
-
##
|
|
144
|
+
## Monitor & debug
|
|
237
145
|
|
|
238
|
-
|
|
239
|
-
import { RedirectPage } from '@djangocfg/layouts/components/RedirectPage';
|
|
146
|
+
`project` on **BaseApp** enables **`window.monitor`**. **Debug:** `Cmd+D` or `?debug=1`.
|
|
240
147
|
|
|
241
|
-
|
|
242
|
-
return <RedirectPage authenticatedPath="/dashboard" unauthenticatedPath="/auth" />;
|
|
243
|
-
}
|
|
244
|
-
```
|
|
148
|
+
---
|
|
245
149
|
|
|
246
|
-
##
|
|
150
|
+
## Utilities
|
|
247
151
|
|
|
248
|
-
|
|
249
|
-
|
|
152
|
+
| API | Role |
|
|
153
|
+
|-----|------|
|
|
154
|
+
| **useErrorEmitter**, **emitRuntimeError** | Error tracking hooks |
|
|
155
|
+
| **ErrorLayout** (`/components/errors`) | 404 / error pages |
|
|
156
|
+
| **RedirectPage** | Auth redirect helper |
|
|
157
|
+
| **PrivacyPage**, **TermsPage**, … (`/pages/legal`) | Legal templates |
|
|
250
158
|
|
|
251
|
-
|
|
252
|
-
```
|
|
159
|
+
---
|
|
253
160
|
|
|
254
|
-
##
|
|
161
|
+
## Package exports
|
|
255
162
|
|
|
256
|
-
| Path |
|
|
257
|
-
|
|
258
|
-
| `@djangocfg/layouts` |
|
|
163
|
+
| Path | Contents |
|
|
164
|
+
|------|----------|
|
|
165
|
+
| `@djangocfg/layouts` | Barrel |
|
|
259
166
|
| `@djangocfg/layouts/layouts` | Layout components |
|
|
260
|
-
| `@djangocfg/layouts/
|
|
261
|
-
| `@djangocfg/layouts/components` | Utility components |
|
|
167
|
+
| `@djangocfg/layouts/components` | Misc |
|
|
262
168
|
| `@djangocfg/layouts/pages/legal` | Legal pages |
|
|
263
|
-
| `@djangocfg/layouts/
|
|
264
|
-
| `@djangocfg/layouts/styles` | CSS |
|
|
265
|
-
| `@djangocfg/layouts/styles/dashboard` | Dashboard CSS |
|
|
169
|
+
| `@djangocfg/layouts/styles` | Base CSS |
|
|
266
170
|
|
|
267
|
-
## Extension
|
|
171
|
+
## Extension packages
|
|
268
172
|
|
|
269
|
-
|
|
270
|
-
|---------|-------------|
|
|
271
|
-
| `@djangocfg/ext-newsletter` | Newsletter subscription |
|
|
272
|
-
| `@djangocfg/ext-knowbase` | Knowledge base + AI chat |
|
|
273
|
-
| `@djangocfg/ext-leads` | Lead capture forms |
|
|
274
|
-
| `@djangocfg/ext-payments` | Payments & subscriptions |
|
|
275
|
-
| `@djangocfg/ext-support` | Support tickets |
|
|
173
|
+
`@djangocfg/ext-newsletter`, `ext-knowbase`, `ext-leads`, `ext-payments`, `ext-support`, …
|
|
276
174
|
|
|
277
175
|
## License
|
|
278
176
|
|
|
279
|
-
MIT — [DjangoCFG](https://djangocfg.com)
|
|
177
|
+
MIT — [DjangoCFG](https://djangocfg.com)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.259",
|
|
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.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/i18n": "^2.1.
|
|
80
|
-
"@djangocfg/monitor": "^2.1.
|
|
81
|
-
"@djangocfg/debuger": "^2.1.
|
|
82
|
-
"@djangocfg/ui-core": "^2.1.
|
|
83
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
84
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
77
|
+
"@djangocfg/api": "^2.1.259",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.259",
|
|
79
|
+
"@djangocfg/i18n": "^2.1.259",
|
|
80
|
+
"@djangocfg/monitor": "^2.1.259",
|
|
81
|
+
"@djangocfg/debuger": "^2.1.259",
|
|
82
|
+
"@djangocfg/ui-core": "^2.1.259",
|
|
83
|
+
"@djangocfg/ui-nextjs": "^2.1.259",
|
|
84
|
+
"@djangocfg/ui-tools": "^2.1.259",
|
|
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.
|
|
113
|
-
"@djangocfg/i18n": "^2.1.
|
|
114
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
115
|
-
"@djangocfg/monitor": "^2.1.
|
|
116
|
-
"@djangocfg/debuger": "^2.1.
|
|
117
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
118
|
-
"@djangocfg/ui-core": "^2.1.
|
|
119
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
120
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
112
|
+
"@djangocfg/api": "^2.1.259",
|
|
113
|
+
"@djangocfg/i18n": "^2.1.259",
|
|
114
|
+
"@djangocfg/centrifugo": "^2.1.259",
|
|
115
|
+
"@djangocfg/monitor": "^2.1.259",
|
|
116
|
+
"@djangocfg/debuger": "^2.1.259",
|
|
117
|
+
"@djangocfg/typescript-config": "^2.1.259",
|
|
118
|
+
"@djangocfg/ui-core": "^2.1.259",
|
|
119
|
+
"@djangocfg/ui-nextjs": "^2.1.259",
|
|
120
|
+
"@djangocfg/ui-tools": "^2.1.259",
|
|
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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* // Public page
|
|
12
12
|
* <PublicLayout
|
|
13
|
-
* navbar={<PublicNavbar config={{
|
|
13
|
+
* navbar={<PublicNavbar config={{ brand: "My App", navigation: navItems, shell: { className: 'mx-auto max-w-7xl' } }} />}
|
|
14
14
|
* >
|
|
15
15
|
* {children}
|
|
16
16
|
* </PublicLayout>
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
// Layout components
|
|
33
33
|
export * from './layouts';
|
|
34
34
|
|
|
35
|
+
// Typed theme style bridge + presets (BaseApp `theme.style`)
|
|
36
|
+
export * from './theme';
|
|
37
|
+
|
|
35
38
|
// Re-export useRouter from nextjs-toploader for progress bar support
|
|
36
39
|
// Use this instead of 'next/navigation' useRouter for router.push() to trigger progress
|
|
37
40
|
export { useRouter } from 'nextjs-toploader/app';
|
|
@@ -52,8 +52,78 @@ import type {
|
|
|
52
52
|
import type { AuthConfig } from '@djangocfg/api/auth';
|
|
53
53
|
import type { MonitorConfig } from '@djangocfg/monitor';
|
|
54
54
|
|
|
55
|
+
import type { PublicNavbarConfig } from '../PublicLayout/components/PublicNavbar';
|
|
56
|
+
import type { PublicFooterConfig } from '../PublicLayout/components/PublicFooter/types';
|
|
57
|
+
|
|
55
58
|
export type LayoutMode = 'public' | 'private' | 'admin';
|
|
56
59
|
|
|
60
|
+
/** `<main>` spacing in `PublicLayout` — offset from navbar / padding before footer. */
|
|
61
|
+
export type PublicMainTopSpacing = 'auto' | 'none';
|
|
62
|
+
|
|
63
|
+
/** Bottom padding of `<main>` above the footer. `compact` = less than `auto`; `none` = no extra gap. */
|
|
64
|
+
export type PublicMainBottomSpacing = 'auto' | 'none' | 'compact';
|
|
65
|
+
|
|
66
|
+
/** Shared marketing chrome defaults for `PublicNavbar` / `PublicFooter`, merged into your public layout. */
|
|
67
|
+
export interface AppLayoutPublicChrome {
|
|
68
|
+
navbar?: Partial<PublicNavbarConfig>;
|
|
69
|
+
footer?: Partial<PublicFooterConfig>;
|
|
70
|
+
/**
|
|
71
|
+
* Passed through to `PublicLayout` as `contentTopSpacing` / `contentBottomSpacing`.
|
|
72
|
+
* Use `bottomSpacing: 'none'` when the page (or footer) should sit flush with no default gap.
|
|
73
|
+
*/
|
|
74
|
+
main?: {
|
|
75
|
+
topSpacing?: PublicMainTopSpacing;
|
|
76
|
+
bottomSpacing?: PublicMainBottomSpacing;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mergePartialNavbar(
|
|
81
|
+
base?: Partial<PublicNavbarConfig>,
|
|
82
|
+
overlay?: Partial<PublicNavbarConfig>
|
|
83
|
+
): Partial<PublicNavbarConfig> | undefined {
|
|
84
|
+
if (!base && !overlay) return undefined;
|
|
85
|
+
const merged: Partial<PublicNavbarConfig> = { ...base, ...overlay };
|
|
86
|
+
if (base?.shell || overlay?.shell) {
|
|
87
|
+
merged.shell = { ...base?.shell, ...overlay?.shell };
|
|
88
|
+
}
|
|
89
|
+
return merged;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function mergePartialFooter(
|
|
93
|
+
base?: Partial<PublicFooterConfig>,
|
|
94
|
+
overlay?: Partial<PublicFooterConfig>
|
|
95
|
+
): Partial<PublicFooterConfig> | undefined {
|
|
96
|
+
if (!base && !overlay) return undefined;
|
|
97
|
+
const merged: Partial<PublicFooterConfig> = { ...base, ...overlay };
|
|
98
|
+
if (base?.shell || overlay?.shell) merged.shell = { ...base?.shell, ...overlay?.shell };
|
|
99
|
+
if (base?.brand || overlay?.brand) merged.brand = { ...base?.brand, ...overlay?.brand };
|
|
100
|
+
if (base?.menus || overlay?.menus) merged.menus = { ...base?.menus, ...overlay?.menus };
|
|
101
|
+
if (base?.meta || overlay?.meta) merged.meta = { ...base?.meta, ...overlay?.meta };
|
|
102
|
+
if (base?.social || overlay?.social) merged.social = { ...base?.social, ...overlay?.social };
|
|
103
|
+
if (base?.controls || overlay?.controls) {
|
|
104
|
+
merged.controls = { ...base?.controls, ...overlay?.controls };
|
|
105
|
+
}
|
|
106
|
+
return merged;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Deep-merge public chrome: root `publicChrome` first, then `layouts.publicChrome` overlays.
|
|
111
|
+
*/
|
|
112
|
+
export function mergeAppLayoutPublicChrome(
|
|
113
|
+
root?: AppLayoutPublicChrome,
|
|
114
|
+
fromLayouts?: AppLayoutPublicChrome
|
|
115
|
+
): AppLayoutPublicChrome | undefined {
|
|
116
|
+
if (!root && !fromLayouts) return undefined;
|
|
117
|
+
const navbar = mergePartialNavbar(root?.navbar, fromLayouts?.navbar);
|
|
118
|
+
const footer = mergePartialFooter(root?.footer, fromLayouts?.footer);
|
|
119
|
+
const main =
|
|
120
|
+
root?.main || fromLayouts?.main
|
|
121
|
+
? { ...root?.main, ...fromLayouts?.main }
|
|
122
|
+
: undefined;
|
|
123
|
+
if (!navbar && !footer && !main) return undefined;
|
|
124
|
+
return { navbar, footer, ...(main ? { main } : {}) };
|
|
125
|
+
}
|
|
126
|
+
|
|
57
127
|
/**
|
|
58
128
|
* Determine layout mode from pathname and enabledPath props
|
|
59
129
|
*/
|
|
@@ -67,7 +137,7 @@ function determineLayoutMode(
|
|
|
67
137
|
if (adminLayout && matchesPath(pathname, adminLayout.enabledPath)) return 'admin';
|
|
68
138
|
if (privateLayout && matchesPath(pathname, privateLayout.enabledPath)) return 'private';
|
|
69
139
|
if (publicLayout && matchesPath(pathname, publicLayout.enabledPath)) return 'public';
|
|
70
|
-
|
|
140
|
+
|
|
71
141
|
// Default: if no enabledPath specified, use public as fallback
|
|
72
142
|
return 'public';
|
|
73
143
|
}
|
|
@@ -82,9 +152,19 @@ export interface I18nLayoutConfig {
|
|
|
82
152
|
onLocaleChange: (locale: string) => void;
|
|
83
153
|
}
|
|
84
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Props passed to every layout component (`public` / `private` / `admin`).
|
|
157
|
+
* Use `publicChrome` to pass defaults for `PublicNavbar` / `PublicFooter` from `AppLayout`.
|
|
158
|
+
*/
|
|
159
|
+
export interface AppLayoutLayoutComponentProps {
|
|
160
|
+
children: ReactNode;
|
|
161
|
+
i18n?: I18nLayoutConfig;
|
|
162
|
+
publicChrome?: AppLayoutPublicChrome;
|
|
163
|
+
}
|
|
164
|
+
|
|
85
165
|
/** Layout configuration with component and enabled paths */
|
|
86
166
|
interface LayoutConfig {
|
|
87
|
-
component: React.ComponentType<
|
|
167
|
+
component: React.ComponentType<AppLayoutLayoutComponentProps>;
|
|
88
168
|
enabledPath?: string | string[];
|
|
89
169
|
}
|
|
90
170
|
|
|
@@ -94,6 +174,8 @@ export interface AppLayoutLayoutsConfig {
|
|
|
94
174
|
admin?: LayoutConfig;
|
|
95
175
|
noLayoutPaths?: string | string[];
|
|
96
176
|
authPath?: string;
|
|
177
|
+
/** Merged over root `publicChrome` on `AppLayout` (see `mergeAppLayoutPublicChrome`). */
|
|
178
|
+
publicChrome?: AppLayoutPublicChrome;
|
|
97
179
|
}
|
|
98
180
|
|
|
99
181
|
export interface AppLayoutBaseAppConfig {
|
|
@@ -173,6 +255,9 @@ export interface AppLayoutProps {
|
|
|
173
255
|
/** i18n configuration for locale switching (applies to all layouts) */
|
|
174
256
|
i18n?: I18nLayoutConfig;
|
|
175
257
|
|
|
258
|
+
/** Base layer for `publicChrome`; `layouts.publicChrome` overlays this. */
|
|
259
|
+
publicChrome?: AppLayoutPublicChrome;
|
|
260
|
+
|
|
176
261
|
/** Monitor configuration — initialises window.monitor + auto-captures JS errors & console */
|
|
177
262
|
monitor?: MonitorConfig;
|
|
178
263
|
|
|
@@ -187,6 +272,7 @@ interface AppLayoutContentProps {
|
|
|
187
272
|
noLayoutPaths?: string | string[];
|
|
188
273
|
authPath?: string;
|
|
189
274
|
i18n?: I18nLayoutConfig;
|
|
275
|
+
publicChrome?: AppLayoutPublicChrome;
|
|
190
276
|
}
|
|
191
277
|
|
|
192
278
|
/**
|
|
@@ -203,6 +289,7 @@ function AppLayoutContent({
|
|
|
203
289
|
noLayoutPaths,
|
|
204
290
|
authPath = '/auth',
|
|
205
291
|
i18n,
|
|
292
|
+
publicChrome,
|
|
206
293
|
}: AppLayoutContentProps) {
|
|
207
294
|
// Use pathname without locale prefix for route matching
|
|
208
295
|
const pathname = usePathnameWithoutLocale();
|
|
@@ -244,7 +331,7 @@ function AppLayoutContent({
|
|
|
244
331
|
return (
|
|
245
332
|
<ClientOnly>
|
|
246
333
|
<Suspense>
|
|
247
|
-
<privateLayout.component i18n={i18n}>
|
|
334
|
+
<privateLayout.component i18n={i18n} publicChrome={publicChrome}>
|
|
248
335
|
{children}
|
|
249
336
|
</privateLayout.component>
|
|
250
337
|
</Suspense>
|
|
@@ -257,7 +344,7 @@ function AppLayoutContent({
|
|
|
257
344
|
return (
|
|
258
345
|
<ClientOnly>
|
|
259
346
|
<Suspense>
|
|
260
|
-
<adminLayout.component i18n={i18n}>
|
|
347
|
+
<adminLayout.component i18n={i18n} publicChrome={publicChrome}>
|
|
261
348
|
{children}
|
|
262
349
|
</adminLayout.component>
|
|
263
350
|
</Suspense>
|
|
@@ -268,7 +355,7 @@ function AppLayoutContent({
|
|
|
268
355
|
if (!privateLayout) {
|
|
269
356
|
if (publicLayout) {
|
|
270
357
|
return (
|
|
271
|
-
<publicLayout.component i18n={i18n}>
|
|
358
|
+
<publicLayout.component i18n={i18n} publicChrome={publicChrome}>
|
|
272
359
|
{children}
|
|
273
360
|
</publicLayout.component>
|
|
274
361
|
);
|
|
@@ -278,7 +365,7 @@ function AppLayoutContent({
|
|
|
278
365
|
return (
|
|
279
366
|
<ClientOnly>
|
|
280
367
|
<Suspense>
|
|
281
|
-
<privateLayout.component i18n={i18n}>
|
|
368
|
+
<privateLayout.component i18n={i18n} publicChrome={publicChrome}>
|
|
282
369
|
{children}
|
|
283
370
|
</privateLayout.component>
|
|
284
371
|
</Suspense>
|
|
@@ -292,13 +379,13 @@ function AppLayoutContent({
|
|
|
292
379
|
return children;
|
|
293
380
|
}
|
|
294
381
|
return (
|
|
295
|
-
<publicLayout.component i18n={i18n}>
|
|
382
|
+
<publicLayout.component i18n={i18n} publicChrome={publicChrome}>
|
|
296
383
|
{children}
|
|
297
384
|
</publicLayout.component>
|
|
298
385
|
);
|
|
299
386
|
}
|
|
300
387
|
};
|
|
301
|
-
|
|
388
|
+
|
|
302
389
|
// No providers here - all providers now in BaseApp
|
|
303
390
|
return renderLayout();
|
|
304
391
|
}
|
|
@@ -315,6 +402,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
315
402
|
const adminLayout = layoutsConfig?.admin ?? props.adminLayout;
|
|
316
403
|
const noLayoutPaths = layoutsConfig?.noLayoutPaths ?? props.noLayoutPaths;
|
|
317
404
|
const authPath = layoutsConfig?.authPath ?? props.authPath;
|
|
405
|
+
const publicChrome = mergeAppLayoutPublicChrome(props.publicChrome, layoutsConfig?.publicChrome);
|
|
318
406
|
|
|
319
407
|
const {
|
|
320
408
|
i18n,
|
|
@@ -355,6 +443,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
355
443
|
noLayoutPaths={noLayoutPaths}
|
|
356
444
|
authPath={authPath}
|
|
357
445
|
i18n={i18n}
|
|
446
|
+
publicChrome={publicChrome}
|
|
358
447
|
/>
|
|
359
448
|
</BaseApp>
|
|
360
449
|
);
|
|
@@ -49,6 +49,7 @@ import { CentrifugoProvider } from '@djangocfg/centrifugo';
|
|
|
49
49
|
import { Toaster, TooltipProvider } from '@djangocfg/ui-core/components';
|
|
50
50
|
import { DialogProvider } from '@djangocfg/ui-core/lib/dialog-service';
|
|
51
51
|
import { ThemeProvider } from '@djangocfg/ui-nextjs/theme';
|
|
52
|
+
import { ThemeStyleBridge } from '../../theme/ThemeStyleBridge';
|
|
52
53
|
import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
|
|
53
54
|
import { ErrorTrackingProvider } from '../../components/errors/ErrorsTracker';
|
|
54
55
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
@@ -115,6 +116,7 @@ export function BaseApp({
|
|
|
115
116
|
defaultTheme={theme?.defaultTheme || 'system'}
|
|
116
117
|
storageKey={theme?.storageKey}
|
|
117
118
|
>
|
|
119
|
+
<ThemeStyleBridge style={theme?.style} />
|
|
118
120
|
<DialogProvider>
|
|
119
121
|
<TooltipProvider>
|
|
120
122
|
<SWRConfig
|