@djangocfg/layouts 2.1.304 → 2.1.307
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/package.json +18 -18
- package/src/layouts/AppLayout/AppLayout.tsx +14 -11
- package/src/layouts/AppLayout/BaseApp.tsx +3 -1
- package/src/layouts/AppLayout/LayoutI18nProvider.tsx +59 -0
- package/src/layouts/AppLayout/index.ts +7 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -5
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +2 -4
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +6 -11
- package/src/layouts/PublicLayout/footers/DefaultFooter/types.ts +15 -6
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +1 -5
- package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushNavbar.tsx +1 -5
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +1 -6
- package/src/layouts/PublicLayout/primitives/NavControls.tsx +5 -9
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +2 -6
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +1 -7
- package/src/layouts/_components/LocaleSwitcher.tsx +40 -178
- package/src/layouts/_components/PrivateSidebarAccount.tsx +6 -8
- package/src/layouts/_components/UserMenu.tsx +15 -19
- package/src/layouts/_components/index.ts +23 -2
- package/src/layouts/_components/locale-switcher/LocaleCard.tsx +91 -0
- package/src/layouts/_components/locale-switcher/LocaleGrid.tsx +128 -0
- package/src/layouts/_components/locale-switcher/LocaleSwitcher.tsx +100 -0
- package/src/layouts/_components/locale-switcher/LocaleSwitcherDialog.tsx +168 -0
- package/src/layouts/_components/locale-switcher/LocaleSwitcherDropdown.tsx +85 -0
- package/src/layouts/_components/locale-switcher/LocaleSwitcherTrigger.tsx +74 -0
- package/src/layouts/_components/locale-switcher/index.ts +27 -0
- package/src/layouts/_components/locale-switcher/localeMeta.ts +109 -0
- package/src/layouts/_components/locale-switcher/types.ts +48 -0
- package/src/layouts/types/layout.types.ts +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.307",
|
|
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.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",
|
|
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.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",
|
|
122
122
|
"@types/node": "^24.7.2",
|
|
123
123
|
"@types/react": "^19.1.0",
|
|
124
124
|
"@types/react-dom": "^19.1.0",
|
|
@@ -38,6 +38,7 @@ import { ClientOnly, Suspense } from '../../components/core';
|
|
|
38
38
|
import { usePathnameWithoutLocale } from '../../hooks';
|
|
39
39
|
import { matchesPath } from '../../utils/pathMatcher';
|
|
40
40
|
import { BaseApp } from './BaseApp';
|
|
41
|
+
import { useLayoutI18nOptional } from './LayoutI18nProvider';
|
|
41
42
|
import { ForcedThemeProvider, ThemeOverride, resolveForcedTheme } from '@djangocfg/ui-nextjs/theme';
|
|
42
43
|
|
|
43
44
|
import type {
|
|
@@ -159,10 +160,12 @@ function determineLayoutMode(
|
|
|
159
160
|
/**
|
|
160
161
|
* Props passed to every layout component (`public` / `private` / `admin`).
|
|
161
162
|
* Use `publicChrome` to pass defaults for `FloatingNavbar` / `DefaultFooter` from `AppLayout`.
|
|
163
|
+
*
|
|
164
|
+
* Locale plumbing is no longer threaded as a prop — layouts read it from
|
|
165
|
+
* `useLayoutI18nOptional()` (mounted in `BaseApp`).
|
|
162
166
|
*/
|
|
163
167
|
export interface AppLayoutLayoutComponentProps {
|
|
164
168
|
children: ReactNode;
|
|
165
|
-
i18n?: I18nLayoutConfig;
|
|
166
169
|
publicChrome?: AppLayoutPublicChrome;
|
|
167
170
|
}
|
|
168
171
|
|
|
@@ -298,7 +301,6 @@ interface AppLayoutContentProps {
|
|
|
298
301
|
adminLayout?: LayoutConfig;
|
|
299
302
|
noLayoutPaths?: string | string[];
|
|
300
303
|
authPath?: string;
|
|
301
|
-
i18n?: I18nLayoutConfig;
|
|
302
304
|
publicChrome?: AppLayoutPublicChrome;
|
|
303
305
|
themeOverrides?: ThemeOverrideRule[];
|
|
304
306
|
}
|
|
@@ -316,12 +318,13 @@ function AppLayoutContent({
|
|
|
316
318
|
adminLayout,
|
|
317
319
|
noLayoutPaths,
|
|
318
320
|
authPath = '/auth',
|
|
319
|
-
i18n,
|
|
320
321
|
publicChrome,
|
|
321
322
|
themeOverrides,
|
|
322
323
|
}: AppLayoutContentProps) {
|
|
323
|
-
// Use pathname without locale prefix for route matching.
|
|
324
|
-
//
|
|
324
|
+
// Use pathname without locale prefix for route matching. The current
|
|
325
|
+
// locale comes from the LayoutI18nProvider mounted by BaseApp — passing
|
|
326
|
+
// it explicitly avoids the regex misfiring on `/ui`, `/ko`, etc.
|
|
327
|
+
const i18n = useLayoutI18nOptional();
|
|
325
328
|
const pathname = usePathnameWithoutLocale(i18n?.locale);
|
|
326
329
|
|
|
327
330
|
// Merge authPath into noLayoutPaths — auth pages are always fullscreen
|
|
@@ -361,7 +364,7 @@ function AppLayoutContent({
|
|
|
361
364
|
return (
|
|
362
365
|
<ClientOnly>
|
|
363
366
|
<Suspense>
|
|
364
|
-
<privateLayout.component
|
|
367
|
+
<privateLayout.component publicChrome={publicChrome}>
|
|
365
368
|
{children}
|
|
366
369
|
</privateLayout.component>
|
|
367
370
|
</Suspense>
|
|
@@ -374,7 +377,7 @@ function AppLayoutContent({
|
|
|
374
377
|
return (
|
|
375
378
|
<ClientOnly>
|
|
376
379
|
<Suspense>
|
|
377
|
-
<adminLayout.component
|
|
380
|
+
<adminLayout.component publicChrome={publicChrome}>
|
|
378
381
|
{children}
|
|
379
382
|
</adminLayout.component>
|
|
380
383
|
</Suspense>
|
|
@@ -385,7 +388,7 @@ function AppLayoutContent({
|
|
|
385
388
|
if (!privateLayout) {
|
|
386
389
|
if (publicLayout) {
|
|
387
390
|
return (
|
|
388
|
-
<publicLayout.component
|
|
391
|
+
<publicLayout.component publicChrome={publicChrome}>
|
|
389
392
|
{children}
|
|
390
393
|
</publicLayout.component>
|
|
391
394
|
);
|
|
@@ -395,7 +398,7 @@ function AppLayoutContent({
|
|
|
395
398
|
return (
|
|
396
399
|
<ClientOnly>
|
|
397
400
|
<Suspense>
|
|
398
|
-
<privateLayout.component
|
|
401
|
+
<privateLayout.component publicChrome={publicChrome}>
|
|
399
402
|
{children}
|
|
400
403
|
</privateLayout.component>
|
|
401
404
|
</Suspense>
|
|
@@ -409,7 +412,7 @@ function AppLayoutContent({
|
|
|
409
412
|
return children;
|
|
410
413
|
}
|
|
411
414
|
return (
|
|
412
|
-
<publicLayout.component
|
|
415
|
+
<publicLayout.component publicChrome={publicChrome}>
|
|
413
416
|
{children}
|
|
414
417
|
</publicLayout.component>
|
|
415
418
|
);
|
|
@@ -480,6 +483,7 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
480
483
|
pwaInstall={pwaInstall}
|
|
481
484
|
monitor={monitor}
|
|
482
485
|
debug={debug}
|
|
486
|
+
i18n={i18n}
|
|
483
487
|
>
|
|
484
488
|
<AppLayoutContent
|
|
485
489
|
children={children}
|
|
@@ -488,7 +492,6 @@ export function AppLayout(props: AppLayoutProps) {
|
|
|
488
492
|
adminLayout={adminLayout}
|
|
489
493
|
noLayoutPaths={noLayoutPaths}
|
|
490
494
|
authPath={authPath}
|
|
491
|
-
i18n={i18n}
|
|
492
495
|
publicChrome={publicChrome}
|
|
493
496
|
themeOverrides={themeOverrides}
|
|
494
497
|
/>
|
|
@@ -58,6 +58,7 @@ import { AuthDialog } from '../../snippets/AuthDialog';
|
|
|
58
58
|
import { A2HSHint, PWAPageResumeManager, PwaProvider } from '@djangocfg/ui-nextjs/pwa';
|
|
59
59
|
|
|
60
60
|
import type { BaseLayoutProps } from '../types/layout.types';
|
|
61
|
+
import { LayoutI18nProvider } from './LayoutI18nProvider';
|
|
61
62
|
|
|
62
63
|
// Lazy load DebugButton from @djangocfg/debuger
|
|
63
64
|
const DebugButton = dynamic(
|
|
@@ -89,6 +90,7 @@ export function BaseApp({
|
|
|
89
90
|
pwaInstall,
|
|
90
91
|
monitor,
|
|
91
92
|
debug,
|
|
93
|
+
i18n,
|
|
92
94
|
}: BaseAppProps) {
|
|
93
95
|
// ErrorBoundary is enabled by default
|
|
94
96
|
const enableErrorBoundary = errorBoundary?.enabled !== false;
|
|
@@ -156,7 +158,7 @@ export function BaseApp({
|
|
|
156
158
|
onMonitorCapture={(d) => FrontendMonitor.capture(errorDetailToMonitorEvent(d))}
|
|
157
159
|
>
|
|
158
160
|
<MonitorProvider {...monitorConfig} />
|
|
159
|
-
{children}
|
|
161
|
+
<LayoutI18nProvider value={i18n}>{children}</LayoutI18nProvider>
|
|
160
162
|
<NextTopLoader
|
|
161
163
|
color="hsl(var(--primary))"
|
|
162
164
|
height={3}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayoutI18nProvider — single source of truth for locale plumbing inside layouts.
|
|
3
|
+
*
|
|
4
|
+
* Mounted by `BaseApp` when `i18n` is passed. Every locale-aware component
|
|
5
|
+
* inside layouts (footer, navbar controls, mobile drawer, private sidebar
|
|
6
|
+
* account, UserMenu, LocaleSwitcher) reads from here instead of receiving
|
|
7
|
+
* `i18n` as a prop.
|
|
8
|
+
*
|
|
9
|
+
* Components that may render outside the provider (e.g. raw shells used in
|
|
10
|
+
* playground previews) should call `useLayoutI18nOptional()` and gracefully
|
|
11
|
+
* hide locale-related UI when it returns `null`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use client';
|
|
15
|
+
|
|
16
|
+
import * as React from 'react';
|
|
17
|
+
|
|
18
|
+
import type { I18nLayoutConfig } from '../types/layout.types';
|
|
19
|
+
|
|
20
|
+
const LayoutI18nContext = React.createContext<I18nLayoutConfig | null>(null);
|
|
21
|
+
|
|
22
|
+
export interface LayoutI18nProviderProps {
|
|
23
|
+
value: I18nLayoutConfig | null | undefined;
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Provider — mount once at the app root (handled automatically by `BaseApp`).
|
|
29
|
+
* Passing `null`/`undefined` is fine: consumers fall back to "no locale switcher".
|
|
30
|
+
*/
|
|
31
|
+
export function LayoutI18nProvider({ value, children }: LayoutI18nProviderProps) {
|
|
32
|
+
// `null` keeps the context discriminator simple — `useLayoutI18nOptional`
|
|
33
|
+
// returns `null` whether the provider is missing or `value` is empty.
|
|
34
|
+
const stable = value ?? null;
|
|
35
|
+
return <LayoutI18nContext.Provider value={stable}>{children}</LayoutI18nContext.Provider>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Throws when used outside the provider. Use for components that strictly
|
|
40
|
+
* require locale plumbing (the locale picker dialog, for instance).
|
|
41
|
+
*/
|
|
42
|
+
export function useLayoutI18n(): I18nLayoutConfig {
|
|
43
|
+
const ctx = React.useContext(LayoutI18nContext);
|
|
44
|
+
if (!ctx) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'useLayoutI18n must be used inside <LayoutI18nProvider> — pass `i18n` to <BaseApp> / <AppLayout>.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return ctx;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional accessor — returns `null` outside the provider or when no `i18n`
|
|
54
|
+
* was configured. Use for opt-in mounts (footer locale switcher, navbar
|
|
55
|
+
* locale dropdown) that should silently disappear when locale support is off.
|
|
56
|
+
*/
|
|
57
|
+
export function useLayoutI18nOptional(): I18nLayoutConfig | null {
|
|
58
|
+
return React.useContext(LayoutI18nContext);
|
|
59
|
+
}
|
|
@@ -20,6 +20,13 @@ export { mergeAppLayoutPublicChrome } from './AppLayout';
|
|
|
20
20
|
export { BaseApp } from './BaseApp';
|
|
21
21
|
export type { BaseAppProps } from './BaseApp';
|
|
22
22
|
|
|
23
|
+
export {
|
|
24
|
+
LayoutI18nProvider,
|
|
25
|
+
useLayoutI18n,
|
|
26
|
+
useLayoutI18nOptional,
|
|
27
|
+
} from './LayoutI18nProvider';
|
|
28
|
+
export type { LayoutI18nProviderProps } from './LayoutI18nProvider';
|
|
29
|
+
|
|
23
30
|
// Re-export theme-override primitives so consumers can import both the
|
|
24
31
|
// AppLayout config surface and the rule/theme types from one place.
|
|
25
32
|
export {
|
|
@@ -16,7 +16,7 @@ import { Preloader } from '@djangocfg/ui-core/components';
|
|
|
16
16
|
import { SidebarInset, SidebarProvider } from '@djangocfg/ui-nextjs/components';
|
|
17
17
|
|
|
18
18
|
import type { AppLayoutPublicChrome } from '../AppLayout/AppLayout';
|
|
19
|
-
import type {
|
|
19
|
+
import type { LayoutVisualConfig } from '../types';
|
|
20
20
|
import { UserMenuConfig } from '../types';
|
|
21
21
|
import { PrivateContent, PrivateSidebar } from './components';
|
|
22
22
|
|
|
@@ -101,8 +101,6 @@ export interface PrivateLayoutProps {
|
|
|
101
101
|
pathname?: string;
|
|
102
102
|
/** Content padding */
|
|
103
103
|
contentPadding?: 'none' | 'default';
|
|
104
|
-
/** i18n configuration for locale switching */
|
|
105
|
-
i18n?: I18nLayoutConfig;
|
|
106
104
|
/**
|
|
107
105
|
* Visual style of the shell. Defaults to `'boxed'` (inset rounded card on a
|
|
108
106
|
* sidebar-coloured canvas). Pass `{ variant: 'full-bleed' }` for the legacy
|
|
@@ -124,7 +122,6 @@ export function PrivateLayout({
|
|
|
124
122
|
header,
|
|
125
123
|
pathname,
|
|
126
124
|
contentPadding = 'default',
|
|
127
|
-
i18n,
|
|
128
125
|
visual,
|
|
129
126
|
requireAuth = true,
|
|
130
127
|
}: PrivateLayoutProps) {
|
|
@@ -167,7 +164,6 @@ export function PrivateLayout({
|
|
|
167
164
|
<PrivateSidebar
|
|
168
165
|
sidebar={sidebar}
|
|
169
166
|
header={header}
|
|
170
|
-
i18n={i18n}
|
|
171
167
|
pathname={pathname}
|
|
172
168
|
variant={sidebarVariant}
|
|
173
169
|
/>
|
|
@@ -29,7 +29,6 @@ import { cn } from '@djangocfg/ui-core/lib';
|
|
|
29
29
|
import { PrivateSidebarAccount } from '../../_components/PrivateSidebarAccount';
|
|
30
30
|
import { LucideIcon } from '../../../components';
|
|
31
31
|
|
|
32
|
-
import type { I18nLayoutConfig } from '../../types';
|
|
33
32
|
import type { HeaderConfig, SidebarItem, SidebarConfig } from '../PrivateLayout';
|
|
34
33
|
|
|
35
34
|
/** Few items → roomier rows; many items → tighter. Same breakpoints for demo, CarAPIS, etc. */
|
|
@@ -99,7 +98,6 @@ const RAIL_NAV = DENSITY.default;
|
|
|
99
98
|
interface PrivateSidebarProps {
|
|
100
99
|
sidebar: SidebarConfig;
|
|
101
100
|
header?: HeaderConfig;
|
|
102
|
-
i18n?: I18nLayoutConfig;
|
|
103
101
|
pathname?: string;
|
|
104
102
|
/**
|
|
105
103
|
* shadcn-sidebar `variant`. Used to trigger the inset/boxed visual:
|
|
@@ -109,7 +107,7 @@ interface PrivateSidebarProps {
|
|
|
109
107
|
variant?: 'sidebar' | 'inset';
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
export function PrivateSidebar({ sidebar, header,
|
|
110
|
+
export function PrivateSidebar({ sidebar, header, pathname: pathnameProp, variant = 'sidebar' }: PrivateSidebarProps) {
|
|
113
111
|
const pathnameFromNext = useNextPathname();
|
|
114
112
|
const pathname = pathnameProp ?? pathnameFromNext;
|
|
115
113
|
const { state, isMobile, setOpen, setOpenMobile } = useSidebar();
|
|
@@ -324,7 +322,7 @@ export function PrivateSidebar({ sidebar, header, i18n, pathname: pathnameProp,
|
|
|
324
322
|
|
|
325
323
|
<SidebarFooter className="p-2">
|
|
326
324
|
{footerExtra}
|
|
327
|
-
<PrivateSidebarAccount header={header}
|
|
325
|
+
<PrivateSidebarAccount header={header} />
|
|
328
326
|
</SidebarFooter>
|
|
329
327
|
</Sidebar>
|
|
330
328
|
);
|
|
@@ -15,6 +15,7 @@ import { useForcedTheme, useThemeContext } from '@djangocfg/ui-nextjs/theme';
|
|
|
15
15
|
import { Link } from '@djangocfg/ui-core/components';
|
|
16
16
|
|
|
17
17
|
import { LocaleSwitcher } from '../../../_components/LocaleSwitcher';
|
|
18
|
+
import { useLayoutI18nOptional } from '../../../AppLayout/LayoutI18nProvider';
|
|
18
19
|
import { FooterMenuSections } from './FooterMenuSections';
|
|
19
20
|
import { FooterProjectInfo } from './FooterProjectInfo';
|
|
20
21
|
|
|
@@ -117,7 +118,7 @@ export function DefaultFooter({ config }: DefaultFooterProps) {
|
|
|
117
118
|
const socialLinks = config.social;
|
|
118
119
|
const copyrightProp = config.meta?.copyright;
|
|
119
120
|
const creditsProp = config.meta?.credits;
|
|
120
|
-
const i18n =
|
|
121
|
+
const i18n = useLayoutI18nOptional();
|
|
121
122
|
const showThemeSwitcher = config.controls?.showThemeSwitcher !== false;
|
|
122
123
|
const showLocaleSwitcher =
|
|
123
124
|
config.controls?.showLocaleSwitcher !== false && Boolean(i18n);
|
|
@@ -260,12 +261,9 @@ export function DefaultFooter({ config }: DefaultFooterProps) {
|
|
|
260
261
|
{showFooterControlsRow && (
|
|
261
262
|
<div className="mt-5 flex items-center justify-center gap-2 border-t border-border/60 pt-4">
|
|
262
263
|
{showThemeSwitcher && <ThemeModeControl />}
|
|
263
|
-
{showLocaleSwitcher &&
|
|
264
|
+
{showLocaleSwitcher && (
|
|
264
265
|
<LocaleSwitcher
|
|
265
|
-
|
|
266
|
-
locales={i18n.locales}
|
|
267
|
-
onChange={i18n.onLocaleChange}
|
|
268
|
-
variant="outline"
|
|
266
|
+
buttonVariant="outline"
|
|
269
267
|
size="default"
|
|
270
268
|
className="h-10 rounded-full border-border/60 bg-muted/30 text-sm hover:bg-muted/40"
|
|
271
269
|
/>
|
|
@@ -348,12 +346,9 @@ export function DefaultFooter({ config }: DefaultFooterProps) {
|
|
|
348
346
|
<div className="flex items-center justify-center gap-2 lg:justify-self-end">
|
|
349
347
|
{slots?.bottomEnd}
|
|
350
348
|
{showThemeSwitcher && <ThemeModeControl />}
|
|
351
|
-
{showLocaleSwitcher &&
|
|
349
|
+
{showLocaleSwitcher && (
|
|
352
350
|
<LocaleSwitcher
|
|
353
|
-
|
|
354
|
-
locales={i18n.locales}
|
|
355
|
-
onChange={i18n.onLocaleChange}
|
|
356
|
-
variant="outline"
|
|
351
|
+
buttonVariant="outline"
|
|
357
352
|
size="default"
|
|
358
353
|
className="h-10 rounded-full border-border/60 bg-muted/30 text-sm hover:bg-muted/40"
|
|
359
354
|
/>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
6
|
import type { ReactNode } from 'react';
|
|
7
7
|
|
|
8
|
-
import type { FooterLink, FooterMenuSection, FooterSocialLinks
|
|
8
|
+
import type { FooterLink, FooterMenuSection, FooterSocialLinks } from '../../../types';
|
|
9
9
|
|
|
10
10
|
export type { FooterLink, FooterMenuSection, FooterSocialLinks };
|
|
11
11
|
|
|
@@ -23,6 +23,15 @@ export interface DefaultFooterConfig {
|
|
|
23
23
|
icon: LucideIcon;
|
|
24
24
|
text: string;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Logo node displayed in the locale-switcher dialog header.
|
|
28
|
+
* Falls back to a generic Globe glyph when omitted.
|
|
29
|
+
*/
|
|
30
|
+
logo?: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Project name displayed beside the logo in the locale-switcher dialog.
|
|
33
|
+
*/
|
|
34
|
+
name?: string;
|
|
26
35
|
};
|
|
27
36
|
menus?: {
|
|
28
37
|
sections?: FooterMenuSection[];
|
|
@@ -38,16 +47,16 @@ export interface DefaultFooterConfig {
|
|
|
38
47
|
};
|
|
39
48
|
};
|
|
40
49
|
social?: FooterSocialLinks;
|
|
41
|
-
i18n?: I18nLayoutConfig;
|
|
42
50
|
/**
|
|
43
|
-
* Full (`variant: 'full'`) footer only — bottom row with theme and/or locale
|
|
44
|
-
*
|
|
45
|
-
*
|
|
51
|
+
* Full (`variant: 'full'`) footer only — bottom row with theme and/or locale
|
|
52
|
+
* controls. The locale switcher reads its plumbing from `LayoutI18nProvider`
|
|
53
|
+
* (mounted by `BaseApp`); when the provider is empty, the switcher hides
|
|
54
|
+
* itself regardless of this flag.
|
|
46
55
|
*/
|
|
47
56
|
controls?: {
|
|
48
57
|
/** Light / system / dark toggle. @default true */
|
|
49
58
|
showThemeSwitcher?: boolean;
|
|
50
|
-
/**
|
|
59
|
+
/** @default true */
|
|
51
60
|
showLocaleSwitcher?: boolean;
|
|
52
61
|
};
|
|
53
62
|
/**
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
PublicNavbarShellConfig,
|
|
22
22
|
PublicNavLayout,
|
|
23
23
|
} from '../../navbarTypes';
|
|
24
|
-
import type {
|
|
24
|
+
import type { NavigationItem, UserMenuConfig } from '../../../types';
|
|
25
25
|
|
|
26
26
|
import { FloatingMobileDrawer } from './FloatingMobileDrawer';
|
|
27
27
|
|
|
@@ -65,8 +65,6 @@ export interface FloatingNavbarConfig {
|
|
|
65
65
|
/** Locale dropdown. Requires `i18n`. @default false */
|
|
66
66
|
showLocaleSwitcher?: boolean;
|
|
67
67
|
};
|
|
68
|
-
/** i18n config (current locale + locales + onLocaleChange). */
|
|
69
|
-
i18n?: I18nLayoutConfig;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
export interface FloatingNavbarProps {
|
|
@@ -113,7 +111,6 @@ export function FloatingNavbar({ config }: FloatingNavbarProps) {
|
|
|
113
111
|
actionsLeadingSlot={config.actionsLeadingSlot}
|
|
114
112
|
actionsTrailingSlot={config.actionsTrailingSlot}
|
|
115
113
|
controls={config.controls}
|
|
116
|
-
i18n={config.i18n}
|
|
117
114
|
outerClassName={outerClassName}
|
|
118
115
|
shapeClassName={shapeClassName}
|
|
119
116
|
shapeForState={({ scrolled, transparent }) =>
|
|
@@ -131,7 +128,6 @@ export function FloatingNavbar({ config }: FloatingNavbarProps) {
|
|
|
131
128
|
containerClassName={containerClassName}
|
|
132
129
|
rounding={rounding}
|
|
133
130
|
controls={config.controls}
|
|
134
|
-
i18n={config.i18n}
|
|
135
131
|
/>
|
|
136
132
|
</>
|
|
137
133
|
);
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
PublicNavbarShellConfig,
|
|
21
21
|
PublicNavLayout,
|
|
22
22
|
} from '../../navbarTypes';
|
|
23
|
-
import type {
|
|
23
|
+
import type { NavigationItem, UserMenuConfig } from '../../../types';
|
|
24
24
|
|
|
25
25
|
import { FlushMobileDrawer } from './FlushMobileDrawer';
|
|
26
26
|
|
|
@@ -64,8 +64,6 @@ export interface FlushNavbarConfig {
|
|
|
64
64
|
/** Locale dropdown. Requires `i18n`. @default false */
|
|
65
65
|
showLocaleSwitcher?: boolean;
|
|
66
66
|
};
|
|
67
|
-
/** i18n config (current locale + locales + onLocaleChange). */
|
|
68
|
-
i18n?: I18nLayoutConfig;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
export interface FlushNavbarProps {
|
|
@@ -109,7 +107,6 @@ export function FlushNavbar({ config }: FlushNavbarProps) {
|
|
|
109
107
|
actionsLeadingSlot={config.actionsLeadingSlot}
|
|
110
108
|
actionsTrailingSlot={config.actionsTrailingSlot}
|
|
111
109
|
controls={config.controls}
|
|
112
|
-
i18n={config.i18n}
|
|
113
110
|
outerClassName={outerClassName}
|
|
114
111
|
shapeClassName={shapeClassName}
|
|
115
112
|
shapeForState={({ scrolled, transparent }) =>
|
|
@@ -126,7 +123,6 @@ export function FlushNavbar({ config }: FlushNavbarProps) {
|
|
|
126
123
|
userMenu={config.userMenu}
|
|
127
124
|
containerClassName={containerClassName}
|
|
128
125
|
controls={config.controls}
|
|
129
|
-
i18n={config.i18n}
|
|
130
126
|
/>
|
|
131
127
|
</>
|
|
132
128
|
);
|
|
@@ -26,7 +26,7 @@ import type {
|
|
|
26
26
|
PublicNavbarPosition,
|
|
27
27
|
PublicNavLayout,
|
|
28
28
|
} from '../../navbarTypes';
|
|
29
|
-
import type {
|
|
29
|
+
import type { NavigationItem, UserMenuConfig } from '../../../types';
|
|
30
30
|
|
|
31
31
|
import { MinimalMobileDrawer } from './MinimalMobileDrawer';
|
|
32
32
|
|
|
@@ -81,8 +81,6 @@ export interface MinimalNavbarConfig {
|
|
|
81
81
|
/** Locale dropdown. Requires `i18n`. @default false */
|
|
82
82
|
showLocaleSwitcher?: boolean;
|
|
83
83
|
};
|
|
84
|
-
/** i18n config (current locale + locales + onLocaleChange). */
|
|
85
|
-
i18n?: I18nLayoutConfig;
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
export interface MinimalNavbarProps {
|
|
@@ -115,7 +113,6 @@ function MinimalActions({
|
|
|
115
113
|
variant="desktop"
|
|
116
114
|
groups={ctx.userMenu?.groups}
|
|
117
115
|
authPath={ctx.userMenu?.authPath}
|
|
118
|
-
i18n={ctx.userMenu?.i18n}
|
|
119
116
|
/>
|
|
120
117
|
</div>
|
|
121
118
|
|
|
@@ -168,7 +165,6 @@ export function MinimalNavbar({ config }: MinimalNavbarProps) {
|
|
|
168
165
|
transparent={transparent}
|
|
169
166
|
transparentThreshold={config.transparentThreshold}
|
|
170
167
|
controls={config.controls}
|
|
171
|
-
i18n={config.i18n}
|
|
172
168
|
outerClassName={outerClassName}
|
|
173
169
|
shapeClassName={shapeClassName}
|
|
174
170
|
innerPadding={containerClassName}
|
|
@@ -187,7 +183,6 @@ export function MinimalNavbar({ config }: MinimalNavbarProps) {
|
|
|
187
183
|
userMenu={config.userMenu}
|
|
188
184
|
containerClassName={containerClassName}
|
|
189
185
|
controls={config.controls}
|
|
190
|
-
i18n={config.i18n}
|
|
191
186
|
/>
|
|
192
187
|
</>
|
|
193
188
|
);
|
|
@@ -7,12 +7,10 @@ import { Button } from '@djangocfg/ui-core/components';
|
|
|
7
7
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
8
8
|
import { useThemeContext } from '@djangocfg/ui-nextjs/theme';
|
|
9
9
|
|
|
10
|
-
import type { I18nLayoutConfig } from '../../types';
|
|
11
10
|
import { LocaleSwitcher } from '../../_components/LocaleSwitcher';
|
|
11
|
+
import { useLayoutI18nOptional } from '../../AppLayout/LayoutI18nProvider';
|
|
12
12
|
|
|
13
13
|
export interface NavControlsProps {
|
|
14
|
-
/** Optional i18n config. Required to render the locale switcher. */
|
|
15
|
-
i18n?: I18nLayoutConfig;
|
|
16
14
|
/** Show the theme (light / system / dark) pill. @default false */
|
|
17
15
|
showThemeSwitcher?: boolean;
|
|
18
16
|
/** Show the locale dropdown. Requires `i18n`. @default false */
|
|
@@ -81,12 +79,12 @@ function ThemeModeControl({ size }: { size: 'compact' | 'default' }) {
|
|
|
81
79
|
* to a navbar config.
|
|
82
80
|
*/
|
|
83
81
|
export function NavControls({
|
|
84
|
-
i18n,
|
|
85
82
|
showThemeSwitcher = false,
|
|
86
83
|
showLocaleSwitcher = false,
|
|
87
84
|
size = 'compact',
|
|
88
85
|
className,
|
|
89
86
|
}: NavControlsProps) {
|
|
87
|
+
const i18n = useLayoutI18nOptional();
|
|
90
88
|
const renderLocale = showLocaleSwitcher && Boolean(i18n);
|
|
91
89
|
if (!showThemeSwitcher && !renderLocale) return null;
|
|
92
90
|
|
|
@@ -98,12 +96,10 @@ export function NavControls({
|
|
|
98
96
|
return (
|
|
99
97
|
<div className={cn('inline-flex items-center gap-1.5', className)}>
|
|
100
98
|
{showThemeSwitcher && <ThemeModeControl size={size} />}
|
|
101
|
-
{renderLocale &&
|
|
99
|
+
{renderLocale && (
|
|
102
100
|
<LocaleSwitcher
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
onChange={i18n.onLocaleChange}
|
|
106
|
-
variant="outline"
|
|
101
|
+
variant="dialog"
|
|
102
|
+
buttonVariant="outline"
|
|
107
103
|
size={size === 'compact' ? 'sm' : 'default'}
|
|
108
104
|
showTriggerLabel={false}
|
|
109
105
|
className={localeBtnClass}
|
|
@@ -22,7 +22,7 @@ import { usePublicLayoutOptional } from '../context';
|
|
|
22
22
|
import { useMobileNavPanel } from '../hooks';
|
|
23
23
|
import { NavControls } from '../primitives/NavControls';
|
|
24
24
|
|
|
25
|
-
import type {
|
|
25
|
+
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
26
26
|
|
|
27
27
|
export interface MobileDrawerShellProps {
|
|
28
28
|
isOpen?: boolean;
|
|
@@ -38,8 +38,6 @@ export interface MobileDrawerShellProps {
|
|
|
38
38
|
showThemeSwitcher?: boolean;
|
|
39
39
|
showLocaleSwitcher?: boolean;
|
|
40
40
|
};
|
|
41
|
-
/** i18n config — required for the locale switcher row. */
|
|
42
|
-
i18n?: I18nLayoutConfig;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export function MobileDrawerShell(props: MobileDrawerShellProps) {
|
|
@@ -81,8 +79,7 @@ export function MobileDrawerShell(props: MobileDrawerShellProps) {
|
|
|
81
79
|
const hasSessionUser = Boolean(isAuthenticated && user);
|
|
82
80
|
const showSignInFooter = !hasSessionUser;
|
|
83
81
|
const showThemeSwitcher = props.controls?.showThemeSwitcher === true;
|
|
84
|
-
const showLocaleSwitcher =
|
|
85
|
-
props.controls?.showLocaleSwitcher === true && Boolean(props.i18n);
|
|
82
|
+
const showLocaleSwitcher = props.controls?.showLocaleSwitcher === true;
|
|
86
83
|
const showControlsRow = showThemeSwitcher || showLocaleSwitcher;
|
|
87
84
|
|
|
88
85
|
return (
|
|
@@ -199,7 +196,6 @@ export function MobileDrawerShell(props: MobileDrawerShellProps) {
|
|
|
199
196
|
{showControlsRow && (
|
|
200
197
|
<div className="shrink-0 border-t border-border/50 px-4 py-3 flex items-center justify-center gap-2">
|
|
201
198
|
<NavControls
|
|
202
|
-
i18n={props.i18n}
|
|
203
199
|
showThemeSwitcher={showThemeSwitcher}
|
|
204
200
|
showLocaleSwitcher={showLocaleSwitcher}
|
|
205
201
|
size="default"
|
|
@@ -50,8 +50,6 @@ import { NavBrand } from '../primitives/NavBrand';
|
|
|
50
50
|
import { NavControls } from '../primitives/NavControls';
|
|
51
51
|
import { NavDesktopItems } from '../primitives/NavDesktopItems';
|
|
52
52
|
|
|
53
|
-
import type { I18nLayoutConfig } from '../../types';
|
|
54
|
-
|
|
55
53
|
const heightCls: Record<PublicNavbarHeight, string> = {
|
|
56
54
|
sm: 'py-2',
|
|
57
55
|
md: 'py-3.5',
|
|
@@ -136,8 +134,6 @@ export interface NavbarShellProps {
|
|
|
136
134
|
actionsTrailingSlot?: ReactNode;
|
|
137
135
|
|
|
138
136
|
// ── Theme + locale controls (rendered next to UserMenu on desktop) ────────
|
|
139
|
-
/** i18n config — enables the locale switcher. Same type as `DefaultFooter`. */
|
|
140
|
-
i18n?: I18nLayoutConfig;
|
|
141
137
|
/** Toggle individual controls. Locale switcher also requires `i18n`. */
|
|
142
138
|
controls?: {
|
|
143
139
|
/** Light / system / dark pill. @default false */
|
|
@@ -267,12 +263,10 @@ export function NavbarShell(props: NavbarShellProps) {
|
|
|
267
263
|
) : null;
|
|
268
264
|
|
|
269
265
|
const showThemeSwitcher = props.controls?.showThemeSwitcher === true;
|
|
270
|
-
const showLocaleSwitcher =
|
|
271
|
-
props.controls?.showLocaleSwitcher === true && Boolean(props.i18n);
|
|
266
|
+
const showLocaleSwitcher = props.controls?.showLocaleSwitcher === true;
|
|
272
267
|
const hasControls = showThemeSwitcher || showLocaleSwitcher;
|
|
273
268
|
const controlsNode = hasControls ? (
|
|
274
269
|
<NavControls
|
|
275
|
-
i18n={props.i18n}
|
|
276
270
|
showThemeSwitcher={showThemeSwitcher}
|
|
277
271
|
showLocaleSwitcher={showLocaleSwitcher}
|
|
278
272
|
/>
|