@djangocfg/layouts 2.1.274 → 2.1.276

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.
Files changed (40) hide show
  1. package/README.md +52 -180
  2. package/package.json +18 -18
  3. package/src/layouts/AppLayout/AppLayout.tsx +14 -14
  4. package/src/layouts/PublicLayout/README.md +144 -0
  5. package/src/layouts/PublicLayout/{components/PublicFooter/PublicFooter.tsx → footers/DefaultFooter/DefaultFooter.tsx} +14 -8
  6. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/DjangoCFGLogo.tsx +0 -6
  7. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterBottom.tsx +0 -4
  8. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterMenuSections.tsx +0 -4
  9. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterProjectInfo.tsx +0 -4
  10. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/FooterSocialLinks.tsx +0 -5
  11. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/index.ts +2 -12
  12. package/src/layouts/PublicLayout/{components/PublicFooter → footers/DefaultFooter}/types.ts +21 -26
  13. package/src/layouts/PublicLayout/footers/index.ts +1 -0
  14. package/src/layouts/PublicLayout/hooks/index.ts +1 -0
  15. package/src/layouts/PublicLayout/hooks/useResponsiveOverflow.ts +140 -0
  16. package/src/layouts/PublicLayout/index.ts +22 -22
  17. package/src/layouts/PublicLayout/navbarTypes.ts +27 -4
  18. package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingMobileDrawer.tsx +29 -0
  19. package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +117 -0
  20. package/src/layouts/PublicLayout/navbars/FloatingNavbar/index.ts +3 -0
  21. package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushMobileDrawer.tsx +19 -0
  22. package/src/layouts/PublicLayout/navbars/FlushNavbar/FlushNavbar.tsx +112 -0
  23. package/src/layouts/PublicLayout/navbars/FlushNavbar/index.ts +3 -0
  24. package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalMobileDrawer.tsx +19 -0
  25. package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +169 -0
  26. package/src/layouts/PublicLayout/navbars/MinimalNavbar/index.ts +3 -0
  27. package/src/layouts/PublicLayout/navbars/index.ts +3 -0
  28. package/src/layouts/PublicLayout/primitives/NavActionItem.tsx +94 -0
  29. package/src/layouts/PublicLayout/{components → primitives}/NavActions.tsx +26 -1
  30. package/src/layouts/PublicLayout/{components → primitives}/NavDesktopItems.tsx +100 -56
  31. package/src/layouts/PublicLayout/{components → primitives}/ThemeBrandMark.tsx +0 -8
  32. package/src/layouts/PublicLayout/primitives/index.ts +7 -0
  33. package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +205 -0
  34. package/src/layouts/PublicLayout/shared/NavbarShell.tsx +295 -0
  35. package/src/layouts/PublicLayout/shared/index.ts +4 -0
  36. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +0 -211
  37. package/src/layouts/PublicLayout/components/PublicNavbar.tsx +0 -99
  38. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +0 -287
  39. package/src/layouts/PublicLayout/components/index.ts +0 -11
  40. /package/src/layouts/PublicLayout/{components → primitives}/NavBrand.tsx +0 -0
@@ -1,211 +0,0 @@
1
- /**
2
- * Public Layout Mobile Drawer
3
- *
4
- * Mobile drawer component for PublicLayout navigation
5
- */
6
-
7
- 'use client';
8
-
9
- import { ArrowRight } from 'lucide-react';
10
- import Link from 'next/link';
11
- import React, { useMemo } from 'react';
12
-
13
- import { useAuth } from '@djangocfg/api/auth';
14
- import { useAppT } from '@djangocfg/i18n';
15
- import { Button } from '@djangocfg/ui-core/components';
16
- import { cn } from '@djangocfg/ui-core/lib';
17
- import { usePathnameWithoutLocale } from '../../../hooks';
18
-
19
- import { UserMenu } from '../../_components/UserMenu';
20
- import { publicFloatingChromeClassName } from '../publicShellShadow';
21
- import { usePublicLayoutOptional } from '../context';
22
- import { useMobileNavPanel } from '../hooks';
23
-
24
- import type { NavigationItem, UserMenuConfig } from '../../types';
25
-
26
- interface PublicMobileDrawerProps {
27
- isOpen?: boolean;
28
- onClose?: () => void;
29
- navigation?: NavigationItem[];
30
- userMenu?: UserMenuConfig;
31
- containerClassName?: string;
32
- /**
33
- * Tailwind rounding for the drawer panel. Defaults to `rounded-2xl`.
34
- * Match `PublicNavigation` `rounding` so bar and sheet align visually.
35
- */
36
- rounding?: string;
37
- }
38
-
39
- export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
40
- const context = usePublicLayoutOptional();
41
- const mobileMenuOpen = props.isOpen ?? context?.mobileMenuOpen ?? false;
42
- const closeMobileMenu = props.onClose ?? context?.closeMobileMenu ?? (() => {});
43
- const navigation = props.navigation ?? [];
44
- const userMenu = props.userMenu;
45
- const containerClassName = props.containerClassName;
46
- const rounding = props.rounding;
47
- const { isAuthenticated, user } = useAuth();
48
- const pathname = usePathnameWithoutLocale();
49
- const t = useAppT();
50
- const { mounted, visible } = useMobileNavPanel({
51
- isOpen: mobileMenuOpen,
52
- onClose: closeMobileMenu,
53
- });
54
-
55
- const labels = useMemo(() => ({
56
- menu: t('layouts.navigation.menu'),
57
- quickActions: 'Actions',
58
- signIn: t('layouts.profile.login'),
59
- }), [t]);
60
-
61
- const mobileNavigation = useMemo(() => {
62
- const hasHome = navigation.some((item) => item.href === '/');
63
- if (hasHome) return navigation;
64
- return [{ label: 'Home', href: '/' }, ...navigation];
65
- }, [navigation]);
66
-
67
- const isActivePath = (href: string) => {
68
- if (href === '/') return pathname === '/';
69
- return pathname === href || pathname.startsWith(`${href}/`);
70
- };
71
-
72
- if (!mounted) return null;
73
-
74
- const hasSessionUser = Boolean(isAuthenticated && user);
75
- const showSignInFooter = !hasSessionUser;
76
-
77
- return (
78
- <>
79
- {mobileMenuOpen && (
80
- <button
81
- type="button"
82
- aria-label={t('layouts.mobile.closeMenu')}
83
- className="fixed inset-0 z-[998] lg:hidden bg-black/35 transition-opacity duration-200"
84
- onClick={closeMobileMenu}
85
- />
86
- )}
87
- {/* Outer shell must not capture taps when the panel is closed: with pointer-events-none on the
88
- inner panel, events would otherwise hit this transparent fixed layer (z-1000) and block the page. */}
89
- <div
90
- className="pointer-events-none fixed inset-x-0 z-1000 lg:hidden px-4 pb-3 sm:px-6 sm:pb-3 lg:px-8"
91
- style={{
92
- top: 'var(--public-navbar-mobile-drawer-top, 5rem)',
93
- bottom: 0,
94
- }}
95
- >
96
- <div
97
- className={cn(
98
- 'mx-auto flex h-full min-h-0 max-h-full w-full flex-col overflow-hidden bg-background/72 backdrop-blur-[10px] dark:bg-card/80 transform-gpu will-change-transform transition-[transform,opacity] duration-[220ms] ease-out',
99
- publicFloatingChromeClassName,
100
- rounding ?? 'rounded-2xl',
101
- containerClassName,
102
- visible
103
- ? 'pointer-events-auto opacity-100 translate-y-0 scale-100'
104
- : 'pointer-events-none opacity-0 -translate-y-2 scale-[0.985]',
105
- )}
106
- style={{
107
- maxHeight: 'min(var(--public-navbar-mobile-drawer-max-height, calc(100dvh - 5rem - 12px)), calc(100dvh - 12px))',
108
- }}
109
- >
110
- {/* Scrollable content */}
111
- <div className="flex-1 min-h-0 overflow-y-auto px-4 py-4 pb-10 space-y-5">
112
- {hasSessionUser && (
113
- <div className="px-2">
114
- <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
115
- {labels.quickActions}
116
- </h3>
117
- </div>
118
- )}
119
-
120
- {hasSessionUser && (
121
- <UserMenu variant="mobile" groups={userMenu?.groups} authPath={userMenu?.authPath} i18n={userMenu?.i18n} />
122
- )}
123
-
124
- {/* Navigation Items */}
125
- <div className="space-y-2">
126
- <div className="px-2">
127
- <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
128
- {labels.menu}
129
- </h3>
130
- </div>
131
- <div className="space-y-1">
132
- {mobileNavigation.map((item) => {
133
- const childItems = item.items ?? [];
134
- const hasChildNav = childItems.length > 0;
135
- const anyChildActive = hasChildNav && childItems.some((sub) => isActivePath(sub.href));
136
- const parentPageActive = hasChildNav
137
- ? isActivePath(item.href) && !anyChildActive
138
- : isActivePath(item.href);
139
- const parentOnlySectionOpen = hasChildNav && anyChildActive;
140
- return (
141
- <div key={item.href}>
142
- <Link
143
- href={item.href}
144
- onClick={closeMobileMenu}
145
- title={item.label}
146
- className={cn(
147
- 'block min-h-11 min-w-0 max-w-full rounded-full border-0 px-5 py-3 text-[15px] font-medium transition-colors ring-0 truncate',
148
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35',
149
- parentPageActive
150
- ? 'border-0 bg-accent font-semibold text-foreground shadow-sm dark:border dark:border-border dark:bg-muted dark:shadow-none'
151
- : parentOnlySectionOpen
152
- ? 'font-semibold text-foreground'
153
- : 'text-foreground hover:bg-accent/60 hover:text-accent-foreground',
154
- )}
155
- >
156
- {item.label}
157
- </Link>
158
- {hasChildNav && (
159
- <div className="ml-3 mt-1.5 space-y-1 border-l border-border/40 pl-3">
160
- {childItems.map((subItem) => {
161
- const subActive = isActivePath(subItem.href);
162
- return (
163
- <Link
164
- key={`${item.href}-${subItem.href}`}
165
- href={subItem.href}
166
- onClick={closeMobileMenu}
167
- title={subItem.label}
168
- className={cn(
169
- 'flex min-h-11 min-w-0 max-w-full items-center rounded-full border-0 px-4 py-2.5 text-sm font-medium transition-colors ring-0 truncate',
170
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35',
171
- subActive
172
- ? 'border-0 bg-accent font-semibold text-foreground shadow-sm dark:border dark:border-border dark:bg-muted/90 dark:shadow-none'
173
- : 'border-0 text-muted-foreground hover:bg-accent/55 hover:text-foreground',
174
- )}
175
- >
176
- {subItem.label}
177
- </Link>
178
- );
179
- })}
180
- </div>
181
- )}
182
- </div>
183
- );
184
- })}
185
- </div>
186
- </div>
187
- </div>
188
-
189
- {showSignInFooter && (
190
- <div className="shrink-0 border-t border-border/50 p-4">
191
- <Link
192
- href={userMenu?.authPath || '/auth'}
193
- onClick={closeMobileMenu}
194
- className="block"
195
- >
196
- <Button className="relative w-full justify-center rounded-full h-11 px-6 pr-12">
197
- {labels.signIn}
198
- <ArrowRight
199
- className="pointer-events-none absolute right-4 top-1/2 h-4 w-4 -translate-y-1/2 shrink-0"
200
- aria-hidden
201
- />
202
- </Button>
203
- </Link>
204
- </div>
205
- )}
206
- </div>
207
- </div>
208
- </>
209
- );
210
- }
211
-
@@ -1,99 +0,0 @@
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 { PublicDesktopDropdownRenderer } from './PublicNavigation';
10
- import type {
11
- PublicNavbarShellConfig,
12
- PublicNavbarPosition,
13
- PublicNavbarVariant,
14
- PublicNavLayout,
15
- PublicNavbarHeight,
16
- } from '../navbarTypes';
17
-
18
- export interface PublicNavbarConfig {
19
- shell?: PublicNavbarShellConfig;
20
- /** Brand: custom React node, or a plain string (wrapped in `<Link href={brandHref}>`). */
21
- brand?: React.ReactNode;
22
- /** @default '/' */
23
- brandHref?: string;
24
- navigation?: NavigationItem[];
25
- userMenu?: UserMenuConfig;
26
- navbarVariant?: PublicNavbarVariant;
27
- navbarPosition?: PublicNavbarPosition;
28
- renderDesktopDropdown?: PublicDesktopDropdownRenderer;
29
- desktopMaxPrimaryItems?: number;
30
- /**
31
- * Desktop nav arrangement.
32
- * - `default` — brand left | nav centered | actions right
33
- * - `brand-left` — brand left | nav after brand | actions pushed right
34
- * - `centered` — all items centered in one row
35
- * - `split` — brand left | actions right | no desktop nav (drawer only)
36
- * @default 'default'
37
- */
38
- navLayout?: PublicNavLayout;
39
- /**
40
- * Navbar vertical padding / height.
41
- * - `sm` → compact
42
- * - `md` → default
43
- * - `lg` → tall
44
- * @default 'md'
45
- */
46
- navbarHeight?: PublicNavbarHeight;
47
- /**
48
- * Slide navbar off-screen on scroll-down; restore on scroll-up.
49
- * @default false
50
- */
51
- hideNavOnScroll?: boolean;
52
- /**
53
- * Transparent at page top, opaque after scrolling.
54
- * Pair with `navbarVariant="floating"` for best results.
55
- * @default false
56
- */
57
- transparent?: boolean;
58
- /**
59
- * scrollY threshold (px) for transparent → opaque transition.
60
- * @default 40
61
- */
62
- transparentThreshold?: number;
63
- }
64
-
65
- export interface PublicNavbarProps {
66
- config: PublicNavbarConfig;
67
- }
68
-
69
- export function PublicNavbar({ config }: PublicNavbarProps) {
70
- const navigation = config.navigation ?? [];
71
-
72
- return (
73
- <>
74
- <PublicNavigation
75
- brand={config.brand}
76
- brandHref={config.brandHref}
77
- navigation={navigation}
78
- userMenu={config.userMenu}
79
- containerClassName={config.shell?.className}
80
- navbarVariant={config.navbarVariant}
81
- navbarPosition={config.navbarPosition}
82
- renderDesktopDropdown={config.renderDesktopDropdown}
83
- desktopMaxPrimaryItems={config.desktopMaxPrimaryItems}
84
- rounding={config.shell?.rounding}
85
- navLayout={config.navLayout}
86
- navbarHeight={config.navbarHeight}
87
- hideNavOnScroll={config.hideNavOnScroll}
88
- transparent={config.transparent}
89
- transparentThreshold={config.transparentThreshold}
90
- />
91
- <PublicMobileDrawer
92
- navigation={navigation}
93
- userMenu={config.userMenu}
94
- containerClassName={config.shell?.className}
95
- rounding={config.shell?.rounding}
96
- />
97
- </>
98
- );
99
- }
@@ -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
-