@djangocfg/layouts 2.1.462 → 2.1.464
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/package.json +13 -13
- package/src/layouts/PublicLayout/navbars/FloatingNavbar/FloatingNavbar.tsx +0 -1
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +68 -9
- package/src/layouts/PublicLayout/publicShellShadow.ts +2 -2
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.464",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
"check": "tsc --noEmit"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
|
-
"@djangocfg/api": "^2.1.
|
|
93
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
94
|
-
"@djangocfg/i18n": "^2.1.
|
|
95
|
-
"@djangocfg/ui-core": "^2.1.
|
|
92
|
+
"@djangocfg/api": "^2.1.464",
|
|
93
|
+
"@djangocfg/centrifugo": "^2.1.464",
|
|
94
|
+
"@djangocfg/i18n": "^2.1.464",
|
|
95
|
+
"@djangocfg/ui-core": "^2.1.464",
|
|
96
96
|
"@hookform/resolvers": "^5.2.2",
|
|
97
97
|
"consola": "^3.4.2",
|
|
98
98
|
"lucide-react": "^0.545.0",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"tailwindcss-animate": "^1.0.7",
|
|
110
110
|
"zod": "^4.3.6",
|
|
111
111
|
"zustand": "^5.0.0",
|
|
112
|
-
"@djangocfg/devtools": "^2.1.
|
|
112
|
+
"@djangocfg/devtools": "^2.1.464"
|
|
113
113
|
},
|
|
114
114
|
"peerDependenciesMeta": {
|
|
115
115
|
"@djangocfg/devtools": {
|
|
@@ -124,12 +124,12 @@
|
|
|
124
124
|
"uuid": "^11.1.0"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
-
"@djangocfg/api": "^2.1.
|
|
128
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
129
|
-
"@djangocfg/i18n": "^2.1.
|
|
130
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
131
|
-
"@djangocfg/ui-core": "^2.1.
|
|
132
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
127
|
+
"@djangocfg/api": "^2.1.464",
|
|
128
|
+
"@djangocfg/centrifugo": "^2.1.464",
|
|
129
|
+
"@djangocfg/i18n": "^2.1.464",
|
|
130
|
+
"@djangocfg/typescript-config": "^2.1.464",
|
|
131
|
+
"@djangocfg/ui-core": "^2.1.464",
|
|
132
|
+
"@djangocfg/ui-tools": "^2.1.464",
|
|
133
133
|
"@types/node": "^25.2.3",
|
|
134
134
|
"@types/react": "^19.2.15",
|
|
135
135
|
"@types/react-dom": "^19.2.3",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"next-intl": "^4.9.1",
|
|
138
138
|
"typescript": "^5.9.3",
|
|
139
139
|
"zustand": "^5.0.9",
|
|
140
|
-
"@djangocfg/devtools": "^2.1.
|
|
140
|
+
"@djangocfg/devtools": "^2.1.464"
|
|
141
141
|
},
|
|
142
142
|
"publishConfig": {
|
|
143
143
|
"access": "public"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ChevronDown } from 'lucide-react';
|
|
4
4
|
import React, { memo } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
|
|
6
7
|
import { Button, Link } from '@djangocfg/ui-core/components';
|
|
7
8
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
@@ -23,22 +24,22 @@ interface NavDesktopItemsProps {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const navItemCls = cn(
|
|
26
|
-
'inline-flex min-h-9 items-center justify-center gap-1 rounded-full border-
|
|
27
|
+
'inline-flex min-h-9 items-center justify-center gap-1 rounded-full border border-transparent px-4 py-1.5 text-sm !font-medium',
|
|
27
28
|
'ring-0 focus-visible:ring-0',
|
|
28
29
|
'text-foreground/90 transition-colors',
|
|
29
|
-
'hover
|
|
30
|
+
'surface-hover hover:text-foreground',
|
|
30
31
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35',
|
|
31
32
|
);
|
|
32
33
|
|
|
33
34
|
const navItemActiveCls =
|
|
34
|
-
'border-
|
|
35
|
+
'border-transparent bg-accent text-foreground shadow-sm dark:bg-muted dark:shadow-none';
|
|
35
36
|
|
|
36
37
|
const labelCls = 'min-w-0 max-w-[11rem] truncate sm:max-w-[13rem]';
|
|
37
38
|
|
|
38
39
|
function subMenuLinkCls(active: boolean) {
|
|
39
40
|
return cn(
|
|
40
41
|
'flex min-h-9 min-w-0 max-w-[min(17rem,calc(100vw-5rem))] items-center rounded-full border-0 px-4 py-2 text-sm font-medium transition-colors',
|
|
41
|
-
'hover
|
|
42
|
+
'surface-hover',
|
|
42
43
|
active
|
|
43
44
|
? 'border-0 bg-accent font-semibold text-foreground shadow-sm dark:bg-muted/90 dark:shadow-none'
|
|
44
45
|
: 'border-0 text-foreground/90',
|
|
@@ -46,10 +47,54 @@ function subMenuLinkCls(active: boolean) {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Real glass: low-opacity surface so the blur actually shows the page behind
|
|
49
|
-
// it.
|
|
50
|
-
//
|
|
50
|
+
// it. The package-level nav-dropdown-glass utility keeps the frosted effect
|
|
51
|
+
// present even when the consuming app does not scan layout sources.
|
|
51
52
|
const popoverCls =
|
|
52
|
-
'absolute left-0 top-full mt-1 z-[1200] min-w-[14.5rem] rounded-xl border border-
|
|
53
|
+
'absolute left-0 top-full mt-1 z-[1200] min-w-[14.5rem] rounded-xl border border-border/70 nav-dropdown-glass p-1.5 shadow-[0_1px_2px_rgba(0,0,0,0.05),0_8px_28px_rgba(0,0,0,0.18)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.45)]';
|
|
54
|
+
|
|
55
|
+
const portalPopoverCls =
|
|
56
|
+
'fixed z-[1200] min-w-[14.5rem] rounded-xl border border-border/70 nav-dropdown-glass backdrop-blur-[5px] backdrop-saturate-150 p-1.5 shadow-[0_1px_2px_rgba(0,0,0,0.05),0_8px_28px_rgba(0,0,0,0.18)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.45)]';
|
|
57
|
+
|
|
58
|
+
function DesktopDropdownPortal({
|
|
59
|
+
anchor,
|
|
60
|
+
children,
|
|
61
|
+
onMouseEnter,
|
|
62
|
+
onMouseLeave,
|
|
63
|
+
}: {
|
|
64
|
+
anchor: HTMLElement;
|
|
65
|
+
children: React.ReactNode;
|
|
66
|
+
onMouseEnter: () => void;
|
|
67
|
+
onMouseLeave: () => void;
|
|
68
|
+
}) {
|
|
69
|
+
const [position, setPosition] = React.useState({ left: 0, top: 0 });
|
|
70
|
+
|
|
71
|
+
React.useLayoutEffect(() => {
|
|
72
|
+
const updatePosition = () => {
|
|
73
|
+
const rect = anchor.getBoundingClientRect();
|
|
74
|
+
setPosition({ left: rect.left, top: rect.bottom + 4 });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
updatePosition();
|
|
78
|
+
window.addEventListener('resize', updatePosition);
|
|
79
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
80
|
+
return () => {
|
|
81
|
+
window.removeEventListener('resize', updatePosition);
|
|
82
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
83
|
+
};
|
|
84
|
+
}, [anchor]);
|
|
85
|
+
|
|
86
|
+
return createPortal(
|
|
87
|
+
<div
|
|
88
|
+
className={portalPopoverCls}
|
|
89
|
+
style={position}
|
|
90
|
+
onMouseEnter={onMouseEnter}
|
|
91
|
+
onMouseLeave={onMouseLeave}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</div>,
|
|
95
|
+
document.body,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
53
98
|
|
|
54
99
|
function NavDesktopItemsRaw({
|
|
55
100
|
items,
|
|
@@ -60,6 +105,7 @@ function NavDesktopItemsRaw({
|
|
|
60
105
|
renderDesktopDropdown,
|
|
61
106
|
}: NavDesktopItemsProps) {
|
|
62
107
|
const { openDropdownKey, scheduleOpen, scheduleClose, closeDropdown } = dropdown;
|
|
108
|
+
const [portalAnchor, setPortalAnchor] = React.useState<{ key: string; element: HTMLElement } | null>(null);
|
|
63
109
|
|
|
64
110
|
const { visibleCount, containerRef, itemRef, measured } = useResponsiveOverflow({
|
|
65
111
|
total: items.length,
|
|
@@ -115,11 +161,24 @@ function NavDesktopItemsRaw({
|
|
|
115
161
|
</div>
|
|
116
162
|
);
|
|
117
163
|
|
|
164
|
+
const defaultPortalPopover = isOpen && portalAnchor?.key === key ? (
|
|
165
|
+
<DesktopDropdownPortal
|
|
166
|
+
anchor={portalAnchor.element}
|
|
167
|
+
onMouseEnter={() => { scheduleOpen(key); }}
|
|
168
|
+
onMouseLeave={() => scheduleClose(key)}
|
|
169
|
+
>
|
|
170
|
+
{defaultItems}
|
|
171
|
+
</DesktopDropdownPortal>
|
|
172
|
+
) : null;
|
|
173
|
+
|
|
118
174
|
return (
|
|
119
175
|
<div
|
|
120
176
|
key={key}
|
|
121
177
|
className="relative"
|
|
122
|
-
onMouseEnter={() =>
|
|
178
|
+
onMouseEnter={(event) => {
|
|
179
|
+
setPortalAnchor({ key, element: event.currentTarget });
|
|
180
|
+
scheduleOpen(key);
|
|
181
|
+
}}
|
|
123
182
|
onMouseLeave={() => scheduleClose(key)}
|
|
124
183
|
>
|
|
125
184
|
<Button
|
|
@@ -145,7 +204,7 @@ function NavDesktopItemsRaw({
|
|
|
145
204
|
{isOpen && (
|
|
146
205
|
renderDesktopDropdown
|
|
147
206
|
? renderDesktopDropdown({ item, isOpen, isActive, close: closeDropdown, defaultPopover, defaultItems })
|
|
148
|
-
:
|
|
207
|
+
: defaultPortalPopover
|
|
149
208
|
)}
|
|
150
209
|
</div>
|
|
151
210
|
);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared “floating glass” chrome for navbar + mobile drawer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Both themes use a semantic border; dark gets a slightly stronger edge.
|
|
5
5
|
*/
|
|
6
6
|
export const publicFloatingChromeClassName =
|
|
7
7
|
[
|
|
8
|
-
'
|
|
8
|
+
'border border-border/55 ring-0 outline-none',
|
|
9
9
|
'shadow-[0_1px_6px_rgba(0,0,0,0.028)]',
|
|
10
10
|
'dark:!border dark:!border-border/75',
|
|
11
11
|
'dark:shadow-[0_3px_14px_rgba(0,0,0,0.07)]',
|
|
@@ -182,7 +182,7 @@ export function MobileDrawerShell(props: MobileDrawerShellProps) {
|
|
|
182
182
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35',
|
|
183
183
|
subActive
|
|
184
184
|
? 'border-0 bg-accent font-semibold text-foreground shadow-sm dark:border dark:border-border dark:bg-muted/90 dark:shadow-none'
|
|
185
|
-
: 'border-0 text-muted-foreground hover
|
|
185
|
+
: 'border-0 text-muted-foreground surface-hover hover:text-foreground',
|
|
186
186
|
)}
|
|
187
187
|
>
|
|
188
188
|
{subItem.label}
|
|
@@ -230,4 +230,3 @@ export function MobileDrawerShell(props: MobileDrawerShellProps) {
|
|
|
230
230
|
</>
|
|
231
231
|
);
|
|
232
232
|
}
|
|
233
|
-
|