@djangocfg/layouts 2.1.307 → 2.1.308

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
@@ -87,7 +87,7 @@ Wraps `BaseApp` and picks **admin → private → public** layout by path (`matc
87
87
 
88
88
  | Component | Use |
89
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 `controls` + `i18n` to show theme / locale switchers next to UserMenu (same shape as `DefaultFooter.controls`). **[See PublicLayout README](./src/layouts/PublicLayout/README.md)** for full props, navbar variants (`FloatingNavbar` / `FlushNavbar` / `MinimalNavbar`), `DefaultFooter`, `NavAction`, `NavControls`, and hooks. |
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
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
92
  | **`AuthLayout`** | Sign-in flows. |
93
93
  | **`AdminLayout`** | Admin console. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.307",
3
+ "version": "2.1.308",
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.307",
78
- "@djangocfg/centrifugo": "^2.1.307",
79
- "@djangocfg/debuger": "^2.1.307",
80
- "@djangocfg/i18n": "^2.1.307",
81
- "@djangocfg/monitor": "^2.1.307",
82
- "@djangocfg/ui-core": "^2.1.307",
83
- "@djangocfg/ui-nextjs": "^2.1.307",
84
- "@djangocfg/ui-tools": "^2.1.307",
77
+ "@djangocfg/api": "^2.1.308",
78
+ "@djangocfg/centrifugo": "^2.1.308",
79
+ "@djangocfg/debuger": "^2.1.308",
80
+ "@djangocfg/i18n": "^2.1.308",
81
+ "@djangocfg/monitor": "^2.1.308",
82
+ "@djangocfg/ui-core": "^2.1.308",
83
+ "@djangocfg/ui-nextjs": "^2.1.308",
84
+ "@djangocfg/ui-tools": "^2.1.308",
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.307",
114
- "@djangocfg/centrifugo": "^2.1.307",
115
- "@djangocfg/debuger": "^2.1.307",
116
- "@djangocfg/i18n": "^2.1.307",
117
- "@djangocfg/monitor": "^2.1.307",
118
- "@djangocfg/typescript-config": "^2.1.307",
119
- "@djangocfg/ui-core": "^2.1.307",
120
- "@djangocfg/ui-nextjs": "^2.1.307",
121
- "@djangocfg/ui-tools": "^2.1.307",
113
+ "@djangocfg/api": "^2.1.308",
114
+ "@djangocfg/centrifugo": "^2.1.308",
115
+ "@djangocfg/debuger": "^2.1.308",
116
+ "@djangocfg/i18n": "^2.1.308",
117
+ "@djangocfg/monitor": "^2.1.308",
118
+ "@djangocfg/typescript-config": "^2.1.308",
119
+ "@djangocfg/ui-core": "^2.1.308",
120
+ "@djangocfg/ui-nextjs": "^2.1.308",
121
+ "@djangocfg/ui-tools": "^2.1.308",
122
122
  "@types/node": "^24.7.2",
123
123
  "@types/react": "^19.1.0",
124
124
  "@types/react-dom": "^19.1.0",
@@ -7,7 +7,7 @@ import { PublicLayout, FloatingNavbar, DefaultFooter } from '@djangocfg/layouts'
7
7
 
8
8
  <PublicLayout
9
9
  navbar={<FloatingNavbar config={{ brand, navigation, userMenu, actions }} />}
10
- footer={<DefaultFooter config={{ variant: 'full', menus: { sections }, i18n }} />}
10
+ footer={<DefaultFooter config={{ variant: 'full', menus: { sections } }} />}
11
11
  >
12
12
  {children}
13
13
  </PublicLayout>
@@ -74,8 +74,7 @@ Three variants. All share the same core props (below); only the chrome differs.
74
74
  | `transparentThreshold` | `number` | `40` | Px past which the nav becomes opaque. |
75
75
  | `desktopMaxPrimaryItems` | `number` | auto | Hard cap for primary items before overflow. |
76
76
  | `renderDesktopDropdown` | `(ctx) => ReactNode` | — | Replace default popover per-item. |
77
- | `controls` | `{ showThemeSwitcher?; showLocaleSwitcher? }` | — | Compact theme + locale pills next to UserMenu. See below. |
78
- | `i18n` | `{ locale, locales, onLocaleChange }` | — | Required for the locale switcher (same shape as `DefaultFooter.i18n`). |
77
+ | `controls` | `{ showThemeSwitcher?; showLocaleSwitcher? }` | — | Compact theme + locale pills next to UserMenu. Locale data flows from `LayoutI18nProvider` (mounted by `BaseApp`); the switcher hides itself when no provider is present. See below. |
79
78
 
80
79
  ### Variant-only
81
80
 
@@ -94,10 +93,9 @@ Three variants. All share the same core props (below); only the chrome differs.
94
93
 
95
94
  ## Theme + locale controls (navbar)
96
95
 
97
- All three navbars accept an optional `controls` block and `i18n` config that
98
- mirrors `DefaultFooter`. When enabled, a compact `NavControls` pill is rendered
99
- on desktop right before `UserMenu`, and an equivalent row appears in the mobile
100
- drawer footer.
96
+ All three navbars accept an optional `controls` block. When enabled, a compact
97
+ `NavControls` pill is rendered on desktop right before `UserMenu`, and an
98
+ equivalent row appears in the mobile drawer footer.
101
99
 
102
100
  ```tsx
103
101
  <FloatingNavbar
@@ -106,7 +104,6 @@ drawer footer.
106
104
  navigation,
107
105
  userMenu: { authPath: '/auth' },
108
106
  controls: { showThemeSwitcher: true, showLocaleSwitcher: true },
109
- i18n: { locale, locales, onLocaleChange: changeLocale },
110
107
  }}
111
108
  />
112
109
  ```
@@ -114,7 +111,12 @@ drawer footer.
114
111
  | Control | Required | Notes |
115
112
  |---|---|---|
116
113
  | `controls.showThemeSwitcher` | — | Light / system / dark pill (uses `useThemeContext`). |
117
- | `controls.showLocaleSwitcher` | `i18n` | Globe dropdown. Silently hidden when `i18n` is omitted. |
114
+ | `controls.showLocaleSwitcher` | `LayoutI18nProvider` | Locale switcher (icon-size pill matching `UserMenu` avatar). Hidden when no provider is mounted. |
115
+
116
+ Locale data (`locale`, `locales`, `onLocaleChange`) is read from
117
+ `LayoutI18nProvider`, which `BaseApp` (and `AppLayout`) mounts automatically
118
+ when you pass an `i18n` config. The switcher renders SVG country flags via
119
+ `country-flag-icons` — no emoji.
118
120
 
119
121
  Same shape works for `FlushNavbar` and `MinimalNavbar`. If you build a custom
120
122
  navbar with `NavbarShell`, the controls node is pre-built and delivered to
@@ -129,7 +131,6 @@ import { NavControls } from '@djangocfg/layouts';
129
131
  <NavControls
130
132
  showThemeSwitcher
131
133
  showLocaleSwitcher
132
- i18n={{ locale, locales, onLocaleChange }}
133
134
  size="compact" // 'compact' (navbar) | 'default' (footer)
134
135
  />
135
136
  ```
@@ -160,7 +161,7 @@ actions={[
160
161
  Three variants: `full` (default) with brand column + menus + controls; `compact` (one row + bottom); `simple` (copyright only).
161
162
 
162
163
  ```tsx
163
- <DefaultFooter config={{ variant: 'full', menus: { sections }, i18n, slots }} />
164
+ <DefaultFooter config={{ variant: 'full', menus: { sections }, slots }} />
164
165
  ```
165
166
 
166
167
  | Prop (`config.*`) | Type | Default | Role |
@@ -178,9 +179,8 @@ Three variants: `full` (default) with brand column + menus + controls; `compact`
178
179
  | `social` | `FooterSocialLinks` | — | Social icon row under brand. |
179
180
  | `meta.copyright` | `string` | `© ${year}. All rights reserved.` | |
180
181
  | `meta.credits` | `{ text: string; url?: string }` | — | |
181
- | `i18n` | `{ locale, locales, onLocaleChange }` | — | Required for the locale switcher. |
182
182
  | `controls.showThemeSwitcher` | `boolean` | `true` | Full variant only. |
183
- | `controls.showLocaleSwitcher` | `boolean` | `true` (if `i18n`) | Full variant only. |
183
+ | `controls.showLocaleSwitcher` | `boolean` | `true` | Full variant only. Reads locale from `LayoutI18nProvider`; hidden when no provider is mounted. |
184
184
  | `slots.aboveMenus` | `ReactNode` | — | Above the brand/menus grid (full variant). |
185
185
  | `slots.belowMenus` | `ReactNode` | — | Between menus and bottom row. |
186
186
  | `slots.bottomStart` | `ReactNode` | — | Next to copyright. |
@@ -44,22 +44,38 @@ export function LocaleSwitcherDropdown({
44
44
  }: LocaleSwitcherDropdownProps) {
45
45
  const currentMeta = getLocaleMeta(locale, labels);
46
46
  const currentLabel = showCode ? locale.toUpperCase() : currentMeta.native;
47
+ const isIconOnly = size === 'icon';
47
48
 
48
49
  return (
49
50
  <DropdownMenu>
50
51
  <DropdownMenuTrigger asChild>
51
- <Button variant={variant} size={size} className={className}>
52
- {showFlag ? (
53
- <LanguageFlag
54
- code={locale}
55
- rounded
56
- className={cn('h-3 w-4 shrink-0', showTriggerLabel && 'mr-1.5')}
57
- />
58
- ) : showIcon ? (
59
- <Globe className={cn('h-4 w-4 shrink-0', showTriggerLabel && 'mr-1')} />
60
- ) : null}
61
- {showTriggerLabel && <span>{currentLabel}</span>}
62
- </Button>
52
+ {isIconOnly ? (
53
+ <Button
54
+ variant={variant}
55
+ size={size}
56
+ aria-label={currentLabel}
57
+ className={cn('overflow-hidden rounded-full p-0', className)}
58
+ >
59
+ {showFlag ? (
60
+ <LanguageFlag code={locale} className="h-full w-full object-cover" />
61
+ ) : (
62
+ <Globe className="h-4 w-4" aria-hidden />
63
+ )}
64
+ </Button>
65
+ ) : (
66
+ <Button variant={variant} size={size} className={className}>
67
+ {showFlag ? (
68
+ <LanguageFlag
69
+ code={locale}
70
+ rounded
71
+ className={cn('h-3 w-4 shrink-0', showTriggerLabel && 'mr-1.5')}
72
+ />
73
+ ) : showIcon ? (
74
+ <Globe className={cn('h-4 w-4 shrink-0', showTriggerLabel && 'mr-1')} />
75
+ ) : null}
76
+ {showTriggerLabel && <span>{currentLabel}</span>}
77
+ </Button>
78
+ )}
63
79
  </DropdownMenuTrigger>
64
80
  <DropdownMenuContent align="end">
65
81
  {locales.map((code) => {
@@ -48,6 +48,35 @@ export const LocaleSwitcherTrigger = React.forwardRef<
48
48
  ) {
49
49
  const meta = getLocaleMeta(locale, labels);
50
50
  const labelText = showCode ? locale.toUpperCase() : meta.native;
51
+ const isIconOnly = size === 'icon';
52
+
53
+ // Icon-only trigger: full-bleed flag inside a circular pill that lines up
54
+ // with neighbouring avatars / icon buttons. No padding, no inner gap.
55
+ if (isIconOnly) {
56
+ return (
57
+ <Button
58
+ ref={ref}
59
+ type="button"
60
+ variant={variant}
61
+ size={size}
62
+ onClick={onClick}
63
+ aria-label={ariaLabel ?? labelText}
64
+ className={cn(
65
+ 'overflow-hidden rounded-full p-0',
66
+ className,
67
+ )}
68
+ >
69
+ {showFlag ? (
70
+ <LanguageFlag
71
+ code={locale}
72
+ className="h-full w-full object-cover"
73
+ />
74
+ ) : (
75
+ <Globe className="h-4 w-4" aria-hidden />
76
+ )}
77
+ </Button>
78
+ );
79
+ }
51
80
 
52
81
  return (
53
82
  <Button