@djangocfg/layouts 2.1.479 → 2.1.481

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.479",
3
+ "version": "2.1.481",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -89,12 +89,12 @@
89
89
  "check": "tsc --noEmit"
90
90
  },
91
91
  "peerDependencies": {
92
- "@djangocfg/analytics": "^2.1.479",
93
- "@djangocfg/api": "^2.1.479",
94
- "@djangocfg/centrifugo": "^2.1.479",
95
- "@djangocfg/devtools": "^2.1.479",
96
- "@djangocfg/i18n": "^2.1.479",
97
- "@djangocfg/ui-core": "^2.1.479",
92
+ "@djangocfg/analytics": "^2.1.481",
93
+ "@djangocfg/api": "^2.1.481",
94
+ "@djangocfg/centrifugo": "^2.1.481",
95
+ "@djangocfg/devtools": "^2.1.481",
96
+ "@djangocfg/i18n": "^2.1.481",
97
+ "@djangocfg/ui-core": "^2.1.481",
98
98
  "@hookform/resolvers": "^5.2.2",
99
99
  "consola": "^3.4.2",
100
100
  "lucide-react": "^0.545.0",
@@ -124,14 +124,14 @@
124
124
  "uuid": "^11.1.1"
125
125
  },
126
126
  "devDependencies": {
127
- "@djangocfg/analytics": "^2.1.479",
128
- "@djangocfg/api": "^2.1.479",
129
- "@djangocfg/centrifugo": "^2.1.479",
130
- "@djangocfg/devtools": "^2.1.479",
131
- "@djangocfg/i18n": "^2.1.479",
132
- "@djangocfg/typescript-config": "^2.1.479",
133
- "@djangocfg/ui-core": "^2.1.479",
134
- "@djangocfg/ui-tools": "^2.1.479",
127
+ "@djangocfg/analytics": "^2.1.481",
128
+ "@djangocfg/api": "^2.1.481",
129
+ "@djangocfg/centrifugo": "^2.1.481",
130
+ "@djangocfg/devtools": "^2.1.481",
131
+ "@djangocfg/i18n": "^2.1.481",
132
+ "@djangocfg/typescript-config": "^2.1.481",
133
+ "@djangocfg/ui-core": "^2.1.481",
134
+ "@djangocfg/ui-tools": "^2.1.481",
135
135
  "@types/node": "^25.9.5",
136
136
  "@types/react": "19.2.15",
137
137
  "@types/react-dom": "19.2.3",
@@ -99,63 +99,8 @@ function PrivateSidebarAccountRaw({ header }: PrivateSidebarAccountProps) {
99
99
  setSidebarOpen(true);
100
100
  }, [setSidebarOpen]);
101
101
 
102
- // Hide entirely in production when there's no user (auth still loading or
103
- // /me failed and the parent guard hasn't redirected yet). In dev keep a
104
- // placeholder so the footer + Log out are reachable for debugging.
105
- // NOTE: this early-return must stay AFTER all hooks above to keep hook order stable.
106
- if (!user && !isDev) return null;
107
-
108
- const userInitial = (account.displayName.charAt(0) || '?').toUpperCase();
109
- const secondary = header?.footerSecondaryAction;
110
-
111
- const triggerClassName = cn(
112
- 'group h-auto w-full gap-3 rounded-lg px-3 py-3 text-left select-none',
113
- 'hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground',
114
- // No focus ring on the menu trigger — the open menu is the affordance.
115
- 'focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
116
- content.isAccountCompact ? 'justify-center px-0 py-2' : 'min-h-[52px]',
117
- );
118
-
119
- const secondaryButton = secondary && !content.isAccountCompact ? (
120
- <SecondaryAction action={secondary} onParentExpand={onSecondaryExpand} />
121
- ) : null;
122
-
123
- const dropdownContentClass = cn(
124
- 'p-1.5',
125
- content.isAccountCompact ? 'min-w-60' : 'w-[var(--radix-dropdown-menu-trigger-width)] min-w-60',
126
- );
127
- // Always open UPWARD over the avatar (Claude pattern). The footer sits at the
128
- // bottom of the rail in both expanded and collapsed states, so `top` reads
129
- // correctly either way (vs. `right`, which floated off to the side).
130
- const dropdownSide: 'top' = 'top';
131
- const avatarClass = cn(
132
- 'h-9 w-9 shrink-0 border border-transparent transition-colors',
133
- 'group-hover:border-sidebar-border/70',
134
- );
135
-
136
102
  const headerLabelText = account.email ?? (account.source === 'dev-fallback' ? 'No active session' : null);
137
103
 
138
- const expandedMeta = content.isAccountCompact ? null : (
139
- <>
140
- <span className="flex min-w-0 flex-1 flex-col text-left">
141
- <span className="truncate text-sm font-medium leading-tight text-sidebar-foreground">
142
- {account.displayName}
143
- </span>
144
- {account.plan ? (
145
- <span className="truncate text-xs leading-snug text-sidebar-foreground/60">
146
- {account.plan}
147
- </span>
148
- ) : null}
149
- </span>
150
- <span className="flex shrink-0 items-center gap-1.5">
151
- {secondaryButton}
152
- {header?.accountAction !== 'dialog' ? (
153
- <ChevronsUpDown className="h-3.5 w-3.5 text-sidebar-foreground/55" aria-hidden />
154
- ) : null}
155
- </span>
156
- </>
157
- );
158
-
159
104
  // `accountAction: 'dialog'` opens the global SettingsDialog (mounted in
160
105
  // PrivateLayout). Requires `settings` to be passed to PrivateLayout.
161
106
  const openSettingsDialog = React.useCallback(() => {
@@ -239,6 +184,68 @@ function PrivateSidebarAccountRaw({ header }: PrivateSidebarAccountProps) {
239
184
  return items;
240
185
  }, [headerLabelText, t, layoutI18n, accountLinks, signOutLabel, handleLogout]);
241
186
 
187
+ // Hide entirely in production when there's no user (auth still loading or
188
+ // /me failed and the parent guard hasn't redirected yet). In dev keep a
189
+ // placeholder so the footer + Log out are reachable for debugging.
190
+ //
191
+ // EVERY hook must run above this line. When a terminal 401 clears the session,
192
+ // `user` flips to null and this branch starts returning early — if any hook
193
+ // lived below it, the hook count would drop between renders and React would
194
+ // throw #310 ("rendered more hooks than during the previous render"). That is
195
+ // exactly what happened on my.cmdop.com/private/: two hooks sat below and the
196
+ // dashboard white-screened on the 401 transition. It only reproduced in
197
+ // production because `isDev` makes this guard statically dead in development.
198
+ if (!user && !isDev) return null;
199
+
200
+ const userInitial = (account.displayName.charAt(0) || '?').toUpperCase();
201
+ const secondary = header?.footerSecondaryAction;
202
+
203
+ const triggerClassName = cn(
204
+ 'group h-auto w-full gap-3 rounded-lg px-3 py-3 text-left select-none',
205
+ 'hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground',
206
+ // No focus ring on the menu trigger — the open menu is the affordance.
207
+ 'focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
208
+ content.isAccountCompact ? 'justify-center px-0 py-2' : 'min-h-[52px]',
209
+ );
210
+
211
+ const secondaryButton = secondary && !content.isAccountCompact ? (
212
+ <SecondaryAction action={secondary} onParentExpand={onSecondaryExpand} />
213
+ ) : null;
214
+
215
+ const dropdownContentClass = cn(
216
+ 'p-1.5',
217
+ content.isAccountCompact ? 'min-w-60' : 'w-[var(--radix-dropdown-menu-trigger-width)] min-w-60',
218
+ );
219
+ // Always open UPWARD over the avatar (Claude pattern). The footer sits at the
220
+ // bottom of the rail in both expanded and collapsed states, so `top` reads
221
+ // correctly either way (vs. `right`, which floated off to the side).
222
+ const dropdownSide: 'top' = 'top';
223
+ const avatarClass = cn(
224
+ 'h-9 w-9 shrink-0 border border-transparent transition-colors',
225
+ 'group-hover:border-sidebar-border/70',
226
+ );
227
+
228
+ const expandedMeta = content.isAccountCompact ? null : (
229
+ <>
230
+ <span className="flex min-w-0 flex-1 flex-col text-left">
231
+ <span className="truncate text-sm font-medium leading-tight text-sidebar-foreground">
232
+ {account.displayName}
233
+ </span>
234
+ {account.plan ? (
235
+ <span className="truncate text-xs leading-snug text-sidebar-foreground/60">
236
+ {account.plan}
237
+ </span>
238
+ ) : null}
239
+ </span>
240
+ <span className="flex shrink-0 items-center gap-1.5">
241
+ {secondaryButton}
242
+ {header?.accountAction !== 'dialog' ? (
243
+ <ChevronsUpDown className="h-3.5 w-3.5 text-sidebar-foreground/55" aria-hidden />
244
+ ) : null}
245
+ </span>
246
+ </>
247
+ );
248
+
242
249
  const triggerButton = (
243
250
  <Button
244
251
  type="button"