@djangocfg/layouts 2.1.275 → 2.1.276
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 +52 -180
- package/package.json +18 -18
- package/src/layouts/AppLayout/AppLayout.tsx +14 -14
- package/src/layouts/PublicLayout/README.md +144 -0
- package/src/layouts/PublicLayout/{components/PublicFooter/PublicFooter.tsx → footers/DefaultFooter/DefaultFooter.tsx} +14 -8
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/DjangoCFGLogo.tsx +0 -6
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterBottom.tsx +0 -4
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterMenuSections.tsx +0 -4
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterProjectInfo.tsx +0 -4
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterSocialLinks.tsx +0 -5
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/index.ts +2 -12
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/types.ts +21 -26
- package/src/layouts/PublicLayout/footers/index.ts +1 -0
- package/src/layouts/PublicLayout/hooks/index.ts +1 -0
- package/src/layouts/PublicLayout/hooks/useResponsiveOverflow.ts +140 -0
- package/src/layouts/PublicLayout/index.ts +22 -22
- package/src/layouts/PublicLayout/navbarTypes.ts +27 -4
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingMobileDrawer.tsx +29 -0
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +117 -0
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushMobileDrawer.tsx +19 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushNavbar.tsx +112 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalMobileDrawer.tsx +19 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +169 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/index.ts +3 -0
- package/src/layouts/PublicLayout/primitives/NavActionItem.tsx +94 -0
- package/src/layouts/PublicLayout/{components → primitives}/NavActions.tsx +26 -1
- package/src/layouts/PublicLayout/{components → primitives}/NavDesktopItems.tsx +100 -56
- package/src/layouts/PublicLayout/{components → primitives}/ThemeBrandMark.tsx +0 -8
- package/src/layouts/PublicLayout/primitives/index.ts +7 -0
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +205 -0
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +295 -0
- package/src/layouts/PublicLayout/shared/index.ts +4 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +0 -211
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +0 -99
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +0 -287
- package/src/layouts/PublicLayout/components/index.ts +0 -11
- /package/src/layouts/PublicLayout/{components → primitives}/NavBrand.tsx +0 -0
package/README.md
CHANGED
|
@@ -8,18 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
Layouts, app shell, and providers for **Next.js App Router**. Part of **[DjangoCFG](https://djangocfg.com)**.
|
|
10
10
|
|
|
11
|
-
## Install & CSS
|
|
12
|
-
|
|
13
11
|
```bash
|
|
14
12
|
pnpm add @djangocfg/layouts
|
|
15
13
|
```
|
|
16
14
|
|
|
17
15
|
```css
|
|
18
|
-
/*
|
|
16
|
+
/* before @import "tailwindcss" */
|
|
19
17
|
@import "@djangocfg/ui-nextjs/styles";
|
|
20
18
|
@import "@djangocfg/layouts/styles";
|
|
21
|
-
@import "@djangocfg/ui-tools/styles";
|
|
22
|
-
@import "@djangocfg/debuger/styles";
|
|
23
19
|
@import "tailwindcss";
|
|
24
20
|
```
|
|
25
21
|
|
|
@@ -29,11 +25,9 @@ Peers: `@djangocfg/ui-core`, `@djangocfg/ui-nextjs`, React 19, Next.js 16+, Tail
|
|
|
29
25
|
|
|
30
26
|
## BaseApp
|
|
31
27
|
|
|
32
|
-
Root provider (theme, auth, analytics, SWR, toasts
|
|
28
|
+
Root provider (theme, auth, analytics, SWR, toasts). Use when you **don't** need route-based layout switching.
|
|
33
29
|
|
|
34
30
|
```tsx
|
|
35
|
-
import { BaseApp } from '@djangocfg/layouts';
|
|
36
|
-
|
|
37
31
|
<BaseApp
|
|
38
32
|
project="my-app"
|
|
39
33
|
theme={{ defaultTheme: 'system', storageKey: 'my-theme' }}
|
|
@@ -44,67 +38,39 @@ import { BaseApp } from '@djangocfg/layouts';
|
|
|
44
38
|
```
|
|
45
39
|
|
|
46
40
|
| Prop | Role |
|
|
47
|
-
|
|
48
|
-
| `project` | Name for monitor / debug |
|
|
49
|
-
| `theme` | `defaultTheme`, `storageKey`, optional **`style`** (
|
|
50
|
-
| `auth`, `analytics`, `centrifugo`, `errorTracking`, `errorBoundary`, `swr`, `pwaInstall`, `monitor`, `debug` | Optional integrations |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `project` | Name for monitor / debug. Enables `window.monitor`. |
|
|
43
|
+
| `theme` | `defaultTheme`, `storageKey`, and optional **`style`** (preset + CSS-var overrides). |
|
|
44
|
+
| `auth`, `analytics`, `centrifugo`, `errorTracking`, `errorBoundary`, `swr`, `pwaInstall`, `monitor`, `debug` | Optional integrations. |
|
|
51
45
|
|
|
52
|
-
###
|
|
46
|
+
### `theme.style`
|
|
53
47
|
|
|
54
|
-
`
|
|
48
|
+
Mounts `ThemeStyleBridge` → injects `<style id="djangocfg-baseapp-theme-style">` with `--*` variables. Merge order: imported globals → preset → `vars` (strongest).
|
|
55
49
|
|
|
56
50
|
| Piece | Meaning |
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
Merge order: **imported globals → preset → `vars`** (strongest).
|
|
62
|
-
|
|
63
|
-
**Presets (summary)**
|
|
51
|
+
|---|---|
|
|
52
|
+
| `preset` | One of `THEME_STYLE_PRESET_ORDER`. Bundles live in `THEME_STYLE_PRESETS`. |
|
|
53
|
+
| `vars.light` / `vars.dark` | Partial `ThemeCssVarMap` — HSL triplets (`192 90% 35%`); `radius` accepts any CSS length. |
|
|
64
54
|
|
|
65
|
-
|
|
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 |
|
|
55
|
+
Presets: `default` · `django-cfg` · `ios` · `soft` · `dense` · `high-contrast`.
|
|
73
56
|
|
|
74
|
-
|
|
57
|
+
Playground-only buckets (shadows, typography, spacing) are **not** injected — export full CSS from the Theme Configurator when you need them.
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
<BaseApp
|
|
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
|
-
}}
|
|
88
|
-
>
|
|
89
|
-
{children}
|
|
90
|
-
</BaseApp>
|
|
91
|
-
```
|
|
59
|
+
Exports: `ThemeStyleConfig`, `ThemeCssVarKey`, `ThemeCssVarMap`, `ThemeStylePresetId`, `THEME_STYLE_PRESETS`, `THEME_STYLE_PRESET_ORDER`, `buildThemeStyleSheet`, `ThemeStyleBridge`.
|
|
92
60
|
|
|
93
61
|
---
|
|
94
62
|
|
|
95
63
|
## AppLayout
|
|
96
64
|
|
|
97
|
-
Wraps
|
|
65
|
+
Wraps `BaseApp` and picks **admin → private → public** layout by path (`matchesPath` / `enabledPath`). `noLayoutPaths` skips the shell entirely (fullscreen / embeds). `publicChrome` merges navbar/footer/main spacing defaults.
|
|
98
66
|
|
|
99
67
|
```tsx
|
|
100
|
-
import { AppLayout } from '@djangocfg/layouts';
|
|
101
|
-
|
|
102
68
|
<AppLayout
|
|
103
69
|
layouts={{
|
|
104
|
-
public:
|
|
70
|
+
public: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
|
|
105
71
|
private: { component: PrivateLayout, enabledPath: ['/dashboard'] },
|
|
106
|
-
admin:
|
|
107
|
-
noLayoutPaths: ['/embed', '/ui'],
|
|
72
|
+
admin: { component: AdminLayout, enabledPath: '/admin' },
|
|
73
|
+
noLayoutPaths: ['/embed', '/ui'],
|
|
108
74
|
}}
|
|
109
75
|
baseApp={{ project: 'my-app', theme: { defaultTheme: 'system' }, auth: { apiUrl: '…' } }}
|
|
110
76
|
i18n={{ locale, locales, onLocaleChange }}
|
|
@@ -113,133 +79,39 @@ import { AppLayout } from '@djangocfg/layouts';
|
|
|
113
79
|
</AppLayout>
|
|
114
80
|
```
|
|
115
81
|
|
|
116
|
-
> **`i18n` is required for correct path matching
|
|
117
|
-
> `AppLayout` strips the locale prefix from the pathname before matching `enabledPath` / `noLayoutPaths`.
|
|
118
|
-
> It uses `i18n.locale` for exact stripping — without it the fallback regex can misfire on
|
|
119
|
-
> 2-letter path segments (e.g. `/ui/*` being treated as locale `ui`).
|
|
120
|
-
> Always pass `i18n` when using next-intl or any locale-prefixed routing.
|
|
82
|
+
> **`i18n` is required** for correct path matching when using `next-intl` or any locale-prefixed routing. Without it the fallback regex can mis-strip 2-letter path segments (e.g. `/ui/*` treated as locale `ui`).
|
|
121
83
|
|
|
122
84
|
---
|
|
123
85
|
|
|
124
|
-
## Layouts
|
|
86
|
+
## Layouts
|
|
125
87
|
|
|
126
88
|
| Component | Use |
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
| **ProfileLayout** | Profile page with avatar, editable fields, 2FA, and slot/tab system |
|
|
134
|
-
|
|
135
|
-
**`PublicLayout` — `backgroundSlot`**
|
|
136
|
-
|
|
137
|
-
Pass any `ReactNode` as `backgroundSlot` to render a full-viewport layer *behind* the sticky navbar and all page content — without affecting layout flow.
|
|
138
|
-
The recommended pattern is `fixed inset-0 -z-10 pointer-events-none`:
|
|
139
|
-
|
|
140
|
-
```tsx
|
|
141
|
-
<PublicLayout
|
|
142
|
-
contentTopSpacing="none"
|
|
143
|
-
contentBottomSpacing="none"
|
|
144
|
-
backgroundSlot={
|
|
145
|
-
<div
|
|
146
|
-
className="pointer-events-none fixed inset-0 -z-10"
|
|
147
|
-
style={{
|
|
148
|
-
background:
|
|
149
|
-
'radial-gradient(ellipse 55% 50% at 10% 0%, rgba(139,92,246,0.13) 0%, transparent 55%),' +
|
|
150
|
-
'radial-gradient(ellipse 40% 30% at 85% 75%, rgba(6,182,212,0.06) 0%, transparent 55%)',
|
|
151
|
-
}}
|
|
152
|
-
/>
|
|
153
|
-
}
|
|
154
|
-
navbar={<PublicNavbar config={…} />}
|
|
155
|
-
>
|
|
156
|
-
<HeroSection />
|
|
157
|
-
</PublicLayout>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Because the element is `fixed`, it covers the full viewport including the sticky navbar area, and `backdrop-blur` on the navbar lets the gradient show through.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
**Brand:** `ThemeBrandMark` / **`ThemeBrandMarkImg`** for logo slots.
|
|
165
|
-
|
|
166
|
-
### PublicNavbar — layout variants & scroll behaviour
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
<PublicNavbar config={{
|
|
170
|
-
brand: <Logo />,
|
|
171
|
-
navigation: navItems,
|
|
172
|
-
userMenu,
|
|
173
|
-
|
|
174
|
-
// Visual
|
|
175
|
-
navbarVariant: 'floating', // 'floating' | 'flush'
|
|
176
|
-
navbarPosition: 'sticky', // 'sticky' | 'fixed' | 'static'
|
|
177
|
-
navbarHeight: 'md', // 'sm' | 'md' | 'lg'
|
|
178
|
-
|
|
179
|
-
// Desktop nav arrangement
|
|
180
|
-
navLayout: 'default',
|
|
181
|
-
// 'default' → brand left | nav centered (absolute) | actions right
|
|
182
|
-
// 'brand-left' → brand left | nav after brand | actions pushed right
|
|
183
|
-
// 'centered' → brand + nav + actions all centered in one row
|
|
184
|
-
// 'split' → brand left | actions right | no desktop nav (drawer only)
|
|
185
|
-
|
|
186
|
-
// Scroll behaviour
|
|
187
|
-
hideNavOnScroll: true, // slide up on scroll-down, back on scroll-up
|
|
188
|
-
transparent: true, // transparent at page top, opaque after threshold
|
|
189
|
-
transparentThreshold: 40, // px, default 40
|
|
190
|
-
}} />
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
**Exported hooks** (for custom navbars):
|
|
194
|
-
|
|
195
|
-
| Hook | Role |
|
|
196
|
-
|------|------|
|
|
197
|
-
| `useNavbarScroll(opts)` | Returns `{ hidden, scrolled }` driven by scroll position |
|
|
198
|
-
| `useDropdownMenu()` | Hover open/close timers for desktop dropdowns |
|
|
199
|
-
| `useNavbarViewportVars(ref, deps)` | Sets `--public-navbar-mobile-drawer-top/max-height` CSS vars |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **`PublicLayout`** | Marketing / docs. Slots for navbar + footer. **[See PublicLayout README](./src/layouts/PublicLayout/README.md)** for full props, navbar variants (`FloatingNavbar` / `FlushNavbar` / `MinimalNavbar`), `DefaultFooter`, `NavAction`, and hooks. |
|
|
91
|
+
| **`PrivateLayout`** | App shell — sidebar + header. |
|
|
92
|
+
| **`AuthLayout`** | Sign-in flows. |
|
|
93
|
+
| **`AdminLayout`** | Admin console. |
|
|
94
|
+
| **`ProfileLayout`** | Profile page — avatar, editable fields, 2FA, tabs, slots (see below). |
|
|
200
95
|
|
|
201
|
-
### ProfileLayout
|
|
96
|
+
### `ProfileLayout`
|
|
202
97
|
|
|
203
98
|
```tsx
|
|
204
|
-
|
|
205
|
-
import type { ProfileTab, ProfileSlots } from '@djangocfg/layouts';
|
|
206
|
-
|
|
207
|
-
const tabs: ProfileTab[] = [
|
|
208
|
-
{
|
|
209
|
-
value: 'billing',
|
|
210
|
-
label: 'Billing',
|
|
211
|
-
content: <BillingSection />,
|
|
212
|
-
},
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
const slots: ProfileSlots = {
|
|
216
|
-
headerBadge: <Badge>Pro</Badge>, // next to user name
|
|
217
|
-
headerMenuItems: <DropdownMenuItem>…</DropdownMenuItem>, // in ⋯ menu
|
|
218
|
-
headerAfter: <OnboardingBanner />, // below avatar, above tabs
|
|
219
|
-
footer: <LinkedAccounts />, // below all tab content
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
<ProfileLayout
|
|
223
|
-
enable2FA
|
|
224
|
-
enableDeleteAccount
|
|
225
|
-
tabs={tabs}
|
|
226
|
-
slots={slots}
|
|
227
|
-
/>
|
|
99
|
+
<ProfileLayout enable2FA enableDeleteAccount tabs={tabs} slots={slots} />
|
|
228
100
|
```
|
|
229
101
|
|
|
230
|
-
| Prop | Type |
|
|
231
|
-
|
|
232
|
-
| `enable2FA` | `boolean` | Show Security tab with 2FA management |
|
|
233
|
-
| `enableDeleteAccount` | `boolean` | Show Delete account in `⋯` menu |
|
|
234
|
-
| `tabs` | `ProfileTab[]` | Extra tabs appended after built-in ones |
|
|
235
|
-
| `slots.headerBadge` | `ReactNode` |
|
|
236
|
-
| `slots.headerMenuItems` | `ReactNode` | Extra `DropdownMenuItem`s in
|
|
237
|
-
| `slots.headerAfter` | `ReactNode` | Below avatar row, above tabs |
|
|
238
|
-
| `slots.footer` | `ReactNode` | Below all tab content |
|
|
102
|
+
| Prop | Type | Role |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `enable2FA` | `boolean` | Show Security tab with 2FA management. |
|
|
105
|
+
| `enableDeleteAccount` | `boolean` | Show Delete account in `⋯` menu. |
|
|
106
|
+
| `tabs` | `ProfileTab[]` | Extra tabs appended after built-in ones. |
|
|
107
|
+
| `slots.headerBadge` | `ReactNode` | Next to user name (plan, role…). |
|
|
108
|
+
| `slots.headerMenuItems` | `ReactNode` | Extra `DropdownMenuItem`s in `⋯` menu. |
|
|
109
|
+
| `slots.headerAfter` | `ReactNode` | Below avatar row, above tabs. |
|
|
110
|
+
| `slots.footer` | `ReactNode` | Below all tab content. |
|
|
239
111
|
|
|
240
112
|
---
|
|
241
113
|
|
|
242
|
-
## i18n
|
|
114
|
+
## i18n
|
|
243
115
|
|
|
244
116
|
```tsx
|
|
245
117
|
import { useLocaleSwitcher } from '@djangocfg/nextjs/i18n/client';
|
|
@@ -252,32 +124,32 @@ const { locale, locales, changeLocale } = useLocaleSwitcher();
|
|
|
252
124
|
|
|
253
125
|
## Monitor & debug
|
|
254
126
|
|
|
255
|
-
`project` on
|
|
127
|
+
`project` on `BaseApp` enables `window.monitor`. Debug panel: `Cmd+D` or `?debug=1`.
|
|
256
128
|
|
|
257
129
|
---
|
|
258
130
|
|
|
259
131
|
## Utilities
|
|
260
132
|
|
|
261
133
|
| API | Role |
|
|
262
|
-
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
134
|
+
|---|---|
|
|
135
|
+
| `useErrorEmitter`, `emitRuntimeError` | Error tracking hooks. |
|
|
136
|
+
| `ErrorLayout` (`/components/errors`) | 404 / error pages. |
|
|
137
|
+
| `RedirectPage` | Auth redirect helper. |
|
|
138
|
+
| `PrivacyPage`, `TermsPage`, … (`/pages/legal`) | Legal templates. |
|
|
267
139
|
|
|
268
140
|
---
|
|
269
141
|
|
|
270
142
|
## Package exports
|
|
271
143
|
|
|
272
144
|
| Path | Contents |
|
|
273
|
-
|
|
274
|
-
| `@djangocfg/layouts` | Barrel |
|
|
275
|
-
| `@djangocfg/layouts/layouts` | Layout components |
|
|
276
|
-
| `@djangocfg/layouts/components` | Misc |
|
|
277
|
-
| `@djangocfg/layouts/pages/legal` | Legal pages |
|
|
278
|
-
| `@djangocfg/layouts/styles` | Base CSS |
|
|
279
|
-
|
|
280
|
-
##
|
|
145
|
+
|---|---|
|
|
146
|
+
| `@djangocfg/layouts` | Barrel. |
|
|
147
|
+
| `@djangocfg/layouts/layouts` | Layout components. |
|
|
148
|
+
| `@djangocfg/layouts/components` | Misc. |
|
|
149
|
+
| `@djangocfg/layouts/pages/legal` | Legal pages. |
|
|
150
|
+
| `@djangocfg/layouts/styles` | Base CSS. |
|
|
151
|
+
|
|
152
|
+
## Extensions
|
|
281
153
|
|
|
282
154
|
`@djangocfg/ext-newsletter`, `ext-knowbase`, `ext-leads`, `ext-payments`, `ext-support`, …
|
|
283
155
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.276",
|
|
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/debuger": "^2.1.
|
|
80
|
-
"@djangocfg/i18n": "^2.1.
|
|
81
|
-
"@djangocfg/monitor": "^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.276",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.276",
|
|
79
|
+
"@djangocfg/debuger": "^2.1.276",
|
|
80
|
+
"@djangocfg/i18n": "^2.1.276",
|
|
81
|
+
"@djangocfg/monitor": "^2.1.276",
|
|
82
|
+
"@djangocfg/ui-core": "^2.1.276",
|
|
83
|
+
"@djangocfg/ui-nextjs": "^2.1.276",
|
|
84
|
+
"@djangocfg/ui-tools": "^2.1.276",
|
|
85
85
|
"@hookform/resolvers": "^5.2.2",
|
|
86
86
|
"consola": "^3.4.2",
|
|
87
87
|
"lucide-react": "^0.545.0",
|
|
@@ -110,15 +110,15 @@
|
|
|
110
110
|
"uuid": "^11.1.0"
|
|
111
111
|
},
|
|
112
112
|
"devDependencies": {
|
|
113
|
-
"@djangocfg/api": "^2.1.
|
|
114
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
115
|
-
"@djangocfg/debuger": "^2.1.
|
|
116
|
-
"@djangocfg/i18n": "^2.1.
|
|
117
|
-
"@djangocfg/monitor": "^2.1.
|
|
118
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
119
|
-
"@djangocfg/ui-core": "^2.1.
|
|
120
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
121
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
113
|
+
"@djangocfg/api": "^2.1.276",
|
|
114
|
+
"@djangocfg/centrifugo": "^2.1.276",
|
|
115
|
+
"@djangocfg/debuger": "^2.1.276",
|
|
116
|
+
"@djangocfg/i18n": "^2.1.276",
|
|
117
|
+
"@djangocfg/monitor": "^2.1.276",
|
|
118
|
+
"@djangocfg/typescript-config": "^2.1.276",
|
|
119
|
+
"@djangocfg/ui-core": "^2.1.276",
|
|
120
|
+
"@djangocfg/ui-nextjs": "^2.1.276",
|
|
121
|
+
"@djangocfg/ui-tools": "^2.1.276",
|
|
122
122
|
"@types/node": "^24.7.2",
|
|
123
123
|
"@types/react": "^19.1.0",
|
|
124
124
|
"@types/react-dom": "^19.1.0",
|
|
@@ -52,8 +52,8 @@ import type {
|
|
|
52
52
|
import type { AuthConfig } from '@djangocfg/api/auth';
|
|
53
53
|
import type { MonitorConfig } from '@djangocfg/monitor';
|
|
54
54
|
|
|
55
|
-
import type {
|
|
56
|
-
import type {
|
|
55
|
+
import type { FloatingNavbarConfig } from '../PublicLayout/navbars/FloatingNavbar';
|
|
56
|
+
import type { DefaultFooterConfig } from '../PublicLayout/footers/DefaultFooter/types';
|
|
57
57
|
|
|
58
58
|
export type LayoutMode = 'public' | 'private' | 'admin';
|
|
59
59
|
|
|
@@ -63,10 +63,10 @@ export type PublicMainTopSpacing = 'auto' | 'none';
|
|
|
63
63
|
/** Bottom padding of `<main>` above the footer. `compact` = less than `auto`; `none` = no extra gap. */
|
|
64
64
|
export type PublicMainBottomSpacing = 'auto' | 'none' | 'compact';
|
|
65
65
|
|
|
66
|
-
/** Shared marketing chrome defaults for `
|
|
66
|
+
/** Shared marketing chrome defaults for `FloatingNavbar` / `DefaultFooter`, merged into your public layout. */
|
|
67
67
|
export interface AppLayoutPublicChrome {
|
|
68
|
-
navbar?: Partial<
|
|
69
|
-
footer?: Partial<
|
|
68
|
+
navbar?: Partial<FloatingNavbarConfig>;
|
|
69
|
+
footer?: Partial<DefaultFooterConfig>;
|
|
70
70
|
/**
|
|
71
71
|
* Passed through to `PublicLayout` as `contentTopSpacing` / `contentBottomSpacing`.
|
|
72
72
|
* Use `bottomSpacing: 'none'` when the page (or footer) should sit flush with no default gap.
|
|
@@ -86,11 +86,11 @@ export interface AppLayoutPublicChrome {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function mergePartialNavbar(
|
|
89
|
-
base?: Partial<
|
|
90
|
-
overlay?: Partial<
|
|
91
|
-
): Partial<
|
|
89
|
+
base?: Partial<FloatingNavbarConfig>,
|
|
90
|
+
overlay?: Partial<FloatingNavbarConfig>
|
|
91
|
+
): Partial<FloatingNavbarConfig> | undefined {
|
|
92
92
|
if (!base && !overlay) return undefined;
|
|
93
|
-
const merged: Partial<
|
|
93
|
+
const merged: Partial<FloatingNavbarConfig> = { ...base, ...overlay };
|
|
94
94
|
if (base?.shell || overlay?.shell) {
|
|
95
95
|
merged.shell = { ...base?.shell, ...overlay?.shell };
|
|
96
96
|
}
|
|
@@ -98,11 +98,11 @@ function mergePartialNavbar(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function mergePartialFooter(
|
|
101
|
-
base?: Partial<
|
|
102
|
-
overlay?: Partial<
|
|
103
|
-
): Partial<
|
|
101
|
+
base?: Partial<DefaultFooterConfig>,
|
|
102
|
+
overlay?: Partial<DefaultFooterConfig>
|
|
103
|
+
): Partial<DefaultFooterConfig> | undefined {
|
|
104
104
|
if (!base && !overlay) return undefined;
|
|
105
|
-
const merged: Partial<
|
|
105
|
+
const merged: Partial<DefaultFooterConfig> = { ...base, ...overlay };
|
|
106
106
|
if (base?.shell || overlay?.shell) merged.shell = { ...base?.shell, ...overlay?.shell };
|
|
107
107
|
if (base?.brand || overlay?.brand) merged.brand = { ...base?.brand, ...overlay?.brand };
|
|
108
108
|
if (base?.menus || overlay?.menus) merged.menus = { ...base?.menus, ...overlay?.menus };
|
|
@@ -164,7 +164,7 @@ export interface I18nLayoutConfig {
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Props passed to every layout component (`public` / `private` / `admin`).
|
|
167
|
-
* Use `publicChrome` to pass defaults for `
|
|
167
|
+
* Use `publicChrome` to pass defaults for `FloatingNavbar` / `DefaultFooter` from `AppLayout`.
|
|
168
168
|
*/
|
|
169
169
|
export interface AppLayoutLayoutComponentProps {
|
|
170
170
|
children: ReactNode;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# PublicLayout
|
|
2
|
+
|
|
3
|
+
Slots-only shell for marketing / docs pages. You bring the navbar + footer; it provides the frame, mobile-drawer context, and main-content top/bottom spacing.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { PublicLayout, FloatingNavbar, DefaultFooter } from '@djangocfg/layouts';
|
|
7
|
+
|
|
8
|
+
<PublicLayout
|
|
9
|
+
navbar={<FloatingNavbar config={{ brand, navigation, userMenu, actions }} />}
|
|
10
|
+
footer={<DefaultFooter config={{ variant: 'full', menus: { sections }, i18n }} />}
|
|
11
|
+
>
|
|
12
|
+
{children}
|
|
13
|
+
</PublicLayout>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## `PublicLayout`
|
|
17
|
+
|
|
18
|
+
| Prop | Type | Default | Role |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `navbar` | `ReactNode` | — | Pick a variant (below) or drop in your own. |
|
|
21
|
+
| `footer` | `ReactNode` | — | Usually `DefaultFooter`. |
|
|
22
|
+
| `backgroundSlot` | `ReactNode` | — | Full-viewport layer behind navbar + content. Use `fixed inset-0 -z-10 pointer-events-none`. |
|
|
23
|
+
| `contentTopSpacing` | `'auto' \| 'none'` | `'auto'` | Auto pads `<main>` based on the navbar variant. |
|
|
24
|
+
| `contentBottomSpacing` | `'auto' \| 'none' \| 'compact'` | `'auto'` | Bottom breathing room before footer. |
|
|
25
|
+
|
|
26
|
+
## Navbar variants
|
|
27
|
+
|
|
28
|
+
Three variants. All share the same core props (below); only the chrome differs.
|
|
29
|
+
|
|
30
|
+
| Variant | Chrome |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `FloatingNavbar` | Rounded, inset, soft shadow. `sticky top-3`. Default. |
|
|
33
|
+
| `FlushNavbar` | Edge-to-edge, bottom border, no rounding. |
|
|
34
|
+
| `MinimalNavbar` | Borderless, transparent-by-default (eesel-style), full-bleed. |
|
|
35
|
+
|
|
36
|
+
### Shared config (all variants)
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Role |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `brand` | `ReactNode` | — | Logo slot. |
|
|
41
|
+
| `brandHref` | `string` | `'/'` | Link target for brand. |
|
|
42
|
+
| `navigation` | `NavigationItem[]` | `[]` | Primary items. Overflow collapses into a responsive "More" dropdown automatically. |
|
|
43
|
+
| `userMenu` | `UserMenuConfig` | — | Groups + `authPath` + optional `i18n`. |
|
|
44
|
+
| `actions` | `NavAction[]` | — | CTA pills on the right (before UserMenu). See `NavAction` below. |
|
|
45
|
+
| `actionsLeadingSlot` | `ReactNode` | — | Arbitrary node between actions and UserMenu. |
|
|
46
|
+
| `actionsTrailingSlot` | `ReactNode` | — | Arbitrary node after the mobile toggle. |
|
|
47
|
+
| `navbarPosition` | `'sticky' \| 'fixed' \| 'static'` | `'sticky'` | |
|
|
48
|
+
| `navbarHeight` | `'sm' \| 'md' \| 'lg'` | `'md'` | `py-2` / `py-3.5` / `py-5`. |
|
|
49
|
+
| `navLayout` | `'default' \| 'brand-left' \| 'centered' \| 'split'` | `'default'` | Desktop arrangement. |
|
|
50
|
+
| `hideNavOnScroll` | `boolean` | `false` | Slide up on scroll-down, back on scroll-up. |
|
|
51
|
+
| `transparent` | `boolean` | `false` (Minimal: `true`) | Transparent at top, opaque after `transparentThreshold`. |
|
|
52
|
+
| `transparentThreshold` | `number` | `40` | Px past which the nav becomes opaque. |
|
|
53
|
+
| `desktopMaxPrimaryItems` | `number` | auto | Hard cap for primary items before overflow. |
|
|
54
|
+
| `renderDesktopDropdown` | `(ctx) => ReactNode` | — | Replace default popover per-item. |
|
|
55
|
+
|
|
56
|
+
### Variant-only
|
|
57
|
+
|
|
58
|
+
| Variant | Extra props |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `FloatingNavbar` | `shell?: { rounding?: string; className?: string }` |
|
|
61
|
+
| `FlushNavbar` | `shell?: { rounding?: string; className?: string }` |
|
|
62
|
+
| `MinimalNavbar` | `containerClassName?: string` (default `mx-auto max-w-[1400px] px-4 sm:px-6 lg:px-10`) |
|
|
63
|
+
|
|
64
|
+
### `navLayout` variants
|
|
65
|
+
|
|
66
|
+
- `default` — brand left | nav centered (flex-1) | actions right.
|
|
67
|
+
- `brand-left` — brand + nav flow left-to-right, actions pushed right.
|
|
68
|
+
- `centered` — all three groups centered in one row.
|
|
69
|
+
- `split` — brand left, actions right, **no desktop nav** (drawer only).
|
|
70
|
+
|
|
71
|
+
## `NavAction`
|
|
72
|
+
|
|
73
|
+
Typed pill used by every navbar's `actions`.
|
|
74
|
+
|
|
75
|
+
| Field | Type | Role |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `label` | `string` | |
|
|
78
|
+
| `href` | `string` | |
|
|
79
|
+
| `external` | `boolean` | Opens in new tab. |
|
|
80
|
+
| `variant` | `'link' \| 'ghost' \| 'outline' \| 'primary'` | Visual style. Default `ghost`. |
|
|
81
|
+
| `hideOnSmall` | `boolean` | Hide below `lg`. |
|
|
82
|
+
| `icon` | `ReactNode` | Optional leading icon. |
|
|
83
|
+
| `onClick` | `(e) => void` | Extra handler; navigation still follows `href`. |
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
actions={[
|
|
87
|
+
{ label: 'Book a demo', href: '/demo', variant: 'ghost', hideOnSmall: true },
|
|
88
|
+
{ label: 'Get started', href: '/signup', variant: 'primary' },
|
|
89
|
+
]}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## `DefaultFooter`
|
|
93
|
+
|
|
94
|
+
Three variants: `full` (default) with brand column + menus + controls; `compact` (one row + bottom); `simple` (copyright only).
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<DefaultFooter config={{ variant: 'full', menus: { sections }, i18n, slots }} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Prop (`config.*`) | Type | Default | Role |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `variant` | `'full' \| 'compact' \| 'simple'` | `'full'` | |
|
|
103
|
+
| `shell.className` | `string` | — | Outer container (e.g. `mx-auto max-w-7xl`). |
|
|
104
|
+
| `brand.slot` | `ReactNode` | — | Replace the brand column. |
|
|
105
|
+
| `brand.description` | `string` | — | Subtitle under brand. |
|
|
106
|
+
| `brand.badge` | `{ icon: LucideIcon; text: string }` | — | Optional badge next to brand. |
|
|
107
|
+
| `brand.showColumn` | `boolean` | `true` | Hide brand column entirely. |
|
|
108
|
+
| `menus.sections` | `FooterMenuSection[]` | `[]` | Link columns. |
|
|
109
|
+
| `menus.columnMinWidth` | `number` | `180` | Auto-fit grid min width. |
|
|
110
|
+
| `menus.maxColumns` | `number` | `5` | |
|
|
111
|
+
| `links` | `FooterLink[]` | `[]` | Bottom-row inline links (privacy / terms / …). |
|
|
112
|
+
| `social` | `FooterSocialLinks` | — | Social icon row under brand. |
|
|
113
|
+
| `meta.copyright` | `string` | `© ${year}. All rights reserved.` | |
|
|
114
|
+
| `meta.credits` | `{ text: string; url?: string }` | — | |
|
|
115
|
+
| `i18n` | `{ locale, locales, onLocaleChange }` | — | Required for the locale switcher. |
|
|
116
|
+
| `controls.showThemeSwitcher` | `boolean` | `true` | Full variant only. |
|
|
117
|
+
| `controls.showLocaleSwitcher` | `boolean` | `true` (if `i18n`) | Full variant only. |
|
|
118
|
+
| `slots.aboveMenus` | `ReactNode` | — | Above the brand/menus grid (full variant). |
|
|
119
|
+
| `slots.belowMenus` | `ReactNode` | — | Between menus and bottom row. |
|
|
120
|
+
| `slots.bottomStart` | `ReactNode` | — | Next to copyright. |
|
|
121
|
+
| `slots.bottomEnd` | `ReactNode` | — | Next to theme/locale controls. |
|
|
122
|
+
|
|
123
|
+
## Hooks (custom navbars)
|
|
124
|
+
|
|
125
|
+
| Hook | Role |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `useNavbarScroll({ hideNavOnScroll, transparent, transparentThreshold })` | `{ hidden, scrolled }` |
|
|
128
|
+
| `useDropdownMenu()` | Hover-open/close timers for desktop dropdowns. |
|
|
129
|
+
| `useNavbarViewportVars(ref, deps)` | Sets `--public-navbar-mobile-drawer-top / max-height` CSS vars. |
|
|
130
|
+
| `useResponsiveOverflow({ total, moreWidth, gap, minVisible })` | Dynamic "fit-or-overflow" for nav items (SSR-safe). |
|
|
131
|
+
|
|
132
|
+
## Primitives
|
|
133
|
+
|
|
134
|
+
`NavBrand`, `NavActions`, `NavActionItem`, `NavDesktopItems`, `ThemeBrandMark`, `ThemeBrandMarkImg`, `PublicLayoutProvider`, `usePublicLayout`.
|
|
135
|
+
|
|
136
|
+
## Context
|
|
137
|
+
|
|
138
|
+
`PublicLayoutProvider` is mounted by `PublicLayout`. Access via `usePublicLayout()`:
|
|
139
|
+
|
|
140
|
+
| Field | Role |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `mobileMenuOpen` / `toggleMobileMenu` / `closeMobileMenu` | Mobile drawer state. |
|
|
143
|
+
| `navbarSurface` | `{ variant, position }` reported by the mounted navbar. |
|
|
144
|
+
| `setNavbarSurface` | Called by navbars on mount/unmount — don't call yourself. |
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public
|
|
2
|
+
* Default Public Footer
|
|
3
3
|
*
|
|
4
|
-
* Professional, flexible footer
|
|
5
|
-
* Supports desktop/mobile responsive layouts, social links, menu sections
|
|
4
|
+
* Professional, flexible footer with full / compact / simple variants.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
'use client';
|
|
@@ -18,7 +17,7 @@ import { LocaleSwitcher } from '../../../_components/LocaleSwitcher';
|
|
|
18
17
|
import { FooterMenuSections } from './FooterMenuSections';
|
|
19
18
|
import { FooterProjectInfo } from './FooterProjectInfo';
|
|
20
19
|
|
|
21
|
-
import type {
|
|
20
|
+
import type { DefaultFooterProps } from './types';
|
|
22
21
|
|
|
23
22
|
function ThemeModeControl() {
|
|
24
23
|
const { theme, setTheme } = useThemeContext();
|
|
@@ -69,7 +68,7 @@ function ThemeModeControl() {
|
|
|
69
68
|
);
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
export function
|
|
71
|
+
export function DefaultFooter({ config }: DefaultFooterProps) {
|
|
73
72
|
const variant = config.variant ?? 'full';
|
|
74
73
|
const shellClass = config.shell?.className;
|
|
75
74
|
const brandSlot = config.brand?.slot;
|
|
@@ -88,6 +87,7 @@ export function PublicFooter({ config }: PublicFooterProps) {
|
|
|
88
87
|
const showLocaleSwitcher =
|
|
89
88
|
config.controls?.showLocaleSwitcher !== false && Boolean(i18n);
|
|
90
89
|
const showFooterControlsRow = showThemeSwitcher || showLocaleSwitcher;
|
|
90
|
+
const slots = config.slots;
|
|
91
91
|
|
|
92
92
|
const currentYear = new Date().getFullYear();
|
|
93
93
|
const copyright = copyrightProp || `© ${currentYear}. All rights reserved.`;
|
|
@@ -241,6 +241,8 @@ export function PublicFooter({ config }: PublicFooterProps) {
|
|
|
241
241
|
</footer>
|
|
242
242
|
<footer className={`max-lg:hidden ${footerSurfaceClass} ${footerSafeBottom}`}>
|
|
243
243
|
<div className={`mx-auto px-6 sm:px-8 lg:px-10 py-14 ${shellClass || 'w-full'}`}>
|
|
244
|
+
{slots?.aboveMenus && <div className="mb-10">{slots.aboveMenus}</div>}
|
|
245
|
+
|
|
244
246
|
<div className="grid grid-cols-12 gap-10 lg:gap-14">
|
|
245
247
|
{showBrandColumn && (
|
|
246
248
|
<div className="col-span-12 lg:col-span-4">
|
|
@@ -264,6 +266,8 @@ export function PublicFooter({ config }: PublicFooterProps) {
|
|
|
264
266
|
</div>
|
|
265
267
|
</div>
|
|
266
268
|
|
|
269
|
+
{slots?.belowMenus && <div className="mt-10">{slots.belowMenus}</div>}
|
|
270
|
+
|
|
267
271
|
<div
|
|
268
272
|
className={
|
|
269
273
|
showFooterControlsRow
|
|
@@ -271,8 +275,9 @@ export function PublicFooter({ config }: PublicFooterProps) {
|
|
|
271
275
|
: 'mt-12 flex flex-col gap-4 border-t border-border/60 pt-5 lg:flex-row lg:items-center lg:justify-between'
|
|
272
276
|
}
|
|
273
277
|
>
|
|
274
|
-
<div className="text-center text-xs text-muted-foreground lg:
|
|
275
|
-
{copyright}
|
|
278
|
+
<div className="flex min-w-0 flex-wrap items-center justify-center gap-3 text-center text-xs text-muted-foreground lg:justify-self-start lg:text-left">
|
|
279
|
+
<span className="whitespace-nowrap">{copyright}</span>
|
|
280
|
+
{slots?.bottomStart}
|
|
276
281
|
</div>
|
|
277
282
|
|
|
278
283
|
<div className="flex min-w-0 flex-wrap items-center justify-center gap-3 text-center text-xs text-muted-foreground lg:justify-self-center">
|
|
@@ -304,8 +309,9 @@ export function PublicFooter({ config }: PublicFooterProps) {
|
|
|
304
309
|
)}
|
|
305
310
|
</div>
|
|
306
311
|
|
|
307
|
-
{showFooterControlsRow && (
|
|
312
|
+
{(showFooterControlsRow || slots?.bottomEnd) && (
|
|
308
313
|
<div className="flex items-center justify-center gap-2 lg:justify-self-end">
|
|
314
|
+
{slots?.bottomEnd}
|
|
309
315
|
{showThemeSwitcher && <ThemeModeControl />}
|
|
310
316
|
{showLocaleSwitcher && i18n && (
|
|
311
317
|
<LocaleSwitcher
|