@djangocfg/layouts 2.1.275 → 2.1.277

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.
Files changed (42) hide show
  1. package/README.md +52 -180
  2. package/package.json +18 -18
  3. package/src/layouts/AppLayout/AppLayout.tsx +14 -14
  4. package/src/layouts/PublicLayout/README.md +144 -0
  5. package/src/layouts/PublicLayout/{components/PublicFooter/PublicFooter.tsx → footers/DefaultFooter/DefaultFooter.tsx} +21 -15
  6. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/DjangoCFGLogo.tsx +0 -6
  7. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterBottom.tsx +3 -7
  8. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterMenuSections.tsx +4 -7
  9. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterProjectInfo.tsx +0 -4
  10. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterSocialLinks.tsx +0 -5
  11. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/index.ts +2 -12
  12. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/types.ts +21 -26
  13. package/src/layouts/PublicLayout/footers/index.ts +1 -0
  14. package/src/layouts/PublicLayout/hooks/index.ts +1 -0
  15. package/src/layouts/PublicLayout/hooks/useResponsiveOverflow.ts +140 -0
  16. package/src/layouts/PublicLayout/index.ts +38 -20
  17. package/src/layouts/PublicLayout/navbarTypes.ts +27 -4
  18. package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingMobileDrawer.tsx +29 -0
  19. package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +127 -0
  20. package/src/layouts/PublicLayout/navbars/FloatingNavbar/index.ts +3 -0
  21. package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushMobileDrawer.tsx +19 -0
  22. package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushNavbar.tsx +122 -0
  23. package/src/layouts/PublicLayout/navbars/FlushNavbar/index.ts +3 -0
  24. package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalMobileDrawer.tsx +19 -0
  25. package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +180 -0
  26. package/src/layouts/PublicLayout/navbars/MinimalNavbar/index.ts +3 -0
  27. package/src/layouts/PublicLayout/navbars/index.ts +3 -0
  28. package/src/layouts/PublicLayout/primitives/ExternalPrefixesContext.tsx +69 -0
  29. package/src/layouts/PublicLayout/primitives/NavActionItem.tsx +95 -0
  30. package/src/layouts/PublicLayout/{components → primitives}/NavActions.tsx +26 -1
  31. package/src/layouts/PublicLayout/{components → primitives}/NavBrand.tsx +4 -3
  32. package/src/layouts/PublicLayout/{components → primitives}/NavDesktopItems.tsx +105 -61
  33. package/src/layouts/PublicLayout/primitives/SmartNavLink.tsx +81 -0
  34. package/src/layouts/PublicLayout/{components → primitives}/ThemeBrandMark.tsx +0 -8
  35. package/src/layouts/PublicLayout/primitives/index.ts +18 -0
  36. package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +205 -0
  37. package/src/layouts/PublicLayout/shared/NavbarShell.tsx +295 -0
  38. package/src/layouts/PublicLayout/shared/index.ts +4 -0
  39. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +0 -211
  40. package/src/layouts/PublicLayout/components/PublicNavbar.tsx +0 -99
  41. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +0 -287
  42. package/src/layouts/PublicLayout/components/index.ts +0 -11
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
- /* Before @import "tailwindcss" */
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, …). Use when you **do not** need route-based layout switching.
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`** (see below) |
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
- ### Theme: `theme.style` + `ThemeStyleBridge`
46
+ ### `theme.style`
53
47
 
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`).
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
- | **`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`, …) |
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
- | 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 |
55
+ Presets: `default` · `django-cfg` · `ios` · `soft` · `dense` · `high-contrast`.
73
56
 
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.
57
+ Playground-only buckets (shadows, typography, spacing) are **not** injected — export full CSS from the Theme Configurator when you need them.
75
58
 
76
- **Exports:** `ThemeStyleConfig`, `ThemeCssVarKey`, `ThemeCssVarMap`, `ThemeStylePresetId`, `THEME_STYLE_PRESETS`, `THEME_STYLE_PRESET_ORDER`, `buildThemeStyleSheet`, `ThemeStyleBridge`.
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 **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`**).
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: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
70
+ public: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
105
71
  private: { component: PrivateLayout, enabledPath: ['/dashboard'] },
106
- admin: { component: AdminLayout, enabledPath: '/admin' },
107
- noLayoutPaths: ['/embed', '/ui'], // fullscreen, no navbar/footer
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 (import from `@djangocfg/layouts`)
86
+ ## Layouts
125
87
 
126
88
  | Component | Use |
127
- |-----------|-----|
128
- | **PublicLayout** | Marketing / docs slots: **`navbar`**, **`footer`**, **`backgroundSlot`**, **`contentTopSpacing`**, **`contentBottomSpacing`** |
129
- | **PublicNavbar** / **PublicFooter** | See `PublicNavbarConfig` below |
130
- | **PrivateLayout** | App shell — sidebar + header |
131
- | **AuthLayout** | Sign-in flows |
132
- | **AdminLayout** | Admin console |
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 — slots & tabs
96
+ ### `ProfileLayout`
202
97
 
203
98
  ```tsx
204
- import { ProfileLayout } from '@djangocfg/layouts';
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 | Description |
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` | Rendered next to the user name (plan, role…) |
236
- | `slots.headerMenuItems` | `ReactNode` | Extra `DropdownMenuItem`s in the `⋯` menu |
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 on AppLayout
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 **BaseApp** enables **`window.monitor`**. **Debug:** `Cmd+D` or `?debug=1`.
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
- | **useErrorEmitter**, **emitRuntimeError** | Error tracking hooks |
264
- | **ErrorLayout** (`/components/errors`) | 404 / error pages |
265
- | **RedirectPage** | Auth redirect helper |
266
- | **PrivacyPage**, **TermsPage**, … (`/pages/legal`) | Legal templates |
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
- ## Extension packages
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.275",
3
+ "version": "2.1.277",
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.275",
78
- "@djangocfg/centrifugo": "^2.1.275",
79
- "@djangocfg/debuger": "^2.1.275",
80
- "@djangocfg/i18n": "^2.1.275",
81
- "@djangocfg/monitor": "^2.1.275",
82
- "@djangocfg/ui-core": "^2.1.275",
83
- "@djangocfg/ui-nextjs": "^2.1.275",
84
- "@djangocfg/ui-tools": "^2.1.275",
77
+ "@djangocfg/api": "^2.1.277",
78
+ "@djangocfg/centrifugo": "^2.1.277",
79
+ "@djangocfg/debuger": "^2.1.277",
80
+ "@djangocfg/i18n": "^2.1.277",
81
+ "@djangocfg/monitor": "^2.1.277",
82
+ "@djangocfg/ui-core": "^2.1.277",
83
+ "@djangocfg/ui-nextjs": "^2.1.277",
84
+ "@djangocfg/ui-tools": "^2.1.277",
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.275",
114
- "@djangocfg/centrifugo": "^2.1.275",
115
- "@djangocfg/debuger": "^2.1.275",
116
- "@djangocfg/i18n": "^2.1.275",
117
- "@djangocfg/monitor": "^2.1.275",
118
- "@djangocfg/typescript-config": "^2.1.275",
119
- "@djangocfg/ui-core": "^2.1.275",
120
- "@djangocfg/ui-nextjs": "^2.1.275",
121
- "@djangocfg/ui-tools": "^2.1.275",
113
+ "@djangocfg/api": "^2.1.277",
114
+ "@djangocfg/centrifugo": "^2.1.277",
115
+ "@djangocfg/debuger": "^2.1.277",
116
+ "@djangocfg/i18n": "^2.1.277",
117
+ "@djangocfg/monitor": "^2.1.277",
118
+ "@djangocfg/typescript-config": "^2.1.277",
119
+ "@djangocfg/ui-core": "^2.1.277",
120
+ "@djangocfg/ui-nextjs": "^2.1.277",
121
+ "@djangocfg/ui-tools": "^2.1.277",
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 { PublicNavbarConfig } from '../PublicLayout/components/PublicNavbar';
56
- import type { PublicFooterConfig } from '../PublicLayout/components/PublicFooter/types';
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 `PublicNavbar` / `PublicFooter`, merged into your public layout. */
66
+ /** Shared marketing chrome defaults for `FloatingNavbar` / `DefaultFooter`, merged into your public layout. */
67
67
  export interface AppLayoutPublicChrome {
68
- navbar?: Partial<PublicNavbarConfig>;
69
- footer?: Partial<PublicFooterConfig>;
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<PublicNavbarConfig>,
90
- overlay?: Partial<PublicNavbarConfig>
91
- ): Partial<PublicNavbarConfig> | undefined {
89
+ base?: Partial<FloatingNavbarConfig>,
90
+ overlay?: Partial<FloatingNavbarConfig>
91
+ ): Partial<FloatingNavbarConfig> | undefined {
92
92
  if (!base && !overlay) return undefined;
93
- const merged: Partial<PublicNavbarConfig> = { ...base, ...overlay };
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<PublicFooterConfig>,
102
- overlay?: Partial<PublicFooterConfig>
103
- ): Partial<PublicFooterConfig> | undefined {
101
+ base?: Partial<DefaultFooterConfig>,
102
+ overlay?: Partial<DefaultFooterConfig>
103
+ ): Partial<DefaultFooterConfig> | undefined {
104
104
  if (!base && !overlay) return undefined;
105
- const merged: Partial<PublicFooterConfig> = { ...base, ...overlay };
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 `PublicNavbar` / `PublicFooter` from `AppLayout`.
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. |