@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,9 +1,3 @@
1
- /**
2
- * DjangoCFG Logo Component
3
- *
4
- * Default SVG logo for DjangoCFG
5
- */
6
-
7
1
  'use client';
8
2
 
9
3
  import React from 'react';
@@ -1,7 +1,3 @@
1
- /**
2
- * Footer Bottom Section Component
3
- */
4
-
5
1
  'use client';
6
2
 
7
3
  import Link from 'next/link';
@@ -1,7 +1,3 @@
1
- /**
2
- * Footer Menu Sections Component
3
- */
4
-
5
1
  'use client';
6
2
 
7
3
  import Link from 'next/link';
@@ -1,7 +1,3 @@
1
- /**
2
- * Footer Project Info Component
3
- */
4
-
5
1
  'use client';
6
2
 
7
3
  import React from 'react';
@@ -1,7 +1,3 @@
1
- /**
2
- * Footer Social Links Component
3
- */
4
-
5
1
  'use client';
6
2
 
7
3
  import {
@@ -34,7 +30,6 @@ export function FooterSocialLinksComponent({
34
30
  className = 'flex space-x-4',
35
31
  iconClassName = 'w-5 h-5',
36
32
  }: FooterSocialLinksProps) {
37
- // Prepare social links data BEFORE render
38
33
  const socialLinksData = socialLinks
39
34
  ? Object.entries(socialLinks)
40
35
  .filter(([_, url]) => url)
@@ -1,8 +1,4 @@
1
- /**
2
- * Public Footer Exports
3
- */
4
-
5
- export { PublicFooter } from './PublicFooter';
1
+ export { DefaultFooter } from './DefaultFooter';
6
2
  export { FooterProjectInfo } from './FooterProjectInfo';
7
3
  export type { FooterProjectInfoProps } from './FooterProjectInfo';
8
4
  export { FooterMenuSections } from './FooterMenuSections';
@@ -10,10 +6,4 @@ export { FooterBottom } from './FooterBottom';
10
6
  export { FooterSocialLinksComponent } from './FooterSocialLinks';
11
7
  export { DjangoCFGLogo } from './DjangoCFGLogo';
12
8
 
13
- export type {
14
- PublicFooterProps,
15
- PublicFooterConfig,
16
- FooterLink,
17
- FooterMenuSection,
18
- FooterSocialLinks,
19
- } from './types';
9
+ export type { DefaultFooterProps, DefaultFooterConfig } from './types';
@@ -1,35 +1,16 @@
1
1
  /**
2
- * Public Footer Types
2
+ * Default Footer types.
3
3
  */
4
4
 
5
5
  import type { LucideIcon } from 'lucide-react';
6
6
  import type { ReactNode } from 'react';
7
+
7
8
  import type { I18nLayoutConfig } from '../../../AppLayout/AppLayout';
9
+ import type { FooterLink, FooterMenuSection, FooterSocialLinks } from '../../../types';
8
10
 
9
- export interface FooterLink {
10
- label: string;
11
- path: string;
12
- external?: boolean;
13
- }
11
+ export type { FooterLink, FooterMenuSection, FooterSocialLinks };
14
12
 
15
- export interface FooterMenuSection {
16
- title: string;
17
- items: FooterLink[];
18
- }
19
-
20
- export interface FooterSocialLinks {
21
- github?: string;
22
- linkedin?: string;
23
- twitter?: string;
24
- telegram?: string;
25
- youtube?: string;
26
- facebook?: string;
27
- instagram?: string;
28
- whatsapp?: string;
29
- email?: string;
30
- }
31
-
32
- export interface PublicFooterConfig {
13
+ export interface DefaultFooterConfig {
33
14
  variant?: 'full' | 'compact' | 'simple';
34
15
  shell?: {
35
16
  className?: string;
@@ -70,8 +51,22 @@ export interface PublicFooterConfig {
70
51
  /** Requires `i18n`. @default true */
71
52
  showLocaleSwitcher?: boolean;
72
53
  };
54
+ /**
55
+ * Arbitrary ReactNode slots for custom content (newsletter, status badges,
56
+ * extra CTAs …). Only rendered in the `full` variant.
57
+ */
58
+ slots?: {
59
+ /** Rendered above the brand/menus grid. Full width. */
60
+ aboveMenus?: ReactNode;
61
+ /** Rendered between the menus grid and the bottom row. Full width. */
62
+ belowMenus?: ReactNode;
63
+ /** Rendered in the bottom row, next to the copyright. */
64
+ bottomStart?: ReactNode;
65
+ /** Rendered in the bottom row, next to controls. */
66
+ bottomEnd?: ReactNode;
67
+ };
73
68
  }
74
69
 
75
- export interface PublicFooterProps {
76
- config: PublicFooterConfig;
70
+ export interface DefaultFooterProps {
71
+ config: DefaultFooterConfig;
77
72
  }
@@ -0,0 +1 @@
1
+ export * from './DefaultFooter';
@@ -4,3 +4,4 @@ export type { UseDropdownMenuReturn } from './useDropdownMenu';
4
4
  export { useNavbarScroll } from './useNavbarScroll';
5
5
  export type { UseNavbarScrollOptions, UseNavbarScrollReturn } from './useNavbarScroll';
6
6
  export { useNavbarViewportVars } from './useNavbarViewportVars';
7
+ export { useResponsiveOverflow } from './useResponsiveOverflow';
@@ -0,0 +1,140 @@
1
+ /**
2
+ * useResponsiveOverflow — figure out how many nav items fit in the available
3
+ * space, pushing the rest into a "More" dropdown.
4
+ *
5
+ * Strategy (SSR-safe, no flash):
6
+ * 1. On the server and first client render we return `count === total`; the
7
+ * caller renders every item inside an *invisible* measurement layer plus
8
+ * the live row (see caller). No layout is committed until the observer
9
+ * runs.
10
+ * 2. After mount, a ResizeObserver on the container + per-item width reads
11
+ * recompute `visibleCount` whenever the nav's available width changes.
12
+ * 3. We reserve space for a "More" chip when anything is overflowing, so the
13
+ * chip itself never pushes the last visible item out (which would cause
14
+ * thrash: n → n-1 → n → …).
15
+ */
16
+
17
+ 'use client';
18
+
19
+ import { useCallback, useEffect, useRef, useState, type RefCallback } from 'react';
20
+
21
+ interface Options {
22
+ total: number;
23
+ /** Approx width reserved for the "More" chip when overflow exists. */
24
+ moreWidth?: number;
25
+ /** Gap between items (must match the flex gap in px). */
26
+ gap?: number;
27
+ /** Min items that must always be visible (clamps lower bound to 1). */
28
+ minVisible?: number;
29
+ }
30
+
31
+ interface Result {
32
+ /** How many items fit. */
33
+ visibleCount: number;
34
+ /** Attach to the container that constrains width. */
35
+ containerRef: RefCallback<HTMLElement | null>;
36
+ /** Attach to each item in the measurement layer (call for every index 0..total-1). */
37
+ itemRef: (index: number) => RefCallback<HTMLElement | null>;
38
+ /** True once a measurement has happened — caller can hide the measure layer then. */
39
+ measured: boolean;
40
+ }
41
+
42
+ export function useResponsiveOverflow({
43
+ total,
44
+ moreWidth = 72,
45
+ gap = 4,
46
+ minVisible = 1,
47
+ }: Options): Result {
48
+ const [visibleCount, setVisibleCount] = useState<number>(total);
49
+ const [measured, setMeasured] = useState(false);
50
+
51
+ const containerElRef = useRef<HTMLElement | null>(null);
52
+ const itemElsRef = useRef<Array<HTMLElement | null>>([]);
53
+ const roRef = useRef<ResizeObserver | null>(null);
54
+
55
+ const recompute = useCallback(() => {
56
+ const container = containerElRef.current;
57
+ if (!container) return;
58
+ const available = container.clientWidth;
59
+ if (available <= 0) return;
60
+
61
+ const widths: number[] = [];
62
+ for (let i = 0; i < total; i++) {
63
+ const el = itemElsRef.current[i];
64
+ widths.push(el ? Math.ceil(el.getBoundingClientRect().width) : 0);
65
+ }
66
+
67
+ // Try to fit all first.
68
+ let usedAll = 0;
69
+ for (let i = 0; i < total; i++) {
70
+ usedAll += widths[i] + (i > 0 ? gap : 0);
71
+ }
72
+ if (usedAll <= available) {
73
+ setVisibleCount(total);
74
+ setMeasured(true);
75
+ return;
76
+ }
77
+
78
+ // Some overflow: reserve space for the "More" chip.
79
+ const budget = available - moreWidth - gap;
80
+ let used = 0;
81
+ let fit = 0;
82
+ for (let i = 0; i < total; i++) {
83
+ const next = used + widths[i] + (i > 0 ? gap : 0);
84
+ if (next > budget) break;
85
+ used = next;
86
+ fit = i + 1;
87
+ }
88
+
89
+ const clamped = Math.max(minVisible, Math.min(total, fit));
90
+ setVisibleCount(clamped);
91
+ setMeasured(true);
92
+ }, [total, moreWidth, gap, minVisible]);
93
+
94
+ // Recompute when `total` changes (items added/removed).
95
+ useEffect(() => {
96
+ itemElsRef.current.length = total;
97
+ recompute();
98
+ }, [total, recompute]);
99
+
100
+ const containerRef = useCallback<RefCallback<HTMLElement | null>>(
101
+ (node) => {
102
+ // Tear down previous observer.
103
+ if (roRef.current) {
104
+ roRef.current.disconnect();
105
+ roRef.current = null;
106
+ }
107
+ containerElRef.current = node;
108
+ if (!node) return;
109
+ if (typeof ResizeObserver === 'undefined') {
110
+ recompute();
111
+ return;
112
+ }
113
+ const ro = new ResizeObserver(() => recompute());
114
+ ro.observe(node);
115
+ roRef.current = ro;
116
+ // Initial measurement — rAF to let children mount.
117
+ requestAnimationFrame(recompute);
118
+ },
119
+ [recompute],
120
+ );
121
+
122
+ const itemRef = useCallback(
123
+ (index: number): RefCallback<HTMLElement | null> =>
124
+ (node) => {
125
+ itemElsRef.current[index] = node;
126
+ },
127
+ [],
128
+ );
129
+
130
+ useEffect(() => {
131
+ return () => {
132
+ if (roRef.current) {
133
+ roRef.current.disconnect();
134
+ roRef.current = null;
135
+ }
136
+ };
137
+ }, []);
138
+
139
+ return { visibleCount, containerRef, itemRef, measured };
140
+ }
@@ -1,10 +1,18 @@
1
1
  /**
2
2
  * Public Layout exports
3
+ *
4
+ * Each navbar and footer is a self-contained variant — pick one and pass it
5
+ * into `PublicLayout` via the `navbar` / `footer` slots.
3
6
  */
4
7
 
8
+ // Layout container
5
9
  export { PublicLayout } from './PublicLayout';
6
10
  export type { PublicLayoutProps } from './PublicLayout';
7
- export { PublicNavigation, PublicMobileDrawer, PublicNavbar } from './components';
11
+
12
+ // Context
13
+ export { PublicLayoutProvider, usePublicLayout } from './context';
14
+
15
+ // Shared types
8
16
  export type {
9
17
  PublicNavbarSurface,
10
18
  PublicNavbarVariant,
@@ -12,28 +20,20 @@ export type {
12
20
  PublicNavbarShellConfig,
13
21
  PublicNavLayout,
14
22
  PublicNavbarHeight,
23
+ PublicDesktopDropdownRenderer,
24
+ PublicDesktopDropdownRenderProps,
15
25
  } from './navbarTypes';
26
+
27
+ // Hook types (consumers occasionally want them)
16
28
  export type { UseNavbarScrollOptions, UseNavbarScrollReturn } from './hooks/useNavbarScroll';
17
29
  export type { UseDropdownMenuReturn } from './hooks/useDropdownMenu';
18
- export type {
19
- PublicDesktopDropdownRenderer,
20
- PublicDesktopDropdownRenderProps,
21
- } from './components/PublicNavigation';
22
- export type { PublicNavbarConfig, PublicNavbarProps } from './components/PublicNavbar';
23
- export {
24
- PublicFooter,
25
- FooterProjectInfo,
26
- FooterMenuSections,
27
- FooterBottom,
28
- FooterSocialLinksComponent,
29
- DjangoCFGLogo,
30
- } from './components/PublicFooter';
31
- export { ThemeBrandMark, ThemeBrandMarkImg } from './components/ThemeBrandMark';
32
- export type { ThemeBrandMarkProps, ThemeBrandMarkImgProps } from './components/ThemeBrandMark';
33
- export type {
34
- PublicFooterProps,
35
- PublicFooterConfig,
36
- FooterProjectInfoProps,
37
- } from './components/PublicFooter';
38
- export { PublicLayoutProvider, usePublicLayout } from './context';
39
30
 
31
+ // Primitives (for users who want to build a custom navbar)
32
+ export { NavBrand, NavActions, NavActionItem, NavDesktopItems, ThemeBrandMark, ThemeBrandMarkImg } from './primitives';
33
+ export type { NavAction, ThemeBrandMarkProps, ThemeBrandMarkImgProps } from './primitives';
34
+
35
+ // Navbar variants
36
+ export * from './navbars';
37
+
38
+ // Footer variants
39
+ export * from './footers';
@@ -1,8 +1,18 @@
1
1
  /**
2
- * Shared navbar surface types — used by PublicNavigation (registration) and PublicLayout (main offset).
2
+ * Shared navbar types — consumed by all navbar variants and PublicLayout main offset.
3
3
  */
4
4
 
5
- export type PublicNavbarVariant = 'floating' | 'flush';
5
+ import type { ReactNode } from 'react';
6
+
7
+ import type { NavigationItem } from '../types';
8
+
9
+ /**
10
+ * Navbar chrome variant used for main-content top spacing.
11
+ * - `floating` — rounded, inset shell (extra top offset for main).
12
+ * - `flush` — edge-to-edge bar with bottom border.
13
+ * - `minimal` — no shell at all (eesel-style): transparent, borderless.
14
+ */
15
+ export type PublicNavbarVariant = 'floating' | 'flush' | 'minimal';
6
16
 
7
17
  export type PublicNavbarPosition = 'sticky' | 'fixed' | 'static';
8
18
 
@@ -18,7 +28,7 @@ export type PublicNavLayout = 'default' | 'brand-left' | 'centered' | 'split';
18
28
  /**
19
29
  * Navbar vertical padding / height.
20
30
  * - `sm` → py-2
21
- * - `md` → py-3.5 (default, matches current)
31
+ * - `md` → py-3.5 (default)
22
32
  * - `lg` → py-5
23
33
  */
24
34
  export type PublicNavbarHeight = 'sm' | 'md' | 'lg';
@@ -28,10 +38,23 @@ export interface PublicNavbarSurface {
28
38
  position: PublicNavbarPosition;
29
39
  }
30
40
 
31
- /** Floating bar + mobile drawer shell (rounding, width / centering). */
41
+ /** Outer shell (rounding, centering). Used by `floating` / `flush` navbars. */
32
42
  export interface PublicNavbarShellConfig {
33
43
  /** Tailwind rounding class (e.g. `rounded-3xl`). */
34
44
  rounding?: string;
35
45
  /** Strip + drawer wrapper (e.g. `mx-auto max-w-7xl`). */
36
46
  className?: string;
37
47
  }
48
+
49
+ // ─── Desktop dropdown renderer ───────────────────────────────────────────────
50
+
51
+ export interface PublicDesktopDropdownRenderProps {
52
+ item: NavigationItem;
53
+ isOpen: boolean;
54
+ isActive: boolean;
55
+ close: () => void;
56
+ defaultPopover: ReactNode;
57
+ defaultItems: ReactNode;
58
+ }
59
+
60
+ export type PublicDesktopDropdownRenderer = (props: PublicDesktopDropdownRenderProps) => ReactNode;
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { cn } from '@djangocfg/ui-core/lib';
6
+
7
+ import { publicFloatingChromeClassName } from '../../publicShellShadow';
8
+ import { MobileDrawerShell, type MobileDrawerShellProps } from '../../shared';
9
+
10
+ type FloatingMobileDrawerProps = Omit<MobileDrawerShellProps, 'panelClassName' | 'outerClassName'> & {
11
+ /** Tailwind rounding for the drawer panel. @default 'rounded-2xl' */
12
+ rounding?: string;
13
+ /** Outer wrapper className (e.g. `mx-auto max-w-7xl`). */
14
+ containerClassName?: string;
15
+ };
16
+
17
+ export function FloatingMobileDrawer({
18
+ rounding,
19
+ containerClassName,
20
+ ...rest
21
+ }: FloatingMobileDrawerProps) {
22
+ return (
23
+ <MobileDrawerShell
24
+ {...rest}
25
+ panelClassName={cn(publicFloatingChromeClassName, rounding ?? 'rounded-2xl')}
26
+ outerClassName={containerClassName}
27
+ />
28
+ );
29
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * FloatingNavbar — rounded, inset shell with soft shadow.
3
+ *
4
+ * Pairs with FloatingMobileDrawer (matching rounding + shadow).
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+
11
+ import { cn } from '@djangocfg/ui-core/lib';
12
+
13
+ import { publicFloatingChromeClassName } from '../../publicShellShadow';
14
+ import type { NavAction } from '../../primitives/NavActionItem';
15
+ import { NavbarShell } from '../../shared';
16
+ import type {
17
+ PublicDesktopDropdownRenderer,
18
+ PublicNavbarHeight,
19
+ PublicNavbarPosition,
20
+ PublicNavbarShellConfig,
21
+ PublicNavLayout,
22
+ } from '../../navbarTypes';
23
+ import type { NavigationItem, UserMenuConfig } from '../../../types';
24
+
25
+ import { FloatingMobileDrawer } from './FloatingMobileDrawer';
26
+
27
+ export interface FloatingNavbarConfig {
28
+ shell?: PublicNavbarShellConfig;
29
+ brand?: React.ReactNode;
30
+ /** @default '/' */
31
+ brandHref?: string;
32
+ navigation?: NavigationItem[];
33
+ userMenu?: UserMenuConfig;
34
+ /** @default 'sticky' */
35
+ navbarPosition?: PublicNavbarPosition;
36
+ renderDesktopDropdown?: PublicDesktopDropdownRenderer;
37
+ desktopMaxPrimaryItems?: number;
38
+ /** @default 'default' */
39
+ navLayout?: PublicNavLayout;
40
+ /** @default 'md' */
41
+ navbarHeight?: PublicNavbarHeight;
42
+ /** @default false */
43
+ hideNavOnScroll?: boolean;
44
+ /** @default false */
45
+ transparent?: boolean;
46
+ /** @default 40 */
47
+ transparentThreshold?: number;
48
+ /** Typed CTA pills (Book a demo / Get started / …) before UserMenu. */
49
+ actions?: NavAction[];
50
+ /** Arbitrary ReactNode between actions and UserMenu. */
51
+ actionsLeadingSlot?: React.ReactNode;
52
+ /** Arbitrary ReactNode after the mobile toggle. */
53
+ actionsTrailingSlot?: React.ReactNode;
54
+ }
55
+
56
+ export interface FloatingNavbarProps {
57
+ config: FloatingNavbarConfig;
58
+ }
59
+
60
+ export function FloatingNavbar({ config }: FloatingNavbarProps) {
61
+ const navigation = config.navigation ?? [];
62
+ const rounding = config.shell?.rounding;
63
+ const containerClassName = config.shell?.className;
64
+ const position = config.navbarPosition ?? 'sticky';
65
+
66
+ const outerClassName = cn(
67
+ position === 'fixed' ? 'fixed' : position === 'static' ? 'static' : 'sticky',
68
+ 'top-3 px-3 sm:px-4 lg:px-6',
69
+ );
70
+
71
+ const shapeClassName = cn(
72
+ 'mx-auto w-full',
73
+ rounding ?? 'rounded-2xl',
74
+ publicFloatingChromeClassName,
75
+ containerClassName,
76
+ '!border-0 dark:!border dark:!border-border/75',
77
+ );
78
+
79
+ return (
80
+ <>
81
+ <NavbarShell
82
+ variant="floating"
83
+ position={position}
84
+ brand={config.brand}
85
+ brandHref={config.brandHref}
86
+ navigation={navigation}
87
+ userMenu={config.userMenu}
88
+ renderDesktopDropdown={config.renderDesktopDropdown}
89
+ desktopMaxPrimaryItems={config.desktopMaxPrimaryItems}
90
+ navLayout={config.navLayout}
91
+ navbarHeight={config.navbarHeight}
92
+ hideNavOnScroll={config.hideNavOnScroll}
93
+ transparent={config.transparent}
94
+ transparentThreshold={config.transparentThreshold}
95
+ actions={config.actions}
96
+ actionsLeadingSlot={config.actionsLeadingSlot}
97
+ actionsTrailingSlot={config.actionsTrailingSlot}
98
+ outerClassName={outerClassName}
99
+ shapeClassName={shapeClassName}
100
+ shapeForState={({ scrolled, transparent }) =>
101
+ cn(
102
+ transparent && 'transition-[background-color,backdrop-filter] duration-200 ease-out',
103
+ !transparent || scrolled
104
+ ? 'bg-background/72 backdrop-blur-[10px] dark:bg-card/80'
105
+ : 'bg-transparent backdrop-blur-0 dark:bg-transparent',
106
+ )
107
+ }
108
+ />
109
+ <FloatingMobileDrawer
110
+ navigation={navigation}
111
+ userMenu={config.userMenu}
112
+ containerClassName={containerClassName}
113
+ rounding={rounding}
114
+ />
115
+ </>
116
+ );
117
+ }
@@ -0,0 +1,3 @@
1
+ export { FloatingNavbar } from './FloatingNavbar';
2
+ export type { FloatingNavbarConfig, FloatingNavbarProps } from './FloatingNavbar';
3
+ export { FloatingMobileDrawer } from './FloatingMobileDrawer';
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { MobileDrawerShell, type MobileDrawerShellProps } from '../../shared';
6
+
7
+ type FlushMobileDrawerProps = Omit<MobileDrawerShellProps, 'panelClassName' | 'outerClassName'> & {
8
+ containerClassName?: string;
9
+ };
10
+
11
+ export function FlushMobileDrawer({ containerClassName, ...rest }: FlushMobileDrawerProps) {
12
+ return (
13
+ <MobileDrawerShell
14
+ {...rest}
15
+ outerClassName={containerClassName}
16
+ panelClassName="border border-border/40 dark:border-border/70 rounded-xl"
17
+ />
18
+ );
19
+ }