@djangocfg/layouts 2.1.256 → 2.1.259
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 +101 -203
- package/package.json +18 -18
- package/src/index.ts +4 -1
- package/src/layouts/AppLayout/AppLayout.tsx +97 -8
- package/src/layouts/AppLayout/BaseApp.tsx +2 -0
- package/src/layouts/AppLayout/index.ts +6 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +3 -1
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +15 -4
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +3 -3
- package/src/layouts/PublicLayout/PublicLayout.tsx +82 -17
- package/src/layouts/PublicLayout/components/PublicFooter/FooterProjectInfo.tsx +17 -24
- package/src/layouts/PublicLayout/components/PublicFooter/PublicFooter.tsx +79 -95
- package/src/layouts/PublicLayout/components/PublicFooter/index.ts +2 -0
- package/src/layouts/PublicLayout/components/PublicFooter/types.ts +41 -31
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +84 -40
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +22 -35
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +184 -98
- package/src/layouts/PublicLayout/components/ThemeBrandMark.tsx +83 -0
- package/src/layouts/PublicLayout/components/index.ts +2 -0
- package/src/layouts/PublicLayout/context.tsx +5 -0
- package/src/layouts/PublicLayout/hooks/index.ts +1 -1
- package/src/layouts/PublicLayout/hooks/useMobileNavPanel.ts +55 -0
- package/src/layouts/PublicLayout/index.ts +8 -0
- package/src/layouts/PublicLayout/navbarTypes.ts +20 -0
- package/src/layouts/PublicLayout/publicShellShadow.ts +12 -0
- package/src/layouts/_components/PrivateSidebarAccount.tsx +16 -3
- package/src/layouts/_components/UserMenu.tsx +133 -30
- package/src/layouts/types/index.ts +10 -1
- package/src/layouts/types/providers.types.ts +10 -0
- package/src/layouts/types/ui.types.ts +9 -0
- package/src/theme/ThemeStyleBridge.tsx +41 -0
- package/src/theme/buildThemeStyleSheet.ts +71 -0
- package/src/theme/index.ts +16 -0
- package/src/theme/themeStyle.types.ts +89 -0
- package/src/theme/themeStylePresets.ts +202 -0
- package/src/layouts/PublicLayout/hooks/useFloatingPanel.ts +0 -61
|
@@ -13,11 +13,13 @@ 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 { cn } from '@djangocfg/ui-core/lib';
|
|
16
17
|
import { usePathnameWithoutLocale } from '../../../hooks';
|
|
17
18
|
|
|
18
19
|
import { UserMenu } from '../../_components/UserMenu';
|
|
20
|
+
import { publicFloatingChromeClassName } from '../publicShellShadow';
|
|
19
21
|
import { usePublicLayoutOptional } from '../context';
|
|
20
|
-
import {
|
|
22
|
+
import { useMobileNavPanel } from '../hooks';
|
|
21
23
|
|
|
22
24
|
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
23
25
|
|
|
@@ -27,6 +29,11 @@ interface PublicMobileDrawerProps {
|
|
|
27
29
|
navigation?: NavigationItem[];
|
|
28
30
|
userMenu?: UserMenuConfig;
|
|
29
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;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
@@ -36,10 +43,11 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
36
43
|
const navigation = props.navigation ?? [];
|
|
37
44
|
const userMenu = props.userMenu;
|
|
38
45
|
const containerClassName = props.containerClassName;
|
|
39
|
-
const
|
|
46
|
+
const rounding = props.rounding;
|
|
47
|
+
const { isAuthenticated, user } = useAuth();
|
|
40
48
|
const pathname = usePathnameWithoutLocale();
|
|
41
49
|
const t = useAppT();
|
|
42
|
-
const {
|
|
50
|
+
const { mounted, visible } = useMobileNavPanel({
|
|
43
51
|
isOpen: mobileMenuOpen,
|
|
44
52
|
onClose: closeMobileMenu,
|
|
45
53
|
});
|
|
@@ -61,7 +69,10 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
61
69
|
return pathname === href || pathname.startsWith(`${href}/`);
|
|
62
70
|
};
|
|
63
71
|
|
|
64
|
-
if (!
|
|
72
|
+
if (!mounted) return null;
|
|
73
|
+
|
|
74
|
+
const hasSessionUser = Boolean(isAuthenticated && user);
|
|
75
|
+
const showSignInFooter = !hasSessionUser;
|
|
65
76
|
|
|
66
77
|
return (
|
|
67
78
|
<>
|
|
@@ -73,26 +84,32 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
73
84
|
onClick={closeMobileMenu}
|
|
74
85
|
/>
|
|
75
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. */}
|
|
76
89
|
<div
|
|
77
|
-
className="fixed inset-x-0 z-1000 lg:hidden px-4 sm:px-6 lg:px-8"
|
|
78
|
-
style={{
|
|
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
|
+
}}
|
|
79
95
|
>
|
|
80
96
|
<div
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
)}
|
|
87
106
|
style={{
|
|
88
107
|
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
108
|
}}
|
|
92
109
|
>
|
|
93
110
|
{/* Scrollable content */}
|
|
94
111
|
<div className="flex-1 min-h-0 overflow-y-auto px-4 py-4 space-y-5">
|
|
95
|
-
{
|
|
112
|
+
{hasSessionUser && (
|
|
96
113
|
<div className="px-2">
|
|
97
114
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
98
115
|
{labels.quickActions}
|
|
@@ -100,8 +117,8 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
100
117
|
</div>
|
|
101
118
|
)}
|
|
102
119
|
|
|
103
|
-
{
|
|
104
|
-
<UserMenu variant="mobile" groups={userMenu?.groups} authPath={userMenu?.authPath} />
|
|
120
|
+
{hasSessionUser && (
|
|
121
|
+
<UserMenu variant="mobile" groups={userMenu?.groups} authPath={userMenu?.authPath} i18n={userMenu?.i18n} />
|
|
105
122
|
)}
|
|
106
123
|
|
|
107
124
|
{/* Navigation Items */}
|
|
@@ -112,49 +129,76 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
112
129
|
</h3>
|
|
113
130
|
</div>
|
|
114
131
|
<div className="space-y-1">
|
|
115
|
-
{mobileNavigation.map((item) =>
|
|
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 (
|
|
116
141
|
<div key={item.href}>
|
|
117
142
|
<Link
|
|
118
143
|
href={item.href}
|
|
119
144
|
onClick={closeMobileMenu}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
)}
|
|
125
155
|
>
|
|
126
156
|
{item.label}
|
|
127
157
|
</Link>
|
|
128
|
-
{
|
|
129
|
-
<div className="ml-3 mt-1 space-y-1 border-l border-border/40 pl-3">
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
})}
|
|
140
180
|
</div>
|
|
141
181
|
)}
|
|
142
182
|
</div>
|
|
143
|
-
)
|
|
183
|
+
);
|
|
184
|
+
})}
|
|
144
185
|
</div>
|
|
145
186
|
</div>
|
|
146
187
|
</div>
|
|
147
188
|
|
|
148
|
-
{
|
|
189
|
+
{showSignInFooter && (
|
|
149
190
|
<div className="shrink-0 border-t border-border/50 p-4">
|
|
150
191
|
<Link
|
|
151
192
|
href={userMenu?.authPath || '/auth'}
|
|
152
193
|
onClick={closeMobileMenu}
|
|
153
194
|
className="block"
|
|
154
195
|
>
|
|
155
|
-
<Button className="w-full justify-
|
|
156
|
-
|
|
157
|
-
<ArrowRight
|
|
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
|
+
/>
|
|
158
202
|
</Button>
|
|
159
203
|
</Link>
|
|
160
204
|
</div>
|
|
@@ -6,69 +6,56 @@ import { PublicMobileDrawer } from './PublicMobileDrawer';
|
|
|
6
6
|
import { PublicNavigation } from './PublicNavigation';
|
|
7
7
|
|
|
8
8
|
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
9
|
+
import type { PublicDesktopDropdownRenderer } from './PublicNavigation';
|
|
9
10
|
import type {
|
|
10
|
-
|
|
11
|
+
PublicNavbarShellConfig,
|
|
11
12
|
PublicNavbarPosition,
|
|
12
13
|
PublicNavbarVariant,
|
|
13
|
-
} from '
|
|
14
|
+
} from '../navbarTypes';
|
|
14
15
|
|
|
15
16
|
export interface PublicNavbarConfig {
|
|
17
|
+
shell?: PublicNavbarShellConfig;
|
|
18
|
+
/** Brand: custom React node, or a plain string (wrapped in `<Link href={brandHref}>`). */
|
|
16
19
|
brand?: React.ReactNode;
|
|
20
|
+
/** @default '/' */
|
|
17
21
|
brandHref?: string;
|
|
18
|
-
logo?: string;
|
|
19
|
-
siteName?: string;
|
|
20
22
|
navigation?: NavigationItem[];
|
|
21
23
|
userMenu?: UserMenuConfig;
|
|
22
|
-
containerClassName?: string;
|
|
23
24
|
navbarVariant?: PublicNavbarVariant;
|
|
24
25
|
navbarPosition?: PublicNavbarPosition;
|
|
25
26
|
renderDesktopDropdown?: PublicDesktopDropdownRenderer;
|
|
26
27
|
desktopMaxPrimaryItems?: number;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export interface PublicNavbarProps
|
|
30
|
-
|
|
31
|
-
* Preferred API: pass everything as a single config object.
|
|
32
|
-
* Flat props are still supported and override `config`.
|
|
33
|
-
*/
|
|
34
|
-
config?: PublicNavbarConfig;
|
|
30
|
+
export interface PublicNavbarProps {
|
|
31
|
+
config: PublicNavbarConfig;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
export function PublicNavbar(
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
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;
|
|
34
|
+
export function PublicNavbar({ config }: PublicNavbarProps) {
|
|
35
|
+
const rounding = config.shell?.rounding;
|
|
36
|
+
const containerClassName = config.shell?.className;
|
|
37
|
+
const navigation = config.navigation ?? [];
|
|
50
38
|
|
|
51
39
|
return (
|
|
52
40
|
<>
|
|
53
41
|
<PublicNavigation
|
|
54
|
-
brand={brand}
|
|
55
|
-
brandHref={brandHref}
|
|
56
|
-
logo={logo}
|
|
57
|
-
siteName={siteName}
|
|
42
|
+
brand={config.brand}
|
|
43
|
+
brandHref={config.brandHref}
|
|
58
44
|
navigation={navigation}
|
|
59
|
-
userMenu={userMenu}
|
|
45
|
+
userMenu={config.userMenu}
|
|
60
46
|
containerClassName={containerClassName}
|
|
61
|
-
navbarVariant={navbarVariant}
|
|
62
|
-
navbarPosition={navbarPosition}
|
|
63
|
-
renderDesktopDropdown={renderDesktopDropdown}
|
|
64
|
-
desktopMaxPrimaryItems={desktopMaxPrimaryItems}
|
|
47
|
+
navbarVariant={config.navbarVariant}
|
|
48
|
+
navbarPosition={config.navbarPosition}
|
|
49
|
+
renderDesktopDropdown={config.renderDesktopDropdown}
|
|
50
|
+
desktopMaxPrimaryItems={config.desktopMaxPrimaryItems}
|
|
51
|
+
rounding={rounding}
|
|
65
52
|
/>
|
|
66
53
|
<PublicMobileDrawer
|
|
67
54
|
navigation={navigation}
|
|
68
|
-
userMenu={userMenu}
|
|
55
|
+
userMenu={config.userMenu}
|
|
69
56
|
containerClassName={containerClassName}
|
|
57
|
+
rounding={rounding}
|
|
70
58
|
/>
|
|
71
59
|
</>
|
|
72
60
|
);
|
|
73
61
|
}
|
|
74
|
-
|