@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.
- package/README.md +52 -180
- package/package.json +18 -18
- package/src/layouts/AppLayout/AppLayout.tsx +14 -14
- package/src/layouts/PublicLayout/README.md +144 -0
- package/src/layouts/PublicLayout/{components/PublicFooter/PublicFooter.tsx → footers/DefaultFooter/DefaultFooter.tsx} +21 -15
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/DjangoCFGLogo.tsx +0 -6
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterBottom.tsx +3 -7
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterMenuSections.tsx +4 -7
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterProjectInfo.tsx +0 -4
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterSocialLinks.tsx +0 -5
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/index.ts +2 -12
- package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/types.ts +21 -26
- package/src/layouts/PublicLayout/footers/index.ts +1 -0
- package/src/layouts/PublicLayout/hooks/index.ts +1 -0
- package/src/layouts/PublicLayout/hooks/useResponsiveOverflow.ts +140 -0
- package/src/layouts/PublicLayout/index.ts +38 -20
- package/src/layouts/PublicLayout/navbarTypes.ts +27 -4
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingMobileDrawer.tsx +29 -0
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +127 -0
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushMobileDrawer.tsx +19 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushNavbar.tsx +122 -0
- package/src/layouts/PublicLayout/navbars/FlushNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalMobileDrawer.tsx +19 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +180 -0
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/index.ts +3 -0
- package/src/layouts/PublicLayout/navbars/index.ts +3 -0
- package/src/layouts/PublicLayout/primitives/ExternalPrefixesContext.tsx +69 -0
- package/src/layouts/PublicLayout/primitives/NavActionItem.tsx +95 -0
- package/src/layouts/PublicLayout/{components → primitives}/NavActions.tsx +26 -1
- package/src/layouts/PublicLayout/{components → primitives}/NavBrand.tsx +4 -3
- package/src/layouts/PublicLayout/{components → primitives}/NavDesktopItems.tsx +105 -61
- package/src/layouts/PublicLayout/primitives/SmartNavLink.tsx +81 -0
- package/src/layouts/PublicLayout/{components → primitives}/ThemeBrandMark.tsx +0 -8
- package/src/layouts/PublicLayout/primitives/index.ts +18 -0
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +205 -0
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +295 -0
- package/src/layouts/PublicLayout/shared/index.ts +4 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +0 -211
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +0 -99
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +0 -287
- package/src/layouts/PublicLayout/components/index.ts +0 -11
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public Layout Navigation — orchestrator.
|
|
3
|
-
*
|
|
4
|
-
* Logic lives in hooks; rendering lives in NavBrand / NavDesktopItems / NavActions.
|
|
5
|
-
* This file wires them together and switches on navLayout.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import React, {
|
|
11
|
-
type ReactNode,
|
|
12
|
-
useEffect,
|
|
13
|
-
useLayoutEffect,
|
|
14
|
-
useMemo,
|
|
15
|
-
useRef,
|
|
16
|
-
} from 'react';
|
|
17
|
-
|
|
18
|
-
import { useAppT } from '@djangocfg/i18n';
|
|
19
|
-
import { useIsTabletOrBelow } from '@djangocfg/ui-core/hooks';
|
|
20
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
21
|
-
|
|
22
|
-
import { usePathnameWithoutLocale } from '../../../hooks';
|
|
23
|
-
import { usePublicLayoutOptional } from '../context';
|
|
24
|
-
import {
|
|
25
|
-
useDropdownMenu,
|
|
26
|
-
useNavbarScroll,
|
|
27
|
-
useNavbarViewportVars,
|
|
28
|
-
} from '../hooks';
|
|
29
|
-
import type {
|
|
30
|
-
PublicNavbarHeight,
|
|
31
|
-
PublicNavbarPosition,
|
|
32
|
-
PublicNavbarVariant,
|
|
33
|
-
PublicNavLayout,
|
|
34
|
-
} from '../navbarTypes';
|
|
35
|
-
import { publicFloatingChromeClassName } from '../publicShellShadow';
|
|
36
|
-
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
37
|
-
|
|
38
|
-
import { NavActions } from './NavActions';
|
|
39
|
-
import { NavBrand } from './NavBrand';
|
|
40
|
-
import { NavDesktopItems } from './NavDesktopItems';
|
|
41
|
-
|
|
42
|
-
// ─── Public types (re-exported so PublicNavbar.tsx can use them) ──────────────
|
|
43
|
-
|
|
44
|
-
export interface PublicDesktopDropdownRenderProps {
|
|
45
|
-
item: NavigationItem;
|
|
46
|
-
isOpen: boolean;
|
|
47
|
-
isActive: boolean;
|
|
48
|
-
close: () => void;
|
|
49
|
-
defaultPopover: React.ReactNode;
|
|
50
|
-
defaultItems: React.ReactNode;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type PublicDesktopDropdownRenderer = (props: PublicDesktopDropdownRenderProps) => ReactNode;
|
|
54
|
-
|
|
55
|
-
export type { PublicNavbarPosition, PublicNavbarVariant } from '../navbarTypes';
|
|
56
|
-
|
|
57
|
-
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
interface PublicNavigationProps {
|
|
60
|
-
brand?: ReactNode;
|
|
61
|
-
/** @default '/' */
|
|
62
|
-
brandHref?: string;
|
|
63
|
-
navigation?: NavigationItem[];
|
|
64
|
-
userMenu?: UserMenuConfig;
|
|
65
|
-
containerClassName?: string;
|
|
66
|
-
navbarVariant?: PublicNavbarVariant;
|
|
67
|
-
navbarPosition?: PublicNavbarPosition;
|
|
68
|
-
renderDesktopDropdown?: PublicDesktopDropdownRenderer;
|
|
69
|
-
/** Max visible top-level desktop items before collapsing into "More". @default 7 */
|
|
70
|
-
desktopMaxPrimaryItems?: number;
|
|
71
|
-
/** Tailwind rounding for floating variant shell. @default 'rounded-2xl' */
|
|
72
|
-
rounding?: string;
|
|
73
|
-
mobileMenuOpen?: boolean;
|
|
74
|
-
onMobileMenuToggle?: () => void;
|
|
75
|
-
/** Desktop nav arrangement. @default 'default' */
|
|
76
|
-
navLayout?: PublicNavLayout;
|
|
77
|
-
/** Navbar vertical padding / height. @default 'md' */
|
|
78
|
-
navbarHeight?: PublicNavbarHeight;
|
|
79
|
-
/** Slide navbar off-screen on scroll-down; restore on scroll-up. @default false */
|
|
80
|
-
hideNavOnScroll?: boolean;
|
|
81
|
-
/** Transparent at page top, opaque after scrolling past threshold. @default false */
|
|
82
|
-
transparent?: boolean;
|
|
83
|
-
/** scrollY threshold for transparent → opaque transition. @default 40 */
|
|
84
|
-
transparentThreshold?: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ─── Height map ───────────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
const heightCls: Record<PublicNavbarHeight, string> = {
|
|
90
|
-
sm: 'py-2',
|
|
91
|
-
md: 'py-3.5',
|
|
92
|
-
lg: 'py-5',
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// ─── Component ────────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
export function PublicNavigation(props: PublicNavigationProps = {}) {
|
|
98
|
-
const context = usePublicLayoutOptional();
|
|
99
|
-
|
|
100
|
-
// ── Resolve props (context fills missing values) ──────────────────────────
|
|
101
|
-
const brand = props.brand;
|
|
102
|
-
const brandHref = props.brandHref ?? '/';
|
|
103
|
-
const navigation = props.navigation ?? [];
|
|
104
|
-
const userMenu = props.userMenu;
|
|
105
|
-
const containerClassName = props.containerClassName;
|
|
106
|
-
const rounding = props.rounding;
|
|
107
|
-
const navbarVariant = props.navbarVariant ?? 'floating';
|
|
108
|
-
const navbarPosition = props.navbarPosition ?? 'sticky';
|
|
109
|
-
const renderDesktopDropdown = props.renderDesktopDropdown;
|
|
110
|
-
const desktopMaxPrimaryItems = Math.max(1, props.desktopMaxPrimaryItems ?? 7);
|
|
111
|
-
const mobileMenuOpen = props.mobileMenuOpen ?? context?.mobileMenuOpen ?? false;
|
|
112
|
-
const toggleMobileMenu = props.onMobileMenuToggle ?? context?.toggleMobileMenu ?? (() => {});
|
|
113
|
-
const navLayout = props.navLayout ?? 'default';
|
|
114
|
-
const navbarHeight = props.navbarHeight ?? 'md';
|
|
115
|
-
const hideNavOnScroll = props.hideNavOnScroll ?? false;
|
|
116
|
-
const transparent = props.transparent ?? false;
|
|
117
|
-
const transparentThreshold = props.transparentThreshold ?? 40;
|
|
118
|
-
|
|
119
|
-
// ── Refs ──────────────────────────────────────────────────────────────────
|
|
120
|
-
const navOuterRef = useRef<HTMLDivElement | null>(null);
|
|
121
|
-
|
|
122
|
-
// ── Hooks ─────────────────────────────────────────────────────────────────
|
|
123
|
-
const { hidden, scrolled } = useNavbarScroll({ hideNavOnScroll, transparent, transparentThreshold });
|
|
124
|
-
const dropdown = useDropdownMenu();
|
|
125
|
-
useNavbarViewportVars(navOuterRef, [navbarPosition, navbarVariant, containerClassName] as const);
|
|
126
|
-
|
|
127
|
-
const isTabletOrBelow = useIsTabletOrBelow();
|
|
128
|
-
const t = useAppT();
|
|
129
|
-
const pathname = usePathnameWithoutLocale();
|
|
130
|
-
|
|
131
|
-
// Close dropdowns when switching to mobile
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
if (isTabletOrBelow) dropdown.closeDropdown();
|
|
134
|
-
}, [isTabletOrBelow, dropdown.closeDropdown]);
|
|
135
|
-
|
|
136
|
-
// Sync navbar surface into context
|
|
137
|
-
const setNavbarSurface = context?.setNavbarSurface;
|
|
138
|
-
useLayoutEffect(() => {
|
|
139
|
-
if (!setNavbarSurface) return;
|
|
140
|
-
setNavbarSurface({ variant: navbarVariant, position: navbarPosition });
|
|
141
|
-
return () => setNavbarSurface(null);
|
|
142
|
-
}, [setNavbarSurface, navbarVariant, navbarPosition]);
|
|
143
|
-
|
|
144
|
-
// ── Derived values ────────────────────────────────────────────────────────
|
|
145
|
-
const toggleMobileLabel = useMemo(() => t('layouts.navigation.toggleMobile'), [t]);
|
|
146
|
-
|
|
147
|
-
const isActivePath = useMemo(() => (href: string) => {
|
|
148
|
-
if (href === '/') return pathname === '/';
|
|
149
|
-
return pathname === href || pathname.startsWith(`${href}/`);
|
|
150
|
-
}, [pathname]);
|
|
151
|
-
|
|
152
|
-
const isGroupActive = useMemo(() => (item: NavigationItem): boolean => {
|
|
153
|
-
if (isActivePath(item.href)) return true;
|
|
154
|
-
return item.items?.some((sub) => isActivePath(sub.href)) ?? false;
|
|
155
|
-
}, [isActivePath]);
|
|
156
|
-
|
|
157
|
-
// Desktop: filter lone link duplicating brand (keeps full list in drawer)
|
|
158
|
-
const desktopNavItems = useMemo(
|
|
159
|
-
() => navigation.filter((item) => item.items?.length || item.href !== brandHref),
|
|
160
|
-
[navigation, brandHref],
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const primaryItems = desktopNavItems.slice(0, desktopMaxPrimaryItems);
|
|
164
|
-
const overflowItems = desktopNavItems.slice(desktopMaxPrimaryItems);
|
|
165
|
-
|
|
166
|
-
// ── Class names ───────────────────────────────────────────────────────────
|
|
167
|
-
|
|
168
|
-
const navOuterClassName = cn(
|
|
169
|
-
navbarPosition === 'fixed'
|
|
170
|
-
? 'fixed'
|
|
171
|
-
: navbarPosition === 'static'
|
|
172
|
-
? 'static'
|
|
173
|
-
: 'sticky',
|
|
174
|
-
navbarVariant === 'floating' ? 'top-3' : 'top-0',
|
|
175
|
-
navbarVariant === 'floating' ? 'px-3 sm:px-4 lg:px-6' : '',
|
|
176
|
-
'inset-x-0 z-50',
|
|
177
|
-
hideNavOnScroll && 'transition-transform duration-300 ease-in-out will-change-transform',
|
|
178
|
-
// Keep visible when mobile menu is open even if scrolling
|
|
179
|
-
hideNavOnScroll && hidden && !mobileMenuOpen && '-translate-y-full',
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
const navShapeClassName = cn(
|
|
183
|
-
'mx-auto w-full',
|
|
184
|
-
navbarVariant === 'floating'
|
|
185
|
-
? cn(rounding ?? 'rounded-2xl', publicFloatingChromeClassName)
|
|
186
|
-
: 'rounded-none border-x-0 border-t-0 border-b border-border/40 dark:border-border/70 shadow-none',
|
|
187
|
-
containerClassName,
|
|
188
|
-
navbarVariant === 'floating' && '!border-0 dark:!border dark:!border-border/75',
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
const navSurfaceClassName = cn(
|
|
192
|
-
transparent && 'transition-[background-color,backdrop-filter] duration-200 ease-out',
|
|
193
|
-
!transparent || scrolled
|
|
194
|
-
? 'bg-background/72 backdrop-blur-[10px] dark:bg-card/80'
|
|
195
|
-
: 'bg-transparent backdrop-blur-0 dark:bg-transparent',
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
// ── Sub-components ────────────────────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
const brandNode = (
|
|
201
|
-
<div className="min-w-0 shrink-0 flex items-center">
|
|
202
|
-
<NavBrand brand={brand} brandHref={brandHref} />
|
|
203
|
-
</div>
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const desktopNavNode = navLayout !== 'split' ? (
|
|
207
|
-
<NavDesktopItems
|
|
208
|
-
primaryItems={primaryItems}
|
|
209
|
-
overflowItems={overflowItems}
|
|
210
|
-
isActivePath={isActivePath}
|
|
211
|
-
isGroupActive={isGroupActive}
|
|
212
|
-
dropdown={dropdown}
|
|
213
|
-
renderDesktopDropdown={renderDesktopDropdown}
|
|
214
|
-
/>
|
|
215
|
-
) : null;
|
|
216
|
-
|
|
217
|
-
const actionsNode = (
|
|
218
|
-
<NavActions
|
|
219
|
-
userMenu={userMenu}
|
|
220
|
-
mobileMenuOpen={mobileMenuOpen}
|
|
221
|
-
onMobileMenuToggle={toggleMobileMenu}
|
|
222
|
-
toggleMobileLabel={toggleMobileLabel}
|
|
223
|
-
forceShowMobileTrigger={navLayout === 'split'}
|
|
224
|
-
/>
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
const h = heightCls[navbarHeight];
|
|
228
|
-
|
|
229
|
-
// ── Layout variants ───────────────────────────────────────────────────────
|
|
230
|
-
|
|
231
|
-
const renderRow = () => {
|
|
232
|
-
switch (navLayout) {
|
|
233
|
-
case 'brand-left':
|
|
234
|
-
return (
|
|
235
|
-
<div className={cn('flex items-center gap-1', h)}>
|
|
236
|
-
<div className="min-w-0 shrink-0 flex items-center mr-4">{brandNode}</div>
|
|
237
|
-
<div className="hidden isolate lg:flex items-center gap-1">
|
|
238
|
-
{desktopNavNode}
|
|
239
|
-
</div>
|
|
240
|
-
<div className="ml-auto flex items-center gap-4">{actionsNode}</div>
|
|
241
|
-
</div>
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
case 'centered':
|
|
245
|
-
return (
|
|
246
|
-
<div className={cn('flex items-center justify-center gap-4', h)}>
|
|
247
|
-
{brandNode}
|
|
248
|
-
<div className="hidden isolate lg:flex items-center gap-1">
|
|
249
|
-
{desktopNavNode}
|
|
250
|
-
</div>
|
|
251
|
-
{actionsNode}
|
|
252
|
-
</div>
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
case 'split':
|
|
256
|
-
return (
|
|
257
|
-
<div className={cn('flex items-center justify-between', h)}>
|
|
258
|
-
{brandNode}
|
|
259
|
-
{actionsNode}
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
default: // 'default' — brand left, nav truly centered (absolute), actions right
|
|
264
|
-
return (
|
|
265
|
-
<div className={cn('relative flex items-center justify-between', h)}>
|
|
266
|
-
{brandNode}
|
|
267
|
-
<div className="hidden isolate lg:flex items-center gap-1 absolute left-1/2 -translate-x-1/2">
|
|
268
|
-
{desktopNavNode}
|
|
269
|
-
</div>
|
|
270
|
-
{actionsNode}
|
|
271
|
-
</div>
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
// ── Render ────────────────────────────────────────────────────────────────
|
|
277
|
-
|
|
278
|
-
return (
|
|
279
|
-
<div ref={navOuterRef} className={navOuterClassName}>
|
|
280
|
-
<nav className={cn(navShapeClassName, navSurfaceClassName)}>
|
|
281
|
-
<div className="w-full pl-6 pr-3 sm:px-4 lg:px-6">
|
|
282
|
-
{renderRow()}
|
|
283
|
-
</div>
|
|
284
|
-
</nav>
|
|
285
|
-
</div>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public Layout Components
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { PublicNavigation } from './PublicNavigation';
|
|
6
|
-
export { PublicMobileDrawer } from './PublicMobileDrawer';
|
|
7
|
-
export { PublicNavbar } from './PublicNavbar';
|
|
8
|
-
export { PublicFooter } from './PublicFooter';
|
|
9
|
-
export { ThemeBrandMark, ThemeBrandMarkImg } from './ThemeBrandMark';
|
|
10
|
-
export type { ThemeBrandMarkProps, ThemeBrandMarkImgProps } from './ThemeBrandMark';
|
|
11
|
-
|