@djangocfg/layouts 2.1.319 → 2.1.321

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 CHANGED
@@ -23,115 +23,68 @@ Peers: `@djangocfg/ui-core`, `@djangocfg/ui-nextjs`, React 19, Next.js 16+, Tail
23
23
 
24
24
  ---
25
25
 
26
- ## BaseApp
26
+ ## Quick start
27
27
 
28
- Root provider (theme, auth, analytics, SWR, toasts). Use when you **don't** need route-based layout switching.
29
-
30
- ```tsx
31
- <BaseApp
32
- project="my-app"
33
- theme={{ defaultTheme: 'system', storageKey: 'my-theme' }}
34
- auth={{ apiUrl: process.env.NEXT_PUBLIC_API_URL }}
35
- >
36
- {children}
37
- </BaseApp>
38
- ```
39
-
40
- | Prop | Role |
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. |
45
-
46
- ### `theme.style`
47
-
48
- Mounts `ThemeStyleBridge` → injects `<style id="djangocfg-baseapp-theme-style">` with `--*` variables. Merge order: imported globals → preset → `vars` (strongest).
49
-
50
- | Piece | Meaning |
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. |
54
-
55
- Presets: `default` · `django-cfg` · `ios` · `soft` · `dense` · `high-contrast`.
56
-
57
- Playground-only buckets (shadows, typography, spacing) are **not** injected — export full CSS from the Theme Configurator when you need them.
58
-
59
- Exports: `ThemeStyleConfig`, `ThemeCssVarKey`, `ThemeCssVarMap`, `ThemeStylePresetId`, `THEME_STYLE_PRESETS`, `THEME_STYLE_PRESET_ORDER`, `buildThemeStyleSheet`, `ThemeStyleBridge`.
60
-
61
- ---
62
-
63
- ## AppLayout
64
-
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.
28
+ `AppLayout` wraps `BaseApp` (theme, auth, analytics, SWR, toasts) and routes the page to the right shell based on path:
66
29
 
67
30
  ```tsx
68
31
  <AppLayout
69
32
  layouts={{
70
- public: { component: PublicLayout, enabledPath: ['/', '/legal', '/contact'] },
33
+ public: { component: PublicLayout, enabledPath: ['/', '/legal'] },
71
34
  private: { component: PrivateLayout, enabledPath: ['/dashboard'] },
72
35
  admin: { component: AdminLayout, enabledPath: '/admin' },
73
- noLayoutPaths: ['/embed', '/ui'],
36
+ noLayoutPaths: ['/embed'],
74
37
  }}
75
38
  baseApp={{ project: 'my-app', theme: { defaultTheme: 'system' }, auth: { apiUrl: '…' } }}
76
- i18n={{ locale, locales, onLocaleChange }}
39
+ i18n={{ locale, locales, onLocaleChange: changeLocale, routing }}
77
40
  >
78
41
  {children}
79
42
  </AppLayout>
80
43
  ```
81
44
 
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`).
45
+ Use `BaseApp` directly when you don't need route-based layout switching see [AppLayout README](./src/layouts/AppLayout/README.md) for the matching rules, `noLayoutPaths`, `publicChrome`, and `i18n.routing` plumbing.
46
+
47
+ > **Pass `i18n`** when using `next-intl` or any locale-prefixed routing. Without it, the path matcher can mis-strip 2-letter segments (e.g. `/ui/*` treated as locale `ui`).
83
48
 
84
49
  ---
85
50
 
86
51
  ## Layouts
87
52
 
88
- | Component | Use |
89
- |---|---|
90
- | **`PublicLayout`** | Marketing / docs. Slots for navbar + footer. All anchors render through `<Link>` from `@djangocfg/ui-core/components` — wrap with `LinkProvider` higher in the tree to inject a locale-aware Link (e.g. `next-intl`). All three navbars accept a `controls` block to show theme / locale switchers next to `UserMenu`; locale data flows from `LayoutI18nProvider` (mounted by `AppLayout` / `BaseApp` when you pass `i18n`). The locale switcher renders SVG country flags and matches the `UserMenu` avatar size at `size="icon"`. **[See PublicLayout README](./src/layouts/PublicLayout/README.md)** for full props, navbar variants (`FloatingNavbar` / `FlushNavbar` / `MinimalNavbar`), `DefaultFooter`, `NavAction`, `NavControls`, and hooks. |
91
- | **`PrivateLayout`** | App shell — sidebar + header. Defaults to the `boxed` visual (inset rounded card on a sidebar-coloured canvas); pass `visual={{ variant: 'full-bleed' }}` for the legacy edge-to-edge layout. |
92
- | **`AuthLayout`** | Sign-in flows. |
93
- | **`AdminLayout`** | Admin console. |
94
- | **`ProfileLayout`** | Profile page — avatar, editable fields, 2FA, tabs, slots (see below). |
53
+ | Component | Use | Docs |
54
+ |---|---|---|
55
+ | **`PublicLayout`** | Marketing / docs. Slots for navbar (`Floating`/`Flush`/`Minimal`) + footer + locale + auth controls. | [README](./src/layouts/PublicLayout/README.md) |
56
+ | **`PrivateLayout`** | Authenticated app shell — sidebar (collapsible icon rail, accordion groups, rail/featured/CTA slots) + popover account footer. | [README](./src/layouts/PrivateLayout/README.md) |
57
+ | **`AuthLayout`** | Sign-in / sign-up flows. | [README](./src/layouts/AuthLayout/README.md) |
58
+ | **`AdminLayout`** | Admin console. | — |
59
+ | **`ProfileLayout`** | Profile page — see below. | |
95
60
 
96
- ### `PrivateLayout` visual variants
61
+ ### `ProfileLayout`
97
62
 
98
63
  ```tsx
99
- <PrivateLayout
100
- sidebar={sidebar}
101
- header={header}
102
- visual={{ variant: 'boxed', inset: 12, radius: '2xl', border: true }}
103
- >
104
- {children}
105
- </PrivateLayout>
64
+ <ProfileLayout enable2FA enableDeleteAccount tabs={tabs} slots={slots} />
106
65
  ```
107
66
 
108
- `boxed` (default) `<SidebarInset>` becomes a rounded card; the wrapper paints `bg-sidebar` so the brand colour bleeds to the viewport edges. Mobile (<md) degrades to full-bleed automatically.
109
- `full-bleed` — content stretches edge-to-edge next to the sidebar (legacy look). Opt in with `visual={{ variant: 'full-bleed' }}`.
67
+ | Prop | Role |
68
+ |---|---|
69
+ | `enable2FA` | Show Security tab with 2FA management. |
70
+ | `enableDeleteAccount` | Show Delete account in `⋯` menu. |
71
+ | `tabs` | Extra `ProfileTab[]` appended after built-ins. |
72
+ | `slots.headerBadge` / `headerMenuItems` / `headerAfter` / `footer` | Slot content around the avatar row, menu, and tab body. |
110
73
 
111
- | Field | Type | Default | Notes |
112
- |---|---|---|---|
113
- | `variant` | `'full-bleed' \| 'boxed'` | `'boxed'` | Switch between the two shells. |
114
- | `inset` | `number \| { x?: number; y?: number }` | `12` | Gap (px) between the card and the viewport edges (md+). |
115
- | `radius` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' \| '3xl'` | `'2xl'` | Card corner radius. |
116
- | `background` | `'sidebar' \| 'muted' \| 'card' \| 'background'` | `'sidebar'` | Canvas colour painted *behind* the boxed card. |
117
- | `border` | `boolean` | `true` | 1px border on the card. |
118
- | `maxWidth` | `'none' \| '7xl' \| 'screen-xl' \| 'screen-2xl'` | `'none'` | Optional inner content width cap. |
74
+ ---
119
75
 
120
- ### `ProfileLayout`
76
+ ## Theme
121
77
 
122
- ```tsx
123
- <ProfileLayout enable2FA enableDeleteAccount tabs={tabs} slots={slots} />
124
- ```
78
+ `baseApp.theme.style` injects `<style id="djangocfg-baseapp-theme-style">` with `--*` CSS variables. Merge order: imported globals → preset → `vars` (strongest).
125
79
 
126
- | Prop | Type | Role |
127
- |---|---|---|
128
- | `enable2FA` | `boolean` | Show Security tab with 2FA management. |
129
- | `enableDeleteAccount` | `boolean` | Show Delete account in `⋯` menu. |
130
- | `tabs` | `ProfileTab[]` | Extra tabs appended after built-in ones. |
131
- | `slots.headerBadge` | `ReactNode` | Next to user name (plan, role…). |
132
- | `slots.headerMenuItems` | `ReactNode` | Extra `DropdownMenuItem`s in `⋯` menu. |
133
- | `slots.headerAfter` | `ReactNode` | Below avatar row, above tabs. |
134
- | `slots.footer` | `ReactNode` | Below all tab content. |
80
+ | Piece | Meaning |
81
+ |---|---|
82
+ | `preset` | `default` · `django-cfg` · `ios` · `soft` · `dense` · `high-contrast`. |
83
+ | `vars.light` / `vars.dark` | Partial `ThemeCssVarMap` HSL triplets (`192 90% 35%`); `radius` accepts any CSS length. |
84
+
85
+ Playground-only buckets (shadows, typography, spacing) are **not** injected export full CSS from the Theme Configurator when you need them.
86
+
87
+ Exports: `ThemeStyleConfig`, `ThemeCssVarKey`, `ThemeCssVarMap`, `ThemeStylePresetId`, `THEME_STYLE_PRESETS`, `THEME_STYLE_PRESET_ORDER`, `buildThemeStyleSheet`, `ThemeStyleBridge`.
135
88
 
136
89
  ---
137
90
 
@@ -142,29 +95,16 @@ import { useLocaleSwitcher } from '@djangocfg/nextjs/i18n/client';
142
95
  import { routing } from '@djangocfg/nextjs/i18n/routing';
143
96
 
144
97
  const { locale, locales, changeLocale } = useLocaleSwitcher();
145
- <AppLayout i18n={{ locale, locales, onLocaleChange: changeLocale, routing }}>
146
- {children}
147
- </AppLayout>
98
+ <AppLayout i18n={{ locale, locales, onLocaleChange: changeLocale, routing }}>...</AppLayout>
148
99
  ```
149
100
 
150
- `i18n.routing` is the object returned by `defineRouting()` (`next-intl/routing`). When passed, `BaseApp` builds a locale-aware `Link` adapter from it and mounts a `LinkProvider`, so every `<Link>` rendered inside layouts (navbar, footer, drawer, …) keeps the active locale prefix on click. Drop it for default-locale-only apps — raw `next/link` works fine.
101
+ When `routing` is set, `BaseApp` mounts a locale-aware `<Link>` adapter so every layout-rendered link keeps the active locale prefix. Drop it for default-locale-only apps.
151
102
 
152
103
  ---
153
104
 
154
105
  ## Monitor & debug
155
106
 
156
- `project` on `BaseApp` enables `window.monitor`. Debug panel: `Cmd+D` or `?debug=1`.
157
-
158
- ---
159
-
160
- ## Utilities
161
-
162
- | API | Role |
163
- |---|---|
164
- | `useErrorEmitter`, `emitRuntimeError` | Error tracking hooks. |
165
- | `ErrorLayout` (`/components/errors`) | 404 / error pages. |
166
- | `RedirectPage` | Auth redirect helper. |
167
- | `PrivacyPage`, `TermsPage`, … (`/pages/legal`) | Legal templates. |
107
+ `baseApp.project` enables `window.monitor`. Debug panel: `Cmd+D` or `?debug=1`.
168
108
 
169
109
  ---
170
110
 
@@ -174,13 +114,12 @@ const { locale, locales, changeLocale } = useLocaleSwitcher();
174
114
  |---|---|
175
115
  | `@djangocfg/layouts` | Barrel. |
176
116
  | `@djangocfg/layouts/layouts` | Layout components. |
177
- | `@djangocfg/layouts/components` | Misc. |
178
- | `@djangocfg/layouts/pages/legal` | Legal pages. |
117
+ | `@djangocfg/layouts/components` | Misc + `ErrorLayout`, `RedirectPage`. |
118
+ | `@djangocfg/layouts/pages/legal` | Legal page templates. |
119
+ | `@djangocfg/layouts/configurator` | JSON Schemas for `<JsonSchemaForm>`-driven configurator UIs (PrivateLayout for now). Pair with `@djangocfg/ui-tools`. |
179
120
  | `@djangocfg/layouts/styles` | Base CSS. |
180
121
 
181
- ## Extensions
182
-
183
- `@djangocfg/ext-newsletter`, `ext-knowbase`, `ext-leads`, `ext-payments`, `ext-support`, …
122
+ Extensions: `@djangocfg/ext-newsletter`, `ext-knowbase`, `ext-leads`, `ext-payments`, `ext-support`, …
184
123
 
185
124
  ## License
186
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.319",
3
+ "version": "2.1.321",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -60,6 +60,16 @@
60
60
  "import": "./src/pages/index.ts",
61
61
  "require": "./src/pages/index.ts"
62
62
  },
63
+ "./configurator": {
64
+ "types": "./src/configurator/index.ts",
65
+ "import": "./src/configurator/index.ts",
66
+ "require": "./src/configurator/index.ts"
67
+ },
68
+ "./configurator/private": {
69
+ "types": "./src/configurator/private/index.ts",
70
+ "import": "./src/configurator/private/index.ts",
71
+ "require": "./src/configurator/private/index.ts"
72
+ },
63
73
  "./styles": "./src/styles/index.css",
64
74
  "./styles/dashboard": "./src/styles/dashboard.css"
65
75
  },
@@ -74,14 +84,13 @@
74
84
  "check": "tsc --noEmit"
75
85
  },
76
86
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.319",
78
- "@djangocfg/centrifugo": "^2.1.319",
79
- "@djangocfg/debuger": "^2.1.319",
80
- "@djangocfg/i18n": "^2.1.319",
81
- "@djangocfg/monitor": "^2.1.319",
82
- "@djangocfg/ui-core": "^2.1.319",
83
- "@djangocfg/ui-nextjs": "^2.1.319",
84
- "@djangocfg/ui-tools": "^2.1.319",
87
+ "@djangocfg/api": "^2.1.321",
88
+ "@djangocfg/centrifugo": "^2.1.321",
89
+ "@djangocfg/debuger": "^2.1.321",
90
+ "@djangocfg/i18n": "^2.1.321",
91
+ "@djangocfg/monitor": "^2.1.321",
92
+ "@djangocfg/ui-core": "^2.1.321",
93
+ "@djangocfg/ui-nextjs": "^2.1.321",
85
94
  "@hookform/resolvers": "^5.2.2",
86
95
  "consola": "^3.4.2",
87
96
  "lucide-react": "^0.545.0",
@@ -111,15 +120,15 @@
111
120
  "uuid": "^11.1.0"
112
121
  },
113
122
  "devDependencies": {
114
- "@djangocfg/api": "^2.1.319",
115
- "@djangocfg/centrifugo": "^2.1.319",
116
- "@djangocfg/debuger": "^2.1.319",
117
- "@djangocfg/i18n": "^2.1.319",
118
- "@djangocfg/monitor": "^2.1.319",
119
- "@djangocfg/typescript-config": "^2.1.319",
120
- "@djangocfg/ui-core": "^2.1.319",
121
- "@djangocfg/ui-nextjs": "^2.1.319",
122
- "@djangocfg/ui-tools": "^2.1.319",
123
+ "@djangocfg/api": "^2.1.321",
124
+ "@djangocfg/centrifugo": "^2.1.321",
125
+ "@djangocfg/debuger": "^2.1.321",
126
+ "@djangocfg/i18n": "^2.1.321",
127
+ "@djangocfg/monitor": "^2.1.321",
128
+ "@djangocfg/typescript-config": "^2.1.321",
129
+ "@djangocfg/ui-core": "^2.1.321",
130
+ "@djangocfg/ui-nextjs": "^2.1.321",
131
+ "@djangocfg/ui-tools": "^2.1.321",
123
132
  "@types/node": "^24.7.2",
124
133
  "@types/react": "^19.1.0",
125
134
  "@types/react-dom": "^19.1.0",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * JSON Schema descriptors for `<JsonSchemaForm>`-driven layout configurators.
3
+ *
4
+ * Drop a schema + uiSchema into `@djangocfg/ui-tools`'s `<JsonSchemaForm>` and
5
+ * you get a ready-made sidebar that edits the matching `<Layout>` props live.
6
+ *
7
+ * Public surface today:
8
+ * - `private` — `PrivateLayout` configurator (shell + sidebar + header)
9
+ *
10
+ * Public + theme configurators are not exported yet — they currently live in
11
+ * the demo app while the schema shape is iterated on.
12
+ */
13
+
14
+ export * from './private';
@@ -0,0 +1,6 @@
1
+ export {
2
+ privateLayoutConfiguratorSchema,
3
+ privateLayoutConfiguratorUiSchema,
4
+ defaultPrivateLayoutConfiguratorData,
5
+ } from './schema';
6
+ export type { PrivateLayoutConfiguratorData } from './schema';
@@ -0,0 +1,190 @@
1
+ /**
2
+ * JSON Schema + uiSchema for a `PrivateLayout` configurator UI.
3
+ *
4
+ * The shape mirrors the real `PrivateLayout` runtime API:
5
+ * - `shell.*` → `visual` prop (`LayoutVisualConfig`)
6
+ * - `sidebar.*` → flags consumed by `SidebarConfig` (caller still maps these
7
+ * into a real `SidebarConfig` with concrete `groups`)
8
+ * - `header.*` → fields consumed by `HeaderConfig`
9
+ *
10
+ * Pair with `<JsonSchemaForm density="compact" schema={...} uiSchema={...}>`
11
+ * from `@djangocfg/ui-tools` to get a ready-made sidebar configurator. The
12
+ * `defaultPrivateLayoutConfiguratorData` is a safe initial value that already
13
+ * matches the schema (no nullable fields, no missing keys).
14
+ *
15
+ * Playground-only knobs (sample item count, banner toggles, etc.) live in the
16
+ * consuming app, not here — this stays a clean view of the real layout API.
17
+ */
18
+
19
+ import type { CustomJsonSchema7, CustomJsonUiSchema7 } from '@djangocfg/ui-core/lib';
20
+
21
+ /** Strongly-typed mirror of the schema — feed this back into PrivateLayout. */
22
+ export interface PrivateLayoutConfiguratorData {
23
+ shell: {
24
+ variant: 'full-bleed' | 'boxed';
25
+ inset: number;
26
+ radius: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
27
+ background: 'sidebar' | 'muted' | 'card' | 'background';
28
+ border: boolean;
29
+ maxWidth: 'none' | '7xl' | 'screen-xl' | 'screen-2xl';
30
+ };
31
+ sidebar: {
32
+ activeIndicator: 'background' | 'rail' | 'both';
33
+ groupLabelStyle: 'uppercase' | 'plain';
34
+ collapsibleGroups: boolean;
35
+ showFeatured: boolean;
36
+ };
37
+ header: {
38
+ userPlan: string;
39
+ showSecondaryAction: boolean;
40
+ };
41
+ }
42
+
43
+ export const privateLayoutConfiguratorSchema: CustomJsonSchema7 = {
44
+ type: 'object',
45
+ properties: {
46
+ shell: {
47
+ type: 'object',
48
+ title: 'Shell',
49
+ properties: {
50
+ variant: {
51
+ type: 'string',
52
+ title: 'Variant',
53
+ enum: ['full-bleed', 'boxed'],
54
+ description: '`boxed` paints a sidebar-coloured canvas around a rounded content card.',
55
+ },
56
+ inset: {
57
+ type: 'integer',
58
+ title: 'Inset (md+)',
59
+ minimum: 0,
60
+ maximum: 32,
61
+ description: 'Gap between the card and viewport edges.',
62
+ },
63
+ radius: {
64
+ type: 'string',
65
+ title: 'Radius',
66
+ enum: ['sm', 'md', 'lg', 'xl', '2xl', '3xl'],
67
+ },
68
+ background: {
69
+ type: 'string',
70
+ title: 'Canvas background',
71
+ enum: ['sidebar', 'muted', 'card', 'background'],
72
+ description: 'Colour painted behind the boxed card.',
73
+ },
74
+ border: {
75
+ type: 'boolean',
76
+ title: 'Border on card',
77
+ description: '1px hairline border around the boxed card.',
78
+ },
79
+ maxWidth: {
80
+ type: 'string',
81
+ title: 'Inner max-width',
82
+ enum: ['none', '7xl', 'screen-xl', 'screen-2xl'],
83
+ },
84
+ },
85
+ },
86
+ sidebar: {
87
+ type: 'object',
88
+ title: 'Sidebar',
89
+ properties: {
90
+ activeIndicator: {
91
+ type: 'string',
92
+ title: 'Active indicator',
93
+ enum: ['background', 'rail', 'both'],
94
+ description: 'Visual treatment for the active nav item.',
95
+ },
96
+ groupLabelStyle: {
97
+ type: 'string',
98
+ title: 'Group label style',
99
+ enum: ['uppercase', 'plain'],
100
+ description: 'Collapsible groups always render `plain` regardless of this setting.',
101
+ },
102
+ collapsibleGroups: {
103
+ type: 'boolean',
104
+ title: 'Collapsible groups',
105
+ description: 'Mailersend-style accordion: label becomes a clickable trigger.',
106
+ },
107
+ showFeatured: {
108
+ type: 'boolean',
109
+ title: 'Featured CTA tile',
110
+ description: 'Accent-tinted tile rendered below groups.',
111
+ },
112
+ },
113
+ },
114
+ header: {
115
+ type: 'object',
116
+ title: 'Header / account footer',
117
+ properties: {
118
+ userPlan: {
119
+ type: 'string',
120
+ title: 'User plan',
121
+ enum: ['', 'Free plan', 'Pro plan', 'Max plan', 'Enterprise'],
122
+ description: 'Subtitle under the display name in the footer trigger. Empty hides it.',
123
+ },
124
+ showSecondaryAction: {
125
+ type: 'boolean',
126
+ title: 'Footer secondary action',
127
+ description: 'Adds a download-style icon button inside the footer trigger with a pulsing accent dot.',
128
+ },
129
+ },
130
+ },
131
+ },
132
+ };
133
+
134
+ export const privateLayoutConfiguratorUiSchema: CustomJsonUiSchema7 = {
135
+ shell: {
136
+ 'ui:collapsible': true,
137
+ inset: {
138
+ 'ui:widget': 'slider',
139
+ 'ui:options': { unit: 'px', step: 2, showInput: false },
140
+ 'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
141
+ },
142
+ radius: {
143
+ 'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
144
+ },
145
+ background: {
146
+ 'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
147
+ },
148
+ border: {
149
+ 'ui:widget': 'switch',
150
+ 'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
151
+ },
152
+ },
153
+ sidebar: {
154
+ 'ui:collapsible': true,
155
+ 'ui:groups': [
156
+ {
157
+ title: 'Visual',
158
+ fields: ['activeIndicator', 'groupLabelStyle', 'collapsibleGroups', 'showFeatured'],
159
+ defaultOpen: true,
160
+ },
161
+ ],
162
+ collapsibleGroups: { 'ui:widget': 'switch' },
163
+ showFeatured: { 'ui:widget': 'switch' },
164
+ },
165
+ header: {
166
+ 'ui:collapsible': true,
167
+ showSecondaryAction: { 'ui:widget': 'switch' },
168
+ },
169
+ };
170
+
171
+ export const defaultPrivateLayoutConfiguratorData: PrivateLayoutConfiguratorData = {
172
+ shell: {
173
+ variant: 'boxed',
174
+ inset: 12,
175
+ radius: '2xl',
176
+ background: 'sidebar',
177
+ border: true,
178
+ maxWidth: 'none',
179
+ },
180
+ sidebar: {
181
+ activeIndicator: 'background',
182
+ groupLabelStyle: 'uppercase',
183
+ collapsibleGroups: false,
184
+ showFeatured: false,
185
+ },
186
+ header: {
187
+ userPlan: 'Pro plan',
188
+ showSecondaryAction: false,
189
+ },
190
+ };
@@ -13,7 +13,7 @@ import { useRouter } from 'next/navigation';
13
13
 
14
14
  import { useAuth } from '@djangocfg/api/auth';
15
15
  import { Preloader } from '@djangocfg/ui-core/components';
16
- import { SidebarInset, SidebarProvider } from '@djangocfg/ui-nextjs/components';
16
+ import { SidebarInset, SidebarProvider } from '@djangocfg/ui-core/components';
17
17
 
18
18
  import type { AppLayoutPublicChrome } from '../AppLayout/AppLayout';
19
19
  import type { LayoutVisualConfig } from '../types';
@@ -27,6 +27,8 @@ export interface SidebarItem {
27
27
  href: string;
28
28
  icon?: string | LucideIconType;
29
29
  badge?: string | number;
30
+ /** Visual style of `badge`: `'count'` (default, neutral pill) or `'pill'` (accent-tinted, e.g. "lite"/"new"). */
31
+ badgeVariant?: 'count' | 'pill';
30
32
  /** Collapsed rail: shown in tooltip; defaults to `label`. */
31
33
  tooltip?: string;
32
34
  }
@@ -38,6 +40,32 @@ export interface SidebarGroupConfig {
38
40
  items: SidebarItem[];
39
41
  /** If true, group is only shown when it has items (for dynamic groups like extensions) */
40
42
  dynamic?: boolean;
43
+ /** Render group as an accordion (Mailersend/Vercel-style). Label becomes a clickable trigger. */
44
+ collapsible?: boolean;
45
+ /** Initial open state for collapsible groups. Auto-expanded if any child is active. Default `false`. */
46
+ defaultOpen?: boolean;
47
+ /** Icon for the group trigger (only when `collapsible`). */
48
+ icon?: string | LucideIconType;
49
+ /**
50
+ * Hide per-item icons inside the group. Defaults to `true` when `collapsible`,
51
+ * `false` otherwise (Mailersend convention: icons live on the trigger, not on children).
52
+ */
53
+ hideItemIcons?: boolean;
54
+ }
55
+
56
+ /** Active-state visual treatment for sidebar nav items. */
57
+ export type SidebarActiveIndicator = 'background' | 'rail' | 'both';
58
+
59
+ /** Rendering of group labels. `'uppercase'` is the legacy ultra-light caps; `'plain'` is sm bold. */
60
+ export type SidebarGroupLabelStyle = 'uppercase' | 'plain';
61
+
62
+ /** Featured CTA tile rendered below groups. */
63
+ export interface SidebarFeaturedConfig {
64
+ icon?: string | LucideIconType;
65
+ label: string;
66
+ href: string;
67
+ badge?: string;
68
+ accent?: 'green' | 'blue' | 'amber' | 'primary';
41
69
  }
42
70
 
43
71
  export interface SidebarConfig {
@@ -65,6 +93,12 @@ export interface SidebarConfig {
65
93
  menuEndShowOnCollapsed?: boolean;
66
94
  /** Custom footer component rendered at the bottom of the sidebar */
67
95
  footer?: ReactNode;
96
+ /** Active-state visual on nav items. Default `'background'` (legacy). */
97
+ activeIndicator?: SidebarActiveIndicator;
98
+ /** Style of group labels. Default `'uppercase'` (legacy). Collapsible groups always use `plain`. */
99
+ groupLabelStyle?: SidebarGroupLabelStyle;
100
+ /** Featured CTA tile rendered below all groups, above `menuEnd`. */
101
+ featured?: SidebarFeaturedConfig;
68
102
  }
69
103
 
70
104
  export interface HeaderConfig {
@@ -86,6 +120,17 @@ export interface HeaderConfig {
86
120
  groups?: UserMenuConfig['groups'];
87
121
  /** Auth page path (for sign in button) */
88
122
  authPath?: string;
123
+ /** Subtitle under the display name in the sidebar footer (e.g. "Max plan"). */
124
+ userPlan?: string;
125
+ /** Optional secondary action button rendered inside the footer trigger (e.g. Get apps download button). */
126
+ footerSecondaryAction?: {
127
+ icon: string | LucideIconType;
128
+ href?: string;
129
+ onClick?: () => void;
130
+ ariaLabel: string;
131
+ /** Show pulsing accent dot on the action (Claude-style "new"). */
132
+ pulse?: boolean;
133
+ };
89
134
  }
90
135
 
91
136
  export interface PrivateLayoutProps {