@djangocfg/layouts 2.1.356 → 2.1.357
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 +17 -17
- package/src/layouts/AdminLayout/AdminLayout.tsx +2 -1
- package/src/layouts/AppLayout/AppLayout.tsx +35 -15
- package/src/layouts/AppLayout/BaseApp.tsx +2 -2
- package/src/layouts/AuthLayout/AuthLayout.tsx +26 -19
- package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +10 -4
- package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -5
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +28 -20
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +12 -4
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +12 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +15 -5
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +9 -4
- package/src/layouts/AuthLayout/context.tsx +35 -13
- package/src/layouts/AuthLayout/shells/AuthShell.tsx +11 -4
- package/src/layouts/AuthLayout/shells/CenteredShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/SplitShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/context.tsx +16 -5
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +32 -247
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +115 -426
- package/src/layouts/{_components → PrivateLayout/components}/PrivateSidebarAccount.tsx +40 -19
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +165 -0
- package/src/layouts/{_components → PrivateLayout/components}/SidebarFeatured.tsx +2 -2
- package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +189 -0
- package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +137 -0
- package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +71 -0
- package/src/layouts/PrivateLayout/components/index.ts +4 -0
- package/src/layouts/PrivateLayout/context.tsx +211 -0
- package/src/layouts/PrivateLayout/density.ts +48 -0
- package/src/layouts/PrivateLayout/hooks/index.ts +13 -0
- package/src/layouts/PrivateLayout/hooks/useAuthGuard.ts +54 -0
- package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +103 -0
- package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +113 -0
- package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +207 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +115 -0
- package/src/layouts/PrivateLayout/index.ts +2 -2
- package/src/layouts/PrivateLayout/types.ts +187 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +44 -183
- package/src/layouts/ProfileLayout/README.md +58 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/ApiKeySection.tsx +197 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/context.tsx +159 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/index.ts +3 -0
- package/src/layouts/ProfileLayout/components/ProfileHeader.tsx +110 -0
- package/src/layouts/ProfileLayout/components/ProfileTab.tsx +29 -0
- package/src/layouts/ProfileLayout/components/{TwoFactorSection.tsx → TwoFactorSection/TwoFactorSection.tsx} +1 -1
- package/src/layouts/ProfileLayout/components/TwoFactorSection/index.ts +1 -0
- package/src/layouts/ProfileLayout/components/index.ts +4 -2
- package/src/layouts/ProfileLayout/context.tsx +4 -6
- package/src/layouts/ProfileLayout/hooks/index.ts +2 -0
- package/src/layouts/ProfileLayout/hooks/useProfileTabs.ts +43 -0
- package/src/layouts/ProfileLayout/index.ts +6 -3
- package/src/layouts/ProfileLayout/types.ts +37 -0
- package/src/layouts/{_components → PublicLayout/components}/UserMenu.tsx +3 -3
- package/src/layouts/PublicLayout/components/index.ts +4 -0
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +12 -2
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +1 -1
- package/src/layouts/PublicLayout/primitives/NavActions.tsx +44 -3
- package/src/layouts/PublicLayout/primitives/NavBrand.tsx +4 -2
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +42 -2
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +1 -1
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +60 -1
- package/src/layouts/_components/index.ts +2 -7
- package/src/layouts/index.ts +9 -4
- package/src/layouts/ProfileLayout/__tests__/TwoFactorSection.test.tsx +0 -234
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +0 -198
- /package/src/layouts/{_components → PublicLayout/components}/UserAvatar.tsx +0 -0
|
@@ -8,165 +8,34 @@
|
|
|
8
8
|
|
|
9
9
|
'use client';
|
|
10
10
|
|
|
11
|
-
import React, { ReactNode
|
|
12
|
-
import { useRouter } from 'next/navigation';
|
|
11
|
+
import React, { ReactNode } from 'react';
|
|
13
12
|
|
|
14
|
-
import { useAuth } from '@djangocfg/api/auth';
|
|
15
13
|
import { Preloader } from '@djangocfg/ui-core/components';
|
|
16
14
|
import { SidebarInset, SidebarProvider } from '@djangocfg/ui-core/components';
|
|
17
15
|
|
|
18
16
|
import type { AppLayoutPublicChrome } from '../AppLayout/AppLayout';
|
|
19
17
|
import type { LayoutVisualConfig } from '../types';
|
|
20
|
-
import { UserMenuConfig } from '../types';
|
|
21
18
|
import { PrivateContent, PrivateSidebar } from './components';
|
|
22
|
-
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
dynamic?: boolean;
|
|
43
|
-
/** Render group as an accordion (Mailersend/Vercel-style). Label becomes a clickable trigger. */
|
|
44
|
-
collapsible?: boolean;
|
|
45
|
-
/** Initial open state for collapsible groups. Auto-expanded if any child is active. Default `false`. */
|
|
46
|
-
defaultOpen?: boolean;
|
|
47
|
-
/** Icon for the group trigger (only when `collapsible`). */
|
|
48
|
-
icon?: string | LucideIconType;
|
|
49
|
-
/**
|
|
50
|
-
* Hide per-item icons inside the group. Defaults to `true` when `collapsible`,
|
|
51
|
-
* `false` otherwise (Mailersend convention: icons live on the trigger, not on children).
|
|
52
|
-
*/
|
|
53
|
-
hideItemIcons?: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Active-state visual treatment for sidebar nav items. */
|
|
57
|
-
export type SidebarActiveIndicator = 'background' | 'rail' | 'both';
|
|
58
|
-
|
|
59
|
-
/** Rendering of group labels. `'uppercase'` is the legacy ultra-light caps; `'plain'` is sm bold. */
|
|
60
|
-
export type SidebarGroupLabelStyle = 'uppercase' | 'plain';
|
|
61
|
-
|
|
62
|
-
/** Featured CTA tile rendered below groups. */
|
|
63
|
-
export interface SidebarFeaturedConfig {
|
|
64
|
-
icon?: string | LucideIconType;
|
|
65
|
-
label: string;
|
|
66
|
-
href: string;
|
|
67
|
-
badge?: string;
|
|
68
|
-
accent?: 'green' | 'blue' | 'amber' | 'primary';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface SidebarConfig {
|
|
72
|
-
/** Grouped items with labels */
|
|
73
|
-
groups: SidebarGroupConfig[];
|
|
74
|
-
/** Home link href */
|
|
75
|
-
homeHref?: string;
|
|
76
|
-
/**
|
|
77
|
-
* Custom block inside the scrollable nav column, **above** all `groups`
|
|
78
|
-
* (below the brand header, same horizontal padding as nav).
|
|
79
|
-
*/
|
|
80
|
-
menuStart?: ReactNode;
|
|
81
|
-
/**
|
|
82
|
-
* Custom block inside the scrollable nav column, **below** all `groups`
|
|
83
|
-
* (above `footer` + account block).
|
|
84
|
-
*/
|
|
85
|
-
menuEnd?: ReactNode;
|
|
86
|
-
/**
|
|
87
|
-
* Keep `menuStart` visible when the desktop sidebar is collapsed to the
|
|
88
|
-
* icon rail. Default `false` — most slot content is full-width and looks
|
|
89
|
-
* broken at ~56px. Set `true` only when the slot renders well in compact mode.
|
|
90
|
-
*/
|
|
91
|
-
menuStartShowOnCollapsed?: boolean;
|
|
92
|
-
/** Same as `menuStartShowOnCollapsed`, but for `menuEnd`. Default `false`. */
|
|
93
|
-
menuEndShowOnCollapsed?: boolean;
|
|
94
|
-
/** Custom footer component rendered at the bottom of the sidebar */
|
|
95
|
-
footer?: ReactNode;
|
|
96
|
-
/** Active-state visual on nav items. Default `'background'` (legacy). */
|
|
97
|
-
activeIndicator?: SidebarActiveIndicator;
|
|
98
|
-
/** Style of group labels. Default `'uppercase'` (legacy). Collapsible groups always use `plain`. */
|
|
99
|
-
groupLabelStyle?: SidebarGroupLabelStyle;
|
|
100
|
-
/** Featured CTA tile rendered below all groups, above `menuEnd`. */
|
|
101
|
-
featured?: SidebarFeaturedConfig;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface HeaderConfig {
|
|
105
|
-
/** Custom header brand node (same idea as PublicNavbar `brand`). */
|
|
106
|
-
brand?: ReactNode;
|
|
107
|
-
/** Shown next to the logo when the sidebar is expanded */
|
|
108
|
-
title?: string;
|
|
109
|
-
/**
|
|
110
|
-
* Brand mark in the sidebar header (Lucide icon name or component).
|
|
111
|
-
* If omitted, a single-letter monogram from `brandLetter` / `title` is shown.
|
|
112
|
-
*/
|
|
113
|
-
brandIcon?: string | LucideIconType;
|
|
114
|
-
/**
|
|
115
|
-
* Monogram when `brandIcon` is not set (one visible character).
|
|
116
|
-
* Defaults to the first letter of `title`, uppercased.
|
|
117
|
-
*/
|
|
118
|
-
brandLetter?: string;
|
|
119
|
-
/** User menu groups (account panel in the sidebar footer) */
|
|
120
|
-
groups?: UserMenuConfig['groups'];
|
|
121
|
-
/** Auth page path (for sign in button) */
|
|
122
|
-
authPath?: string;
|
|
123
|
-
/** Subtitle under the display name in the sidebar footer (e.g. "Max plan"). */
|
|
124
|
-
userPlan?: string;
|
|
125
|
-
/** Optional secondary action button rendered inside the footer trigger (e.g. Get apps download button). */
|
|
126
|
-
footerSecondaryAction?: {
|
|
127
|
-
icon: string | LucideIconType;
|
|
128
|
-
href?: string;
|
|
129
|
-
onClick?: () => void;
|
|
130
|
-
ariaLabel: string;
|
|
131
|
-
/** Show pulsing accent dot on the action (Claude-style "new"). */
|
|
132
|
-
pulse?: boolean;
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface PrivateLayoutProps {
|
|
137
|
-
children: ReactNode;
|
|
138
|
-
/** Sidebar configuration */
|
|
139
|
-
sidebar?: SidebarConfig;
|
|
140
|
-
/** Title + account links (no top navbar — title is used in the sidebar chrome) */
|
|
141
|
-
header?: HeaderConfig;
|
|
142
|
-
/**
|
|
143
|
-
* Path for active nav highlighting. With `@djangocfg/nextjs` i18n routing, pass `usePathname()` from
|
|
144
|
-
* `@djangocfg/nextjs/i18n/navigation` (no `/[locale]` segment). If omitted, uses `next/navigation` (includes locale).
|
|
145
|
-
*/
|
|
146
|
-
pathname?: string;
|
|
147
|
-
/** Content padding */
|
|
148
|
-
contentPadding?: 'none' | 'default';
|
|
149
|
-
/**
|
|
150
|
-
* Content scroll behaviour.
|
|
151
|
-
* - `'auto'` (default) — the shell scroll-area scrolls vertically.
|
|
152
|
-
* - `'hidden'` — shell does NOT scroll; use for full-height pages (e.g. Kanban)
|
|
153
|
-
* where children manage their own scroll surfaces.
|
|
154
|
-
*/
|
|
155
|
-
contentScroll?: 'auto' | 'hidden';
|
|
156
|
-
/**
|
|
157
|
-
* Visual style of the shell. Defaults to `'boxed'` (inset rounded card on a
|
|
158
|
-
* sidebar-coloured canvas). Pass `{ variant: 'full-bleed' }` for the legacy
|
|
159
|
-
* edge-to-edge layout.
|
|
160
|
-
*/
|
|
161
|
-
visual?: LayoutVisualConfig;
|
|
162
|
-
/**
|
|
163
|
-
* Skip the built-in auth guard. Useful for static showcases / playground
|
|
164
|
-
* embeds where there's no real session. Default `true` (guard on).
|
|
165
|
-
*/
|
|
166
|
-
requireAuth?: boolean;
|
|
167
|
-
/** Reserved for `AppLayout` passthrough (`publicChrome`); unused in this layout. */
|
|
168
|
-
publicChrome?: AppLayoutPublicChrome;
|
|
169
|
-
}
|
|
19
|
+
import { useAuthGuard } from './hooks';
|
|
20
|
+
import { useLayoutVisual } from './hooks';
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
HeaderConfig,
|
|
24
|
+
PrivateLayoutProps,
|
|
25
|
+
SidebarConfig,
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
SidebarItem,
|
|
30
|
+
SidebarGroupConfig,
|
|
31
|
+
SidebarConfig,
|
|
32
|
+
HeaderConfig,
|
|
33
|
+
SidebarActiveIndicator,
|
|
34
|
+
SidebarGroupLabelStyle,
|
|
35
|
+
SidebarFeaturedConfig,
|
|
36
|
+
} from './types';
|
|
37
|
+
|
|
38
|
+
export { PrivateLayoutProps };
|
|
170
39
|
|
|
171
40
|
export function PrivateLayout({
|
|
172
41
|
children,
|
|
@@ -178,25 +47,19 @@ export function PrivateLayout({
|
|
|
178
47
|
visual,
|
|
179
48
|
requireAuth = true,
|
|
180
49
|
}: PrivateLayoutProps) {
|
|
181
|
-
const {
|
|
182
|
-
|
|
183
|
-
|
|
50
|
+
const { isLoading, loadingText } = useAuthGuard({
|
|
51
|
+
requireAuth,
|
|
52
|
+
authPath: header?.authPath,
|
|
53
|
+
});
|
|
184
54
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (!isLoading && !isAuthenticated && !isRedirecting) {
|
|
188
|
-
const currentUrl = window.location.pathname + window.location.search;
|
|
189
|
-
saveRedirectUrl(currentUrl);
|
|
190
|
-
setIsRedirecting(true);
|
|
191
|
-
router.push(header?.authPath || '/auth');
|
|
192
|
-
}
|
|
193
|
-
}, [requireAuth, isAuthenticated, isLoading, isRedirecting, router, saveRedirectUrl, header?.authPath]);
|
|
55
|
+
const { providerStyle, providerClassName, insetClassName, sidebarVariant } =
|
|
56
|
+
useLayoutVisual(visual);
|
|
194
57
|
|
|
195
|
-
if (
|
|
58
|
+
if (isLoading) {
|
|
196
59
|
return (
|
|
197
60
|
<Preloader
|
|
198
61
|
variant="fullscreen"
|
|
199
|
-
text={
|
|
62
|
+
text={loadingText}
|
|
200
63
|
size="lg"
|
|
201
64
|
backdrop={true}
|
|
202
65
|
backdropOpacity={80}
|
|
@@ -204,14 +67,11 @@ export function PrivateLayout({
|
|
|
204
67
|
);
|
|
205
68
|
}
|
|
206
69
|
|
|
207
|
-
const variant: LayoutVisualConfig['variant'] = visual?.variant ?? 'boxed';
|
|
208
|
-
const sidebarVariant = variant === 'boxed' ? 'inset' : 'sidebar';
|
|
209
|
-
|
|
210
70
|
return (
|
|
211
71
|
<SidebarProvider
|
|
212
72
|
defaultOpen={true}
|
|
213
|
-
style={
|
|
214
|
-
className={
|
|
73
|
+
style={providerStyle}
|
|
74
|
+
className={providerClassName}
|
|
215
75
|
>
|
|
216
76
|
{sidebar && (
|
|
217
77
|
<PrivateSidebar
|
|
@@ -222,7 +82,7 @@ export function PrivateLayout({
|
|
|
222
82
|
/>
|
|
223
83
|
)}
|
|
224
84
|
|
|
225
|
-
<SidebarInset className={
|
|
85
|
+
<SidebarInset className={insetClassName}>
|
|
226
86
|
<PrivateContent
|
|
227
87
|
padding={contentPadding}
|
|
228
88
|
scroll={contentScroll}
|
|
@@ -235,78 +95,3 @@ export function PrivateLayout({
|
|
|
235
95
|
</SidebarProvider>
|
|
236
96
|
);
|
|
237
97
|
}
|
|
238
|
-
|
|
239
|
-
/** CSS variables consumed by the boxed `SidebarInset` (margin + radius). */
|
|
240
|
-
function resolveProviderStyle(visual: LayoutVisualConfig | undefined): React.CSSProperties | undefined {
|
|
241
|
-
if ((visual?.variant ?? 'boxed') !== 'boxed') return undefined;
|
|
242
|
-
const inset = normaliseInset(visual?.inset);
|
|
243
|
-
return {
|
|
244
|
-
['--app-shell-inset-x' as string]: `${inset.x}px`,
|
|
245
|
-
['--app-shell-inset-y' as string]: `${inset.y}px`,
|
|
246
|
-
['--app-shell-radius' as string]: BOXED_RADIUS_REM[visual?.radius ?? '2xl'],
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Statically-known Tailwind classes for the boxed inset. Margin and radius are
|
|
252
|
-
* driven by the CSS variables set in `resolveProviderStyle`, so JIT can fully
|
|
253
|
-
* extract these classes at build time.
|
|
254
|
-
*/
|
|
255
|
-
const BOXED_INSET_CLASS = [
|
|
256
|
-
'flex flex-col',
|
|
257
|
-
'md:peer-data-[variant=inset]:my-[var(--app-shell-inset-y)]',
|
|
258
|
-
'md:peer-data-[variant=inset]:mr-[var(--app-shell-inset-x)]',
|
|
259
|
-
'md:peer-data-[variant=inset]:rounded-[var(--app-shell-radius)]',
|
|
260
|
-
'md:peer-data-[variant=inset]:overflow-hidden',
|
|
261
|
-
].join(' ');
|
|
262
|
-
|
|
263
|
-
const BOXED_INSET_BORDER_CLASS =
|
|
264
|
-
'md:peer-data-[variant=inset]:border md:peer-data-[variant=inset]:border-border/60';
|
|
265
|
-
|
|
266
|
-
const BOXED_RADIUS_REM: Record<NonNullable<LayoutVisualConfig['radius']>, string> = {
|
|
267
|
-
sm: '0.375rem',
|
|
268
|
-
md: '0.5rem',
|
|
269
|
-
lg: '0.75rem',
|
|
270
|
-
xl: '1rem',
|
|
271
|
-
'2xl': '1.25rem',
|
|
272
|
-
'3xl': '1.75rem',
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
function resolveInsetClassName(visual: LayoutVisualConfig | undefined): string {
|
|
276
|
-
if ((visual?.variant ?? 'boxed') !== 'boxed') return 'flex flex-col';
|
|
277
|
-
const border = visual?.border ?? true;
|
|
278
|
-
return border ? `${BOXED_INSET_CLASS} ${BOXED_INSET_BORDER_CLASS}` : BOXED_INSET_CLASS;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Background painted *behind* the boxed container on md+. On mobile the
|
|
283
|
-
* canvas tint is dropped because the sidebar is a Drawer — leaking the
|
|
284
|
-
* canvas colour to the whole viewport just makes the page look dim.
|
|
285
|
-
*
|
|
286
|
-
* `bg-sidebar` (the default) overrides shadcn-sidebar's built-in
|
|
287
|
-
* `has-[&_[data-variant=inset]]:bg-sidebar` only at the breakpoint where
|
|
288
|
-
* the inset shape actually exists.
|
|
289
|
-
*/
|
|
290
|
-
const BOXED_BG_CLASS: Record<NonNullable<LayoutVisualConfig['background']>, string> = {
|
|
291
|
-
sidebar: 'md:!bg-sidebar',
|
|
292
|
-
muted: 'md:!bg-muted',
|
|
293
|
-
card: 'md:!bg-card',
|
|
294
|
-
background: 'md:!bg-background',
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
function resolveProviderClassName(visual: LayoutVisualConfig | undefined): string | undefined {
|
|
298
|
-
// h-svh + overflow-hidden: lock the shell to exactly one viewport height so
|
|
299
|
-
// the inner scroll-area (PrivateContent) is the only scroll surface. Without
|
|
300
|
-
// this, SidebarProvider grows via min-h-svh and the whole page scrolls.
|
|
301
|
-
const base = 'h-svh overflow-hidden';
|
|
302
|
-
if ((visual?.variant ?? 'boxed') !== 'boxed') return base;
|
|
303
|
-
// `max-md:!bg-background` neutralises shadcn-sidebar's built-in
|
|
304
|
-
// `has-[[data-variant=inset]]:bg-sidebar` below md so the mobile Drawer shell
|
|
305
|
-
// doesn't paint the whole viewport with the canvas tint.
|
|
306
|
-
return `${base} max-md:!bg-background ${BOXED_BG_CLASS[visual?.background ?? 'sidebar']}`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function normaliseInset(inset: LayoutVisualConfig['inset']): { x: number; y: number } {
|
|
310
|
-
if (typeof inset === 'number') return { x: inset, y: inset };
|
|
311
|
-
return { x: inset?.x ?? 12, y: inset?.y ?? 12 };
|
|
312
|
-
}
|