@djangocfg/layouts 2.1.257 → 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 +4 -1
- package/src/layouts/PublicLayout/PublicLayout.tsx +31 -8
- 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 +69 -30
- package/src/layouts/PublicLayout/components/PublicNavbar.tsx +24 -34
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +162 -94
- package/src/layouts/PublicLayout/components/ThemeBrandMark.tsx +83 -0
- package/src/layouts/PublicLayout/components/index.ts +2 -0
- package/src/layouts/PublicLayout/index.ts +5 -0
- package/src/layouts/PublicLayout/navbarTypes.ts +8 -0
- package/src/layouts/PublicLayout/publicShellShadow.ts +12 -0
- package/src/layouts/_components/UserMenu.tsx +2 -2
- package/src/layouts/types/index.ts +9 -1
- package/src/layouts/types/providers.types.ts +10 -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
|
@@ -13,9 +13,11 @@ 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
22
|
import { useMobileNavPanel } from '../hooks';
|
|
21
23
|
|
|
@@ -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,7 +43,8 @@ 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
50
|
const { mounted, visible } = useMobileNavPanel({
|
|
@@ -63,6 +71,9 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
63
71
|
|
|
64
72
|
if (!mounted) return null;
|
|
65
73
|
|
|
74
|
+
const hasSessionUser = Boolean(isAuthenticated && user);
|
|
75
|
+
const showSignInFooter = !hasSessionUser;
|
|
76
|
+
|
|
66
77
|
return (
|
|
67
78
|
<>
|
|
68
79
|
{mobileMenuOpen && (
|
|
@@ -76,24 +87,29 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
76
87
|
{/* Outer shell must not capture taps when the panel is closed: with pointer-events-none on the
|
|
77
88
|
inner panel, events would otherwise hit this transparent fixed layer (z-1000) and block the page. */}
|
|
78
89
|
<div
|
|
79
|
-
className="pointer-events-none fixed inset-x-0 z-1000 lg:hidden px-4 sm:px-6 lg:px-8"
|
|
80
|
-
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
|
+
}}
|
|
81
95
|
>
|
|
82
96
|
<div
|
|
83
|
-
className={
|
|
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,
|
|
84
102
|
visible
|
|
85
103
|
? 'pointer-events-auto opacity-100 translate-y-0 scale-100'
|
|
86
|
-
: 'pointer-events-none opacity-0 -translate-y-2 scale-[0.985]'
|
|
87
|
-
}
|
|
104
|
+
: 'pointer-events-none opacity-0 -translate-y-2 scale-[0.985]',
|
|
105
|
+
)}
|
|
88
106
|
style={{
|
|
89
107
|
maxHeight: 'min(var(--public-navbar-mobile-drawer-max-height, calc(100dvh - 5rem - 12px)), calc(100dvh - 12px))',
|
|
90
|
-
backgroundColor: 'hsl(var(--background) / 0.72)',
|
|
91
|
-
backdropFilter: 'blur(10px)',
|
|
92
108
|
}}
|
|
93
109
|
>
|
|
94
110
|
{/* Scrollable content */}
|
|
95
111
|
<div className="flex-1 min-h-0 overflow-y-auto px-4 py-4 space-y-5">
|
|
96
|
-
{
|
|
112
|
+
{hasSessionUser && (
|
|
97
113
|
<div className="px-2">
|
|
98
114
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
99
115
|
{labels.quickActions}
|
|
@@ -101,7 +117,7 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
101
117
|
</div>
|
|
102
118
|
)}
|
|
103
119
|
|
|
104
|
-
{
|
|
120
|
+
{hasSessionUser && (
|
|
105
121
|
<UserMenu variant="mobile" groups={userMenu?.groups} authPath={userMenu?.authPath} i18n={userMenu?.i18n} />
|
|
106
122
|
)}
|
|
107
123
|
|
|
@@ -116,31 +132,51 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
116
132
|
{mobileNavigation.map((item) => {
|
|
117
133
|
const childItems = item.items ?? [];
|
|
118
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;
|
|
119
140
|
return (
|
|
120
141
|
<div key={item.href}>
|
|
121
142
|
<Link
|
|
122
143
|
href={item.href}
|
|
123
144
|
onClick={closeMobileMenu}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
)}
|
|
129
155
|
>
|
|
130
156
|
{item.label}
|
|
131
157
|
</Link>
|
|
132
158
|
{hasChildNav && (
|
|
133
|
-
<div className="ml-3 mt-1 space-y-1 border-l border-border/40 pl-3">
|
|
134
|
-
{childItems.map((subItem) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
})}
|
|
144
180
|
</div>
|
|
145
181
|
)}
|
|
146
182
|
</div>
|
|
@@ -150,16 +186,19 @@ export function PublicMobileDrawer(props: PublicMobileDrawerProps = {}) {
|
|
|
150
186
|
</div>
|
|
151
187
|
</div>
|
|
152
188
|
|
|
153
|
-
{
|
|
189
|
+
{showSignInFooter && (
|
|
154
190
|
<div className="shrink-0 border-t border-border/50 p-4">
|
|
155
191
|
<Link
|
|
156
192
|
href={userMenu?.authPath || '/auth'}
|
|
157
193
|
onClick={closeMobileMenu}
|
|
158
194
|
className="block"
|
|
159
195
|
>
|
|
160
|
-
<Button className="w-full justify-
|
|
161
|
-
|
|
162
|
-
<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
|
+
/>
|
|
163
202
|
</Button>
|
|
164
203
|
</Link>
|
|
165
204
|
</div>
|
|
@@ -7,65 +7,55 @@ import { PublicNavigation } from './PublicNavigation';
|
|
|
7
7
|
|
|
8
8
|
import type { NavigationItem, UserMenuConfig } from '../../types';
|
|
9
9
|
import type { PublicDesktopDropdownRenderer } from './PublicNavigation';
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
PublicNavbarShellConfig,
|
|
12
|
+
PublicNavbarPosition,
|
|
13
|
+
PublicNavbarVariant,
|
|
14
|
+
} from '../navbarTypes';
|
|
11
15
|
|
|
12
16
|
export interface PublicNavbarConfig {
|
|
17
|
+
shell?: PublicNavbarShellConfig;
|
|
18
|
+
/** Brand: custom React node, or a plain string (wrapped in `<Link href={brandHref}>`). */
|
|
13
19
|
brand?: React.ReactNode;
|
|
20
|
+
/** @default '/' */
|
|
14
21
|
brandHref?: string;
|
|
15
|
-
logo?: string;
|
|
16
|
-
siteName?: string;
|
|
17
22
|
navigation?: NavigationItem[];
|
|
18
23
|
userMenu?: UserMenuConfig;
|
|
19
|
-
containerClassName?: string;
|
|
20
24
|
navbarVariant?: PublicNavbarVariant;
|
|
21
25
|
navbarPosition?: PublicNavbarPosition;
|
|
22
26
|
renderDesktopDropdown?: PublicDesktopDropdownRenderer;
|
|
23
27
|
desktopMaxPrimaryItems?: number;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
export interface PublicNavbarProps
|
|
27
|
-
|
|
28
|
-
* Preferred API: pass everything as a single config object.
|
|
29
|
-
* Flat props are still supported and override `config`.
|
|
30
|
-
*/
|
|
31
|
-
config?: PublicNavbarConfig;
|
|
30
|
+
export interface PublicNavbarProps {
|
|
31
|
+
config: PublicNavbarConfig;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function PublicNavbar(
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const logo = props.logo ?? cfg.logo;
|
|
39
|
-
const siteName = props.siteName ?? cfg.siteName;
|
|
40
|
-
const navigation = props.navigation ?? cfg.navigation ?? [];
|
|
41
|
-
const userMenu = props.userMenu ?? cfg.userMenu;
|
|
42
|
-
const containerClassName = props.containerClassName ?? cfg.containerClassName;
|
|
43
|
-
const navbarVariant = props.navbarVariant ?? cfg.navbarVariant;
|
|
44
|
-
const navbarPosition = props.navbarPosition ?? cfg.navbarPosition;
|
|
45
|
-
const renderDesktopDropdown = props.renderDesktopDropdown ?? cfg.renderDesktopDropdown;
|
|
46
|
-
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 ?? [];
|
|
47
38
|
|
|
48
39
|
return (
|
|
49
40
|
<>
|
|
50
41
|
<PublicNavigation
|
|
51
|
-
brand={brand}
|
|
52
|
-
brandHref={brandHref}
|
|
53
|
-
logo={logo}
|
|
54
|
-
siteName={siteName}
|
|
42
|
+
brand={config.brand}
|
|
43
|
+
brandHref={config.brandHref}
|
|
55
44
|
navigation={navigation}
|
|
56
|
-
userMenu={userMenu}
|
|
45
|
+
userMenu={config.userMenu}
|
|
57
46
|
containerClassName={containerClassName}
|
|
58
|
-
navbarVariant={navbarVariant}
|
|
59
|
-
navbarPosition={navbarPosition}
|
|
60
|
-
renderDesktopDropdown={renderDesktopDropdown}
|
|
61
|
-
desktopMaxPrimaryItems={desktopMaxPrimaryItems}
|
|
47
|
+
navbarVariant={config.navbarVariant}
|
|
48
|
+
navbarPosition={config.navbarPosition}
|
|
49
|
+
renderDesktopDropdown={config.renderDesktopDropdown}
|
|
50
|
+
desktopMaxPrimaryItems={config.desktopMaxPrimaryItems}
|
|
51
|
+
rounding={rounding}
|
|
62
52
|
/>
|
|
63
53
|
<PublicMobileDrawer
|
|
64
54
|
navigation={navigation}
|
|
65
|
-
userMenu={userMenu}
|
|
55
|
+
userMenu={config.userMenu}
|
|
66
56
|
containerClassName={containerClassName}
|
|
57
|
+
rounding={rounding}
|
|
67
58
|
/>
|
|
68
59
|
</>
|
|
69
60
|
);
|
|
70
61
|
}
|
|
71
|
-
|