@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.462",
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.462",
93
- "@djangocfg/centrifugo": "^2.1.462",
94
- "@djangocfg/i18n": "^2.1.462",
95
- "@djangocfg/ui-core": "^2.1.462",
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.462"
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.462",
128
- "@djangocfg/centrifugo": "^2.1.462",
129
- "@djangocfg/i18n": "^2.1.462",
130
- "@djangocfg/typescript-config": "^2.1.462",
131
- "@djangocfg/ui-core": "^2.1.462",
132
- "@djangocfg/ui-tools": "^2.1.462",
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.462"
140
+ "@djangocfg/devtools": "^2.1.464"
141
141
  },
142
142
  "publishConfig": {
143
143
  "access": "public"
@@ -87,7 +87,6 @@ export function FloatingNavbar({ config }: FloatingNavbarProps) {
87
87
  rounding ?? 'rounded-2xl',
88
88
  publicFloatingChromeClassName,
89
89
  containerClassName,
90
- '!border-0 dark:!border dark:!border-border/75',
91
90
  );
92
91
 
93
92
  return (
@@ -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-0 px-4 py-1.5 text-sm font-medium',
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:bg-accent/55 hover:text-foreground',
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-0 bg-accent font-semibold text-foreground shadow-sm dark:bg-muted dark:shadow-none';
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:bg-accent/55',
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. backdrop-blur-2xl + saturate gives the frosted look; the previous
50
- // bg-card/80 (80% opaque) let almost nothing through, so the blur was invisible.
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-white/10 bg-background/55 backdrop-blur-2xl backdrop-saturate-150 dark:bg-card/55 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)]';
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={() => scheduleOpen(key)}
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
- : defaultPopover
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
- * Light: barely-there lift. Dark: one soft outer shadow (border already defines the edge).
4
+ * Both themes use a semantic border; dark gets a slightly stronger edge.
5
5
  */
6
6
  export const publicFloatingChromeClassName =
7
7
  [
8
- '!border-0 ring-0 outline-none',
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:bg-accent/55 hover:text-foreground',
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
-