@djangocfg/layouts 2.1.249 → 2.1.251
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 +11 -9
- package/package.json +18 -18
- package/src/index.ts +4 -2
- package/src/layouts/AppLayout/AppLayout.tsx +70 -13
- package/src/layouts/AppLayout/index.ts +7 -1
- package/src/layouts/PublicLayout/PublicLayout.tsx +27 -42
- package/src/layouts/PublicLayout/components/PublicFooter/FooterMenuSections.tsx +13 -2
- package/src/layouts/PublicLayout/components/PublicFooter/FooterProjectInfo.tsx +7 -5
- package/src/layouts/PublicLayout/components/PublicFooter/PublicFooter.tsx +130 -109
- package/src/layouts/PublicLayout/components/PublicFooter/types.ts +9 -4
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +32 -8
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +74 -0
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +274 -113
- package/src/layouts/PublicLayout/components/index.ts +1 -0
- package/src/layouts/PublicLayout/context.tsx +0 -9
- package/src/layouts/PublicLayout/index.ts +8 -1
|
@@ -12,7 +12,6 @@ import React, { useEffect, useState } from 'react';
|
|
|
12
12
|
import { Laptop, Moon, Sun } from 'lucide-react';
|
|
13
13
|
|
|
14
14
|
import { Button } from '@djangocfg/ui-core/components';
|
|
15
|
-
import { useIsMobile } from '@djangocfg/ui-core/hooks';
|
|
16
15
|
import { useThemeContext } from '@djangocfg/ui-nextjs/theme';
|
|
17
16
|
|
|
18
17
|
import { LocaleSwitcher } from '../../../_components/LocaleSwitcher';
|
|
@@ -71,33 +70,36 @@ function ThemeModeControl() {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
export function PublicFooter({
|
|
74
|
-
|
|
73
|
+
projectInfo,
|
|
75
74
|
description,
|
|
76
|
-
logo,
|
|
77
75
|
badge,
|
|
78
76
|
socialLinks,
|
|
79
77
|
links = [],
|
|
80
78
|
menuSections = [],
|
|
79
|
+
showProjectInfo = true,
|
|
80
|
+
menuColumnMinWidth = 180,
|
|
81
|
+
menuMaxColumns = 5,
|
|
81
82
|
copyright: copyrightProp,
|
|
82
83
|
credits: creditsProp,
|
|
83
84
|
variant = 'full',
|
|
84
85
|
containerClassName,
|
|
85
86
|
i18n,
|
|
86
87
|
}: PublicFooterProps) {
|
|
87
|
-
const isMobile = useIsMobile();
|
|
88
|
-
|
|
89
88
|
// Prepare data BEFORE render
|
|
90
89
|
const currentYear = new Date().getFullYear();
|
|
91
|
-
const copyright = copyrightProp || `© ${currentYear}
|
|
92
|
-
const credits = creditsProp
|
|
93
|
-
text: 'Built with DjangoCFG',
|
|
94
|
-
url: 'https://djangocfg.com',
|
|
95
|
-
};
|
|
90
|
+
const copyright = copyrightProp || `© ${currentYear}. All rights reserved.`;
|
|
91
|
+
const credits = creditsProp;
|
|
96
92
|
|
|
97
93
|
// Simple variant - minimal footer
|
|
98
94
|
if (variant === 'simple') {
|
|
99
95
|
return (
|
|
100
|
-
<footer
|
|
96
|
+
<footer
|
|
97
|
+
className="border-t border-border/60 mt-auto"
|
|
98
|
+
style={{
|
|
99
|
+
background: 'linear-gradient(to bottom, hsl(var(--accent) / 0.26), hsl(var(--background) / 0.97) 42%, hsl(var(--accent) / 0.34))',
|
|
100
|
+
boxShadow: 'inset 0 1px 0 hsl(var(--border) / 0.75), inset 0 14px 26px hsl(var(--accent) / 0.18)',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
101
103
|
<div className={`mx-auto px-4 py-4 ${containerClassName || 'w-full'}`}>
|
|
102
104
|
<div className="text-center">
|
|
103
105
|
<div className="text-sm text-muted-foreground">{copyright}</div>
|
|
@@ -110,17 +112,22 @@ export function PublicFooter({
|
|
|
110
112
|
// Compact variant - single line with logo and links
|
|
111
113
|
if (variant === 'compact') {
|
|
112
114
|
return (
|
|
113
|
-
<footer
|
|
115
|
+
<footer
|
|
116
|
+
className="border-t border-border/60 mt-auto"
|
|
117
|
+
style={{
|
|
118
|
+
background: 'linear-gradient(to bottom, hsl(var(--accent) / 0.26), hsl(var(--background) / 0.97) 42%, hsl(var(--accent) / 0.34))',
|
|
119
|
+
boxShadow: 'inset 0 1px 0 hsl(var(--border) / 0.75), inset 0 14px 26px hsl(var(--accent) / 0.18)',
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
114
122
|
<div className={`mx-auto px-4 sm:px-6 lg:px-8 py-8 ${containerClassName || 'w-full'}`}>
|
|
115
123
|
{/* Main row: logo left, links right */}
|
|
116
124
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-6">
|
|
117
125
|
{/* Logo + Name */}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
</div>
|
|
126
|
+
{projectInfo ?? (
|
|
127
|
+
<div className="flex items-center gap-3">
|
|
128
|
+
<span className="text-lg font-semibold text-foreground">Project</span>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
124
131
|
|
|
125
132
|
{/* Links */}
|
|
126
133
|
{links.length > 0 && (
|
|
@@ -153,17 +160,19 @@ export function PublicFooter({
|
|
|
153
160
|
{/* Bottom row: copyright + credits */}
|
|
154
161
|
<div className="mt-6 pt-6 border-t border-border flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-muted-foreground">
|
|
155
162
|
<span>{copyright}</span>
|
|
156
|
-
{credits
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
{credits && (
|
|
164
|
+
credits.url ? (
|
|
165
|
+
<a
|
|
166
|
+
href={credits.url}
|
|
167
|
+
target="_blank"
|
|
168
|
+
rel="noopener noreferrer"
|
|
169
|
+
className="hover:text-primary transition-colors"
|
|
170
|
+
>
|
|
171
|
+
{credits.text}
|
|
172
|
+
</a>
|
|
173
|
+
) : (
|
|
174
|
+
<span>{credits.text}</span>
|
|
175
|
+
)
|
|
167
176
|
)}
|
|
168
177
|
</div>
|
|
169
178
|
</div>
|
|
@@ -171,18 +180,25 @@ export function PublicFooter({
|
|
|
171
180
|
);
|
|
172
181
|
}
|
|
173
182
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
<footer
|
|
186
|
+
className="lg:hidden border-t border-border/60 mt-auto"
|
|
187
|
+
style={{
|
|
188
|
+
background: 'linear-gradient(to bottom, hsl(var(--accent) / 0.26), hsl(var(--background) / 0.97) 42%, hsl(var(--accent) / 0.34))',
|
|
189
|
+
boxShadow: 'inset 0 1px 0 hsl(var(--border) / 0.75), inset 0 14px 26px hsl(var(--accent) / 0.18)',
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
178
192
|
<div className={`mx-auto px-4 py-8 ${containerClassName || 'w-full'}`}>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
{showProjectInfo && (
|
|
194
|
+
projectInfo ?? (
|
|
195
|
+
<FooterProjectInfo
|
|
196
|
+
description={description}
|
|
197
|
+
socialLinks={socialLinks}
|
|
198
|
+
variant="mobile"
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
)}
|
|
186
202
|
|
|
187
203
|
{/* Quick Links */}
|
|
188
204
|
{links.length > 0 && (
|
|
@@ -213,15 +229,17 @@ export function PublicFooter({
|
|
|
213
229
|
|
|
214
230
|
<div className="border-t border-border mt-6 pt-4 space-y-3">
|
|
215
231
|
<div className="text-xs text-muted-foreground text-center">{copyright}</div>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{credits.text
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
232
|
+
{credits && (
|
|
233
|
+
<div className="text-xs text-muted-foreground text-center">
|
|
234
|
+
{credits.url ? (
|
|
235
|
+
<a href={credits.url} target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors">
|
|
236
|
+
{credits.text}
|
|
237
|
+
</a>
|
|
238
|
+
) : (
|
|
239
|
+
credits.text
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
225
243
|
</div>
|
|
226
244
|
|
|
227
245
|
<div className="mt-5 pt-4 border-t border-border/60 flex items-center justify-center gap-2">
|
|
@@ -239,69 +257,72 @@ export function PublicFooter({
|
|
|
239
257
|
</div>
|
|
240
258
|
</div>
|
|
241
259
|
</footer>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
<footer
|
|
261
|
+
className="max-lg:hidden border-t border-border/60 mt-auto"
|
|
262
|
+
style={{
|
|
263
|
+
background: 'linear-gradient(to bottom, hsl(var(--accent) / 0.26), hsl(var(--background) / 0.97) 42%, hsl(var(--accent) / 0.34))',
|
|
264
|
+
boxShadow: 'inset 0 1px 0 hsl(var(--border) / 0.75), inset 0 14px 26px hsl(var(--accent) / 0.18)',
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
<div className={`mx-auto px-6 sm:px-8 lg:px-10 py-14 ${containerClassName || 'w-full'}`}>
|
|
268
|
+
<div className="grid grid-cols-12 gap-10 lg:gap-14">
|
|
269
|
+
{showProjectInfo && (
|
|
270
|
+
<div className="col-span-12 lg:col-span-4">
|
|
271
|
+
{projectInfo ?? (
|
|
272
|
+
<FooterProjectInfo
|
|
273
|
+
description={description}
|
|
274
|
+
badge={badge}
|
|
275
|
+
socialLinks={socialLinks}
|
|
276
|
+
variant="desktop"
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
263
281
|
|
|
264
|
-
|
|
265
|
-
|
|
282
|
+
<div className={showProjectInfo ? 'col-span-12 lg:col-span-8 lg:pl-8' : 'col-span-12'}>
|
|
283
|
+
<FooterMenuSections
|
|
284
|
+
menuSections={menuSections}
|
|
285
|
+
minColumnWidth={menuColumnMinWidth}
|
|
286
|
+
maxColumns={menuMaxColumns}
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
266
289
|
</div>
|
|
267
|
-
</div>
|
|
268
290
|
|
|
269
|
-
|
|
270
|
-
|
|
291
|
+
<div className="mt-12 pt-5 border-t border-border/60 grid grid-cols-1 lg:grid-cols-[1fr_auto_1fr] items-center gap-4 lg:gap-6">
|
|
292
|
+
<div className="text-xs text-muted-foreground text-center lg:text-left lg:justify-self-start">
|
|
293
|
+
{copyright}
|
|
294
|
+
</div>
|
|
271
295
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
296
|
+
<div className="min-w-0 flex items-center justify-center gap-3 text-xs text-muted-foreground flex-wrap">
|
|
297
|
+
{credits && (
|
|
298
|
+
credits.url ? (
|
|
299
|
+
<a href={credits.url} target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors whitespace-nowrap">
|
|
300
|
+
{credits.text}
|
|
301
|
+
</a>
|
|
302
|
+
) : (
|
|
303
|
+
<span className="whitespace-nowrap">{credits.text}</span>
|
|
304
|
+
)
|
|
305
|
+
)}
|
|
306
|
+
{links.length > 0 && links.map((link) =>
|
|
307
|
+
link.external ? (
|
|
308
|
+
<a
|
|
309
|
+
key={link.path}
|
|
310
|
+
href={link.path}
|
|
311
|
+
target="_blank"
|
|
312
|
+
rel="noopener noreferrer"
|
|
313
|
+
className="hover:text-foreground transition-colors whitespace-nowrap"
|
|
314
|
+
>
|
|
315
|
+
{link.label}
|
|
316
|
+
</a>
|
|
317
|
+
) : (
|
|
318
|
+
<Link key={link.path} href={link.path} className="hover:text-foreground transition-colors whitespace-nowrap">
|
|
319
|
+
{link.label}
|
|
320
|
+
</Link>
|
|
321
|
+
)
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
281
324
|
|
|
282
|
-
|
|
283
|
-
{links.length > 0 && (
|
|
284
|
-
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
285
|
-
{links.map((link) =>
|
|
286
|
-
link.external ? (
|
|
287
|
-
<a
|
|
288
|
-
key={link.path}
|
|
289
|
-
href={link.path}
|
|
290
|
-
target="_blank"
|
|
291
|
-
rel="noopener noreferrer"
|
|
292
|
-
className="hover:text-foreground transition-colors whitespace-nowrap"
|
|
293
|
-
>
|
|
294
|
-
{link.label}
|
|
295
|
-
</a>
|
|
296
|
-
) : (
|
|
297
|
-
<Link key={link.path} href={link.path} className="hover:text-foreground transition-colors whitespace-nowrap">
|
|
298
|
-
{link.label}
|
|
299
|
-
</Link>
|
|
300
|
-
)
|
|
301
|
-
)}
|
|
302
|
-
</div>
|
|
303
|
-
)}
|
|
304
|
-
<div className="flex items-center gap-2">
|
|
325
|
+
<div className="flex items-center justify-center lg:justify-self-end gap-2">
|
|
305
326
|
<ThemeModeControl />
|
|
306
327
|
{i18n && (
|
|
307
328
|
<LocaleSwitcher
|
|
@@ -316,7 +337,7 @@ export function PublicFooter({
|
|
|
316
337
|
</div>
|
|
317
338
|
</div>
|
|
318
339
|
</div>
|
|
319
|
-
</
|
|
320
|
-
|
|
340
|
+
</footer>
|
|
341
|
+
</>
|
|
321
342
|
);
|
|
322
343
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
6
7
|
import type { I18nLayoutConfig } from '../../../AppLayout/AppLayout';
|
|
7
8
|
|
|
8
9
|
export interface FooterLink {
|
|
@@ -29,12 +30,10 @@ export interface FooterSocialLinks {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface PublicFooterProps {
|
|
32
|
-
/**
|
|
33
|
-
|
|
33
|
+
/** Custom project/brand block node (full control over footer brand area) */
|
|
34
|
+
projectInfo?: ReactNode;
|
|
34
35
|
/** Project description */
|
|
35
36
|
description?: string;
|
|
36
|
-
/** Logo path or URL */
|
|
37
|
-
logo?: string;
|
|
38
37
|
/** Optional badge */
|
|
39
38
|
badge?: {
|
|
40
39
|
icon: LucideIcon;
|
|
@@ -46,6 +45,12 @@ export interface PublicFooterProps {
|
|
|
46
45
|
links?: FooterLink[];
|
|
47
46
|
/** Footer menu sections (desktop grid) */
|
|
48
47
|
menuSections?: FooterMenuSection[];
|
|
48
|
+
/** Show brand block (logo + project info) in footer */
|
|
49
|
+
showProjectInfo?: boolean;
|
|
50
|
+
/** Minimum width in pixels for each footer menu column */
|
|
51
|
+
menuColumnMinWidth?: number;
|
|
52
|
+
/** Soft max column count for menu grid */
|
|
53
|
+
menuMaxColumns?: number;
|
|
49
54
|
/** Copyright text (auto-generated if not provided) */
|
|
50
55
|
copyright?: string;
|
|
51
56
|
/** Credits */
|
|
@@ -13,6 +13,7 @@ import React, { useMemo } from 'react';
|
|
|
13
13
|
import { useAuth } from '@djangocfg/api/auth';
|
|
14
14
|
import { useAppT } from '@djangocfg/i18n';
|
|
15
15
|
import { Button } from '@djangocfg/ui-core/components';
|
|
16
|
+
import { usePathnameWithoutLocale } from '../../../hooks';
|
|
16
17
|
|
|
17
18
|
import { UserMenu } from '../../_components/UserMenu';
|
|
18
19
|
import { usePublicLayoutOptional } from '../context';
|
|
@@ -32,10 +33,11 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
32
33
|
const context = usePublicLayoutOptional();
|
|
33
34
|
const mobileMenuOpen = props.isOpen ?? context?.mobileMenuOpen ?? false;
|
|
34
35
|
const closeMobileMenu = props.onClose ?? context?.closeMobileMenu ?? (() => {});
|
|
35
|
-
const navigation = props.navigation ??
|
|
36
|
-
const userMenu = props.userMenu
|
|
37
|
-
const containerClassName = props.containerClassName
|
|
36
|
+
const navigation = props.navigation ?? [];
|
|
37
|
+
const userMenu = props.userMenu;
|
|
38
|
+
const containerClassName = props.containerClassName;
|
|
38
39
|
const { isAuthenticated } = useAuth();
|
|
40
|
+
const pathname = usePathnameWithoutLocale();
|
|
39
41
|
const t = useAppT();
|
|
40
42
|
const { isRendered, isActive, onTransitionEnd } = useFloatingPanel({
|
|
41
43
|
isOpen: mobileMenuOpen,
|
|
@@ -48,6 +50,17 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
48
50
|
signIn: t('layouts.profile.login'),
|
|
49
51
|
}), [t]);
|
|
50
52
|
|
|
53
|
+
const mobileNavigation = useMemo(() => {
|
|
54
|
+
const hasHome = navigation.some((item) => item.href === '/');
|
|
55
|
+
if (hasHome) return navigation;
|
|
56
|
+
return [{ label: 'Home', href: '/' }, ...navigation];
|
|
57
|
+
}, [navigation]);
|
|
58
|
+
|
|
59
|
+
const isActivePath = (href: string) => {
|
|
60
|
+
if (href === '/') return pathname === '/';
|
|
61
|
+
return pathname === href || pathname.startsWith(`${href}/`);
|
|
62
|
+
};
|
|
63
|
+
|
|
51
64
|
if (!isRendered) return null;
|
|
52
65
|
|
|
53
66
|
return (
|
|
@@ -60,15 +73,22 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
60
73
|
onClick={closeMobileMenu}
|
|
61
74
|
/>
|
|
62
75
|
)}
|
|
63
|
-
<div
|
|
76
|
+
<div
|
|
77
|
+
className="fixed inset-x-0 z-1000 lg:hidden px-4 sm:px-6 lg:px-8"
|
|
78
|
+
style={{ top: 'var(--public-navbar-mobile-drawer-top, 5rem)' }}
|
|
79
|
+
>
|
|
64
80
|
<div
|
|
65
81
|
onTransitionEnd={onTransitionEnd}
|
|
66
|
-
className={`mx-auto w-full
|
|
82
|
+
className={`mx-auto w-full rounded-2xl border border-border/60 bg-background/95 backdrop-blur-xl shadow-2xl overflow-hidden flex flex-col transform-gpu will-change-transform transition-[transform,opacity] duration-[220ms] ease-out ${containerClassName || ''} ${
|
|
67
83
|
isActive
|
|
68
84
|
? 'opacity-100 translate-y-0 scale-100'
|
|
69
85
|
: 'opacity-0 -translate-y-2 scale-[0.985] pointer-events-none'
|
|
70
86
|
}`}
|
|
71
|
-
style={{
|
|
87
|
+
style={{
|
|
88
|
+
maxHeight: 'min(var(--public-navbar-mobile-drawer-max-height, calc(100dvh - 5rem - 12px)), calc(100dvh - 12px))',
|
|
89
|
+
backgroundColor: 'hsl(var(--background) / 0.72)',
|
|
90
|
+
backdropFilter: 'blur(10px)',
|
|
91
|
+
}}
|
|
72
92
|
>
|
|
73
93
|
{/* Scrollable content */}
|
|
74
94
|
<div className="flex-1 min-h-0 overflow-y-auto px-4 py-4 space-y-5">
|
|
@@ -92,12 +112,16 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
92
112
|
</h3>
|
|
93
113
|
</div>
|
|
94
114
|
<div className="space-y-1">
|
|
95
|
-
{
|
|
115
|
+
{mobileNavigation.map((item) => (
|
|
96
116
|
<div key={item.href}>
|
|
97
117
|
<Link
|
|
98
118
|
href={item.href}
|
|
99
119
|
onClick={closeMobileMenu}
|
|
100
|
-
className=
|
|
120
|
+
className={`block px-3 py-2.5 rounded-lg text-[15px] font-medium transition-colors ${
|
|
121
|
+
isActivePath(item.href)
|
|
122
|
+
? 'bg-accent/55 text-foreground'
|
|
123
|
+
: 'text-foreground hover:bg-accent/70 hover:text-accent-foreground'
|
|
124
|
+
}`}
|
|
101
125
|
>
|
|
102
126
|
{item.label}
|
|
103
127
|
</Link>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { PublicMobileDrawer } from './PublicMobileDrawer';
|
|
6
|
+
import { PublicNavigation } from './PublicNavigation';
|
|
7
|
+
|
|
8
|
+
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
9
|
+
import type {
|
|
10
|
+
PublicDesktopDropdownRenderer,
|
|
11
|
+
PublicNavbarPosition,
|
|
12
|
+
PublicNavbarVariant,
|
|
13
|
+
} from './PublicNavigation';
|
|
14
|
+
|
|
15
|
+
export interface PublicNavbarConfig {
|
|
16
|
+
brand?: React.ReactNode;
|
|
17
|
+
brandHref?: string;
|
|
18
|
+
logo?: string;
|
|
19
|
+
siteName?: string;
|
|
20
|
+
navigation?: NavigationItem[];
|
|
21
|
+
userMenu?: UserMenuConfig;
|
|
22
|
+
containerClassName?: string;
|
|
23
|
+
navbarVariant?: PublicNavbarVariant;
|
|
24
|
+
navbarPosition?: PublicNavbarPosition;
|
|
25
|
+
renderDesktopDropdown?: PublicDesktopDropdownRenderer;
|
|
26
|
+
desktopMaxPrimaryItems?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PublicNavbarProps extends PublicNavbarConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Preferred API: pass everything as a single config object.
|
|
32
|
+
* Flat props are still supported and override `config`.
|
|
33
|
+
*/
|
|
34
|
+
config?: PublicNavbarConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function PublicNavbar(props: PublicNavbarProps = {}) {
|
|
38
|
+
const cfg = props.config ?? {};
|
|
39
|
+
const brand = props.brand ?? cfg.brand;
|
|
40
|
+
const brandHref = props.brandHref ?? cfg.brandHref;
|
|
41
|
+
const logo = props.logo ?? cfg.logo;
|
|
42
|
+
const siteName = props.siteName ?? cfg.siteName;
|
|
43
|
+
const navigation = props.navigation ?? cfg.navigation ?? [];
|
|
44
|
+
const userMenu = props.userMenu ?? cfg.userMenu;
|
|
45
|
+
const containerClassName = props.containerClassName ?? cfg.containerClassName;
|
|
46
|
+
const navbarVariant = props.navbarVariant ?? cfg.navbarVariant;
|
|
47
|
+
const navbarPosition = props.navbarPosition ?? cfg.navbarPosition;
|
|
48
|
+
const renderDesktopDropdown = props.renderDesktopDropdown ?? cfg.renderDesktopDropdown;
|
|
49
|
+
const desktopMaxPrimaryItems = props.desktopMaxPrimaryItems ?? cfg.desktopMaxPrimaryItems;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<PublicNavigation
|
|
54
|
+
brand={brand}
|
|
55
|
+
brandHref={brandHref}
|
|
56
|
+
logo={logo}
|
|
57
|
+
siteName={siteName}
|
|
58
|
+
navigation={navigation}
|
|
59
|
+
userMenu={userMenu}
|
|
60
|
+
containerClassName={containerClassName}
|
|
61
|
+
navbarVariant={navbarVariant}
|
|
62
|
+
navbarPosition={navbarPosition}
|
|
63
|
+
renderDesktopDropdown={renderDesktopDropdown}
|
|
64
|
+
desktopMaxPrimaryItems={desktopMaxPrimaryItems}
|
|
65
|
+
/>
|
|
66
|
+
<PublicMobileDrawer
|
|
67
|
+
navigation={navigation}
|
|
68
|
+
userMenu={userMenu}
|
|
69
|
+
containerClassName={containerClassName}
|
|
70
|
+
/>
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|