@djangocfg/layouts 2.1.358 → 2.1.359
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/configurator/private/schema.ts +8 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +4 -0
- package/src/layouts/PrivateLayout/README.md +47 -1
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +5 -66
- package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +1 -6
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +34 -18
- package/src/layouts/PrivateLayout/components/SidebarBrandSwitcher.tsx +223 -0
- package/src/layouts/PrivateLayout/components/index.ts +1 -0
- package/src/layouts/PrivateLayout/context.tsx +2 -9
- package/src/layouts/PrivateLayout/hooks/index.ts +0 -5
- package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +11 -88
- package/src/layouts/PrivateLayout/hooks/useSidebarDefaultOpen.ts +16 -5
- package/src/layouts/PrivateLayout/index.ts +3 -0
- package/src/layouts/PrivateLayout/types.ts +35 -0
- package/src/layouts/types/providers.types.ts +2 -2
- package/src/theme/ThemeStyleBridge.tsx +1 -3
- package/src/theme/index.ts +2 -4
- package/src/theme/buildThemeStyleSheet.ts +0 -71
- package/src/theme/themeStyle.types.ts +0 -89
- package/src/theme/themeStylePresets.ts +0 -202
|
@@ -41,8 +41,6 @@ export interface PrivateLayoutContextValue {
|
|
|
41
41
|
isExpanded: boolean;
|
|
42
42
|
/** Whether the sidebar is collapsed to icon rail */
|
|
43
43
|
isCollapsed: boolean;
|
|
44
|
-
/** Whether the sidebar is temporarily hover-expanded overlay */
|
|
45
|
-
isHoverExpanded: boolean;
|
|
46
44
|
/** Nav density based on total item count */
|
|
47
45
|
density: NavDensity;
|
|
48
46
|
/** Density tokens for the current state */
|
|
@@ -94,7 +92,6 @@ interface PrivateLayoutProviderProps {
|
|
|
94
92
|
pathname: string;
|
|
95
93
|
isMobile: boolean;
|
|
96
94
|
state: 'expanded' | 'collapsed';
|
|
97
|
-
isHoverExpanded?: boolean;
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
// ============================================================================
|
|
@@ -108,7 +105,6 @@ export function PrivateLayoutProvider({
|
|
|
108
105
|
pathname,
|
|
109
106
|
isMobile,
|
|
110
107
|
state,
|
|
111
|
-
isHoverExpanded = false,
|
|
112
108
|
}: PrivateLayoutProviderProps) {
|
|
113
109
|
const homeHref = sidebar?.homeHref || '/';
|
|
114
110
|
const brandTitle = header?.title?.trim() || 'Dashboard';
|
|
@@ -127,8 +123,7 @@ export function PrivateLayoutProvider({
|
|
|
127
123
|
const isExpanded = state === 'expanded';
|
|
128
124
|
const isCollapsed = state === 'collapsed';
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
const menuNav = isCollapsed && !isHoverExpanded ? RAIL_NAV : DENSITY[density];
|
|
126
|
+
const menuNav = isCollapsed ? RAIL_NAV : DENSITY[density];
|
|
132
127
|
|
|
133
128
|
const isActive = React.useCallback(
|
|
134
129
|
(href: string) => {
|
|
@@ -144,7 +139,7 @@ export function PrivateLayoutProvider({
|
|
|
144
139
|
[pathname, allItems],
|
|
145
140
|
);
|
|
146
141
|
|
|
147
|
-
const collapsedRail = !isMobile && isCollapsed
|
|
142
|
+
const collapsedRail = !isMobile && isCollapsed;
|
|
148
143
|
|
|
149
144
|
const hasMenuStart = sidebar?.menuStart != null && sidebar.menuStart !== false;
|
|
150
145
|
const hasMenuEnd = sidebar?.menuEnd != null && sidebar.menuEnd !== false;
|
|
@@ -166,7 +161,6 @@ export function PrivateLayoutProvider({
|
|
|
166
161
|
isMobile,
|
|
167
162
|
isExpanded,
|
|
168
163
|
isCollapsed,
|
|
169
|
-
isHoverExpanded,
|
|
170
164
|
density,
|
|
171
165
|
menuNav,
|
|
172
166
|
activeIndicator,
|
|
@@ -187,7 +181,6 @@ export function PrivateLayoutProvider({
|
|
|
187
181
|
isMobile,
|
|
188
182
|
isExpanded,
|
|
189
183
|
isCollapsed,
|
|
190
|
-
isHoverExpanded,
|
|
191
184
|
density,
|
|
192
185
|
menuNav,
|
|
193
186
|
activeIndicator,
|
|
@@ -4,11 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
export { useAuthGuard } from './useAuthGuard';
|
|
6
6
|
export { useLayoutVisual } from './useLayoutVisual';
|
|
7
|
-
export {
|
|
8
|
-
useHoverExpand,
|
|
9
|
-
blockSidebarCollapse,
|
|
10
|
-
allowSidebarCollapse,
|
|
11
|
-
} from './useHoverExpand';
|
|
12
7
|
export { useShellVisualState } from './useShellVisualState';
|
|
13
8
|
export { useSidebarKeyboard } from './useSidebarKeyboard';
|
|
14
9
|
export { useSidebarDefaultOpen } from './useSidebarDefaultOpen';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shell Visual State Hook
|
|
3
3
|
*
|
|
4
4
|
* Centralizes ALL visual decisions for the sidebar + content pair:
|
|
5
|
-
* - sidebar state: expanded |
|
|
5
|
+
* - sidebar state: expanded | collapsed-rail
|
|
6
6
|
* - layout variant: boxed (inset) | full-bleed
|
|
7
7
|
* - what to show/hide in each state
|
|
8
8
|
* - CSS modifiers for sidebar root, inner shell, and content inset
|
|
@@ -28,9 +28,7 @@ export interface ShellVisualFlags {
|
|
|
28
28
|
isExpanded: boolean;
|
|
29
29
|
/** Collapsed to icon rail */
|
|
30
30
|
isCollapsed: boolean;
|
|
31
|
-
/**
|
|
32
|
-
isHoverOverlay: boolean;
|
|
33
|
-
/** True only in desktop collapsed rail without hover */
|
|
31
|
+
/** True only in desktop collapsed rail */
|
|
34
32
|
isRail: boolean;
|
|
35
33
|
}
|
|
36
34
|
|
|
@@ -58,12 +56,8 @@ export interface ShellContentFlags {
|
|
|
58
56
|
// ============================================================================
|
|
59
57
|
|
|
60
58
|
export interface ShellChromeFlags {
|
|
61
|
-
/** Sidebar casts a shadow (overlay mode) */
|
|
62
|
-
showShadow: boolean;
|
|
63
59
|
/** Right border/separator on sidebar */
|
|
64
60
|
showBorder: boolean;
|
|
65
|
-
/** Internal right padding inside sidebar (overlay breathing room) */
|
|
66
|
-
needsInternalPadding: boolean;
|
|
67
61
|
/** Content inset should have margin to make room for sidebar */
|
|
68
62
|
contentHasSidebarGap: boolean;
|
|
69
63
|
/** Sidebar width is fixed to icon-rail */
|
|
@@ -101,107 +95,36 @@ export interface ShellVisualState {
|
|
|
101
95
|
export function useShellVisualState(
|
|
102
96
|
layoutVariant?: LayoutVisualConfig['variant'],
|
|
103
97
|
): ShellVisualState {
|
|
104
|
-
const { isMobile, isExpanded, isCollapsed
|
|
105
|
-
usePrivateLayoutContext();
|
|
98
|
+
const { isMobile, isExpanded, isCollapsed } = usePrivateLayoutContext();
|
|
106
99
|
|
|
107
|
-
const isRail = !isMobile && isCollapsed
|
|
108
|
-
const isHoverOverlay = !isMobile && isCollapsed && isHoverExpanded;
|
|
100
|
+
const isRail = !isMobile && isCollapsed;
|
|
109
101
|
const variant = layoutVariant ?? 'boxed';
|
|
110
102
|
|
|
111
103
|
return useMemo(() => {
|
|
112
|
-
|
|
113
|
-
// Content visibility
|
|
114
|
-
// ------------------------------------------------------------------------
|
|
115
|
-
const showLabels = isExpanded || isHoverOverlay;
|
|
104
|
+
const showLabels = isExpanded || isMobile;
|
|
116
105
|
const showGroupLabels = showLabels;
|
|
117
106
|
const showBadgeText = showLabels;
|
|
118
107
|
const showTooltips = isRail;
|
|
119
108
|
const isAccountCompact = isRail;
|
|
120
109
|
const hideSlots = isRail;
|
|
121
110
|
|
|
122
|
-
// ------------------------------------------------------------------------
|
|
123
|
-
// Chrome
|
|
124
|
-
// ------------------------------------------------------------------------
|
|
125
|
-
// Only hover-overlay is an overlay — it needs shadow + border + padding.
|
|
126
|
-
// Persistent expanded is part of the layout flow — no shadow.
|
|
127
|
-
const showShadow = isHoverOverlay;
|
|
128
|
-
const showBorder = isHoverOverlay;
|
|
129
|
-
const needsInternalPadding = isHoverOverlay;
|
|
130
|
-
|
|
131
|
-
// Content gap: only persistent expanded pushes the inset.
|
|
132
|
-
// Hover-overlay is temporary — content must NOT shift.
|
|
133
|
-
// Collapsed rail also leaves a gap (the rail itself is narrow but present).
|
|
134
111
|
const contentHasSidebarGap = isExpanded || isRail;
|
|
135
112
|
const isRailWidth = isRail;
|
|
113
|
+
const showBorder = false;
|
|
136
114
|
|
|
137
|
-
// ------------------------------------------------------------------------
|
|
138
|
-
// Modifiers
|
|
139
|
-
// ------------------------------------------------------------------------
|
|
140
115
|
const sidebarRoot: string[] = [];
|
|
141
116
|
const sidebarInner: string[] = [];
|
|
142
117
|
const sidebarContent: string[] = [];
|
|
143
118
|
|
|
144
|
-
if (isHoverOverlay) {
|
|
145
|
-
sidebarRoot.push('z-50', '!w-[var(--sidebar-width)]', 'min-w-[var(--sidebar-width)]');
|
|
146
|
-
// Allow scroll inside hover-expanded overlay — shadcn hardcodes
|
|
147
|
-
// overflow-hidden on div[data-sidebar=sidebar]; override it here.
|
|
148
|
-
sidebarInner.push('!overflow-auto');
|
|
149
|
-
// Extra right padding on content so text doesn't hug the right edge
|
|
150
|
-
sidebarContent.push('!overflow-auto', 'pr-4');
|
|
151
|
-
|
|
152
|
-
if (showShadow) {
|
|
153
|
-
sidebarInner.push(
|
|
154
|
-
'shadow-[4px_0_24px_-4px_rgba(0,0,0,0.08)]',
|
|
155
|
-
'dark:shadow-[4px_0_24px_-4px_rgba(0,0,0,0.25)]',
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
if (showBorder) {
|
|
159
|
-
sidebarInner.push('border-r', 'border-sidebar-border/30');
|
|
160
|
-
}
|
|
161
|
-
// Hide the shadcn gradient border line on hover overlay
|
|
162
|
-
sidebarInner.push('[&>div[aria-hidden]]:hidden');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Boxed variant: inner sidebar gets rounded corners when persistent expanded
|
|
166
119
|
if (variant === 'boxed' && isExpanded) {
|
|
167
120
|
sidebarInner.push('rounded-sm');
|
|
168
121
|
}
|
|
169
122
|
|
|
170
123
|
return {
|
|
171
|
-
flags: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
isHoverOverlay,
|
|
176
|
-
isRail,
|
|
177
|
-
},
|
|
178
|
-
content: {
|
|
179
|
-
showLabels,
|
|
180
|
-
showGroupLabels,
|
|
181
|
-
showBadgeText,
|
|
182
|
-
showTooltips,
|
|
183
|
-
isAccountCompact,
|
|
184
|
-
hideSlots,
|
|
185
|
-
},
|
|
186
|
-
chrome: {
|
|
187
|
-
showShadow,
|
|
188
|
-
showBorder,
|
|
189
|
-
needsInternalPadding,
|
|
190
|
-
contentHasSidebarGap,
|
|
191
|
-
isRailWidth,
|
|
192
|
-
},
|
|
193
|
-
modifiers: {
|
|
194
|
-
sidebarRoot,
|
|
195
|
-
sidebarInner,
|
|
196
|
-
sidebarContent,
|
|
197
|
-
},
|
|
124
|
+
flags: { isMobile, isExpanded, isCollapsed, isRail },
|
|
125
|
+
content: { showLabels, showGroupLabels, showBadgeText, showTooltips, isAccountCompact, hideSlots },
|
|
126
|
+
chrome: { showBorder, contentHasSidebarGap, isRailWidth },
|
|
127
|
+
modifiers: { sidebarRoot, sidebarInner, sidebarContent },
|
|
198
128
|
};
|
|
199
|
-
}, [
|
|
200
|
-
isMobile,
|
|
201
|
-
isExpanded,
|
|
202
|
-
isCollapsed,
|
|
203
|
-
isHoverOverlay,
|
|
204
|
-
isRail,
|
|
205
|
-
variant,
|
|
206
|
-
]);
|
|
129
|
+
}, [isMobile, isExpanded, isCollapsed, isRail, variant]);
|
|
207
130
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Resolves the default sidebar open/closed state.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. Cookie written by shadcn-sidebar (user's last explicit choice)
|
|
6
|
+
* 2. Viewport width — collapse by default on tablet and below (< 1024px lg)
|
|
7
|
+
* 3. Expanded (large desktop, no cookie)
|
|
4
8
|
*
|
|
5
9
|
* Must run on the client — returns `true` during SSR.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
13
|
+
const COLLAPSE_BELOW_PX = 1024; // Tailwind lg
|
|
9
14
|
|
|
10
15
|
export function useSidebarDefaultOpen(): boolean {
|
|
11
16
|
if (typeof document === 'undefined') return true;
|
|
@@ -14,8 +19,14 @@ export function useSidebarDefaultOpen(): boolean {
|
|
|
14
19
|
.split('; ')
|
|
15
20
|
.find((row) => row.startsWith(`${SIDEBAR_COOKIE_NAME}=`));
|
|
16
21
|
|
|
17
|
-
if (
|
|
22
|
+
if (match) {
|
|
23
|
+
return match.split('=')[1] === 'true';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// No cookie — first visit. Collapse on smaller screens.
|
|
27
|
+
if (typeof window !== 'undefined') {
|
|
28
|
+
return window.innerWidth >= COLLAPSE_BELOW_PX;
|
|
29
|
+
}
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
return value === 'true';
|
|
31
|
+
return true;
|
|
21
32
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
export { PrivateLayout } from './PrivateLayout';
|
|
6
6
|
export { PrivateLayoutProvider, usePrivateLayoutContext } from './context';
|
|
7
|
+
export { SidebarBrandSwitcher } from './components';
|
|
7
8
|
export type {
|
|
8
9
|
PrivateLayoutProps,
|
|
9
10
|
SidebarItem,
|
|
@@ -13,4 +14,6 @@ export type {
|
|
|
13
14
|
SidebarActiveIndicator,
|
|
14
15
|
SidebarGroupLabelStyle,
|
|
15
16
|
SidebarFeaturedConfig,
|
|
17
|
+
SidebarBrandSwitcherConfig,
|
|
18
|
+
SidebarBrandSwitcherItem,
|
|
16
19
|
} from './types';
|
|
@@ -111,11 +111,46 @@ export interface SidebarConfig {
|
|
|
111
111
|
density?: 'comfortable' | 'default' | 'compact';
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Brand Switcher Types
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
export interface SidebarBrandSwitcherItem {
|
|
119
|
+
/** Display name */
|
|
120
|
+
label: string;
|
|
121
|
+
/** Avatar image URL or initials fallback */
|
|
122
|
+
avatar?: string;
|
|
123
|
+
/** Single letter shown when `avatar` is absent */
|
|
124
|
+
monogram?: string;
|
|
125
|
+
/** Navigation target on select */
|
|
126
|
+
href?: string;
|
|
127
|
+
/** Callback on select (alternative to href) */
|
|
128
|
+
onSelect?: () => void;
|
|
129
|
+
/** Mark as currently active */
|
|
130
|
+
active?: boolean;
|
|
131
|
+
/** Small secondary line under label (e.g. plan name, role) */
|
|
132
|
+
description?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface SidebarBrandSwitcherConfig {
|
|
136
|
+
items: SidebarBrandSwitcherItem[];
|
|
137
|
+
/** Label for "add new" action at the bottom of the dropdown. Omit to hide. */
|
|
138
|
+
addLabel?: string;
|
|
139
|
+
/** Called when "add new" is clicked */
|
|
140
|
+
onAdd?: () => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
114
143
|
// ============================================================================
|
|
115
144
|
// Header Config
|
|
116
145
|
// ============================================================================
|
|
117
146
|
|
|
118
147
|
export interface HeaderConfig {
|
|
148
|
+
/**
|
|
149
|
+
* Brand switcher config. When provided, replaces the static brand header
|
|
150
|
+
* with a dropdown for switching workspaces/accounts/projects.
|
|
151
|
+
* Takes priority over `brand`, `title`, `brandIcon`, `brandLetter`.
|
|
152
|
+
*/
|
|
153
|
+
switcher?: SidebarBrandSwitcherConfig;
|
|
119
154
|
/** Custom header brand node (same idea as PublicNavbar `brand`). */
|
|
120
155
|
brand?: ReactNode;
|
|
121
156
|
/** Shown next to the logo when the sidebar is expanded */
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Note: Analytics, PWA, Push, and Error types are defined in their respective modules
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ThemeStyleConfig } from '
|
|
8
|
+
import type { ThemeStyleConfig } from '@djangocfg/ui-core/styles/presets';
|
|
9
9
|
|
|
10
10
|
// Re-export for consumers that only import from `layouts/types`
|
|
11
|
-
export type { ThemeStyleConfig, ThemeCssVarKey, ThemeCssVarMap, ThemeStylePresetId } from '
|
|
11
|
+
export type { ThemeStyleConfig, ThemeCssVarKey, ThemeCssVarMap, ThemeStylePresetId } from '@djangocfg/ui-core/styles/presets';
|
|
12
12
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// Theme Configuration
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo } from 'react';
|
|
4
4
|
|
|
5
|
-
import { buildThemeStyleSheet } from '
|
|
6
|
-
|
|
7
|
-
import type { ThemeStyleConfig } from './themeStyle.types';
|
|
5
|
+
import { buildThemeStyleSheet, type ThemeStyleConfig } from '@djangocfg/ui-core/styles/presets';
|
|
8
6
|
|
|
9
7
|
const STYLE_ELEMENT_ID = 'djangocfg-baseapp-theme-style';
|
|
10
8
|
|
package/src/theme/index.ts
CHANGED
|
@@ -7,10 +7,8 @@ export type {
|
|
|
7
7
|
ThemeCssVarSidebarKey,
|
|
8
8
|
ThemeStyleConfig,
|
|
9
9
|
ThemeStylePresetId,
|
|
10
|
-
} from '
|
|
10
|
+
} from '@djangocfg/ui-core/styles/presets';
|
|
11
11
|
|
|
12
|
-
export { THEME_STYLE_PRESETS, THEME_STYLE_PRESET_ORDER } from '
|
|
13
|
-
|
|
14
|
-
export { buildThemeStyleSheet } from './buildThemeStyleSheet';
|
|
12
|
+
export { THEME_STYLE_PRESETS, THEME_STYLE_PRESET_ORDER, buildThemeStyleSheet } from '@djangocfg/ui-core/styles/presets';
|
|
15
13
|
|
|
16
14
|
export { ThemeStyleBridge, type ThemeStyleBridgeProps } from './ThemeStyleBridge';
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { ThemeCssVarMap, ThemeStyleConfig, ThemeStylePresetId } from './themeStyle.types';
|
|
2
|
-
import { THEME_STYLE_PRESETS } from './themeStylePresets';
|
|
3
|
-
|
|
4
|
-
function mergeLayer(
|
|
5
|
-
base: ThemeCssVarMap | undefined,
|
|
6
|
-
over: ThemeCssVarMap | undefined
|
|
7
|
-
): ThemeCssVarMap {
|
|
8
|
-
return { ...base, ...over };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Tailwind v4 `rounded-*` utilities are backed by `--radius-*` scale variables (xs/sm/md/…),
|
|
13
|
-
* not by our semantic `--radius`.
|
|
14
|
-
*
|
|
15
|
-
* When semantic `radius` is present, emit the derived scale too so the injected stylesheet
|
|
16
|
-
* controls both semantic and Tailwind scale rounding across the UI.
|
|
17
|
-
*/
|
|
18
|
-
function withTailwindRadiusScale(vars: ThemeCssVarMap): Array<[string, string]> {
|
|
19
|
-
const entries = Object.entries(vars);
|
|
20
|
-
const radius = vars.radius;
|
|
21
|
-
if (!radius) return entries;
|
|
22
|
-
|
|
23
|
-
const r = String(radius).trim();
|
|
24
|
-
if (!r) return entries;
|
|
25
|
-
|
|
26
|
-
const scale: Array<[string, string]> = [
|
|
27
|
-
['radius-xs', `calc(${r} - 6px)`],
|
|
28
|
-
['radius-sm', `calc(${r} - 4px)`],
|
|
29
|
-
['radius-md', `calc(${r} - 2px)`],
|
|
30
|
-
['radius-lg', r],
|
|
31
|
-
['radius-xl', `calc(${r} + 4px)`],
|
|
32
|
-
['radius-2xl', `calc(${r} + 8px)`],
|
|
33
|
-
['radius-3xl', `calc(${r} + 12px)`],
|
|
34
|
-
['radius-4xl', `calc(${r} + 16px)`],
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
// Put derived values after semantic `radius` so they always win in the injected block.
|
|
38
|
-
return [...entries, ...scale];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Build a small stylesheet fragment for injection after globals.
|
|
43
|
-
* Order: preset (if not default) → `vars.light` / `vars.dark`.
|
|
44
|
-
*/
|
|
45
|
-
export function buildThemeStyleSheet(style?: ThemeStyleConfig): string {
|
|
46
|
-
if (!style) return '';
|
|
47
|
-
|
|
48
|
-
const presetId: ThemeStylePresetId = style.preset ?? 'default';
|
|
49
|
-
const preset = THEME_STYLE_PRESETS[presetId] ?? THEME_STYLE_PRESETS.default;
|
|
50
|
-
|
|
51
|
-
const light = mergeLayer(preset.light, style.vars?.light);
|
|
52
|
-
const dark = mergeLayer(preset.dark, style.vars?.dark);
|
|
53
|
-
|
|
54
|
-
const blocks: string[] = [];
|
|
55
|
-
|
|
56
|
-
if (Object.keys(light).length > 0) {
|
|
57
|
-
const body = withTailwindRadiusScale(light)
|
|
58
|
-
.map(([k, v]) => ` --${k}: ${v};`)
|
|
59
|
-
.join('\n');
|
|
60
|
-
blocks.push(`:root {\n${body}\n}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (Object.keys(dark).length > 0) {
|
|
64
|
-
const body = withTailwindRadiusScale(dark)
|
|
65
|
-
.map(([k, v]) => ` --${k}: ${v};`)
|
|
66
|
-
.join('\n');
|
|
67
|
-
blocks.push(`.dark {\n${body}\n}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return blocks.join('\n\n');
|
|
71
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed theme token overrides for BaseApp (`theme.style`).
|
|
3
|
-
*
|
|
4
|
-
* Values are raw HSL components as in ui-core CSS, e.g. `192 90% 35%` (no `hsl()` wrapper).
|
|
5
|
-
* **`radius`** accepts any valid CSS length (`0.75rem`, `1rem`, …).
|
|
6
|
-
*
|
|
7
|
-
* ### Parity with the Theme Configurator playground
|
|
8
|
-
*
|
|
9
|
-
* The playground (`apps/playground`) uses **`ThemeData`**: nested objects (`colors.primary`, `radius`, …).
|
|
10
|
-
* This package maps the **same semantics** onto **global CSS variables** (`--primary`, `--radius`, …) that
|
|
11
|
-
* `buildThemeStyleSheet` injects. Playground-only buckets (**`shadows`**, **`typography`**, **`spacing`**, …)
|
|
12
|
-
* are **not** represented here — export full CSS from the configurator when you need those.
|
|
13
|
-
*
|
|
14
|
-
* Rough mapping: `colors.*` → kebab key without `Foreground` → `*-foreground`; `radius` → `radius`;
|
|
15
|
-
* `sidebar.*` → `sidebar-*`.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** Core semantic colors (HSL triplets) */
|
|
19
|
-
export type ThemeCssVarColorKey =
|
|
20
|
-
| 'background'
|
|
21
|
-
| 'foreground'
|
|
22
|
-
| 'card'
|
|
23
|
-
| 'card-foreground'
|
|
24
|
-
| 'popover'
|
|
25
|
-
| 'popover-foreground'
|
|
26
|
-
| 'primary'
|
|
27
|
-
| 'primary-foreground'
|
|
28
|
-
| 'secondary'
|
|
29
|
-
| 'secondary-foreground'
|
|
30
|
-
| 'muted'
|
|
31
|
-
| 'muted-foreground'
|
|
32
|
-
| 'accent'
|
|
33
|
-
| 'accent-foreground'
|
|
34
|
-
| 'destructive'
|
|
35
|
-
| 'destructive-foreground';
|
|
36
|
-
|
|
37
|
-
/** Layout / focus tokens — `radius` is usually a length, rest are HSL or shared with colors */
|
|
38
|
-
export type ThemeCssVarChromeKey = 'border' | 'input' | 'ring' | 'radius';
|
|
39
|
-
|
|
40
|
-
export type ThemeCssVarSidebarKey =
|
|
41
|
-
| 'sidebar-background'
|
|
42
|
-
| 'sidebar-foreground'
|
|
43
|
-
| 'sidebar-primary'
|
|
44
|
-
| 'sidebar-primary-foreground'
|
|
45
|
-
| 'sidebar-accent'
|
|
46
|
-
| 'sidebar-accent-foreground'
|
|
47
|
-
| 'sidebar-border'
|
|
48
|
-
| 'sidebar-ring';
|
|
49
|
-
|
|
50
|
-
export type ThemeCssVarChartKey = 'chart-1' | 'chart-2' | 'chart-3' | 'chart-4' | 'chart-5';
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Keys that match `--${key}` in `ui-core` theme files (`light.css` / `dark.css`).
|
|
54
|
-
* Use `ThemeCssVarMap` for partial overrides on top of a preset.
|
|
55
|
-
*/
|
|
56
|
-
export type ThemeCssVarKey =
|
|
57
|
-
| ThemeCssVarColorKey
|
|
58
|
-
| ThemeCssVarChromeKey
|
|
59
|
-
| ThemeCssVarSidebarKey
|
|
60
|
-
| ThemeCssVarChartKey;
|
|
61
|
-
|
|
62
|
-
/** Partial map of semantic variables for one color mode */
|
|
63
|
-
export type ThemeCssVarMap = Partial<Record<ThemeCssVarKey, string>>;
|
|
64
|
-
|
|
65
|
-
/** Built-in bundles in `THEME_STYLE_PRESETS` — see README preset table. */
|
|
66
|
-
export type ThemeStylePresetId =
|
|
67
|
-
| 'default'
|
|
68
|
-
| 'django-cfg'
|
|
69
|
-
| 'ios'
|
|
70
|
-
| 'soft'
|
|
71
|
-
| 'dense'
|
|
72
|
-
| 'high-contrast';
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Optional style layer: named preset + per-mode overrides.
|
|
76
|
-
* Merged as: globals (CSS imports) → **preset** → **`vars.light` / `vars.dark`** (later wins).
|
|
77
|
-
*/
|
|
78
|
-
export interface ThemeStyleConfig {
|
|
79
|
-
/**
|
|
80
|
-
* Built-in token bundle. Use `'default'` or omit to rely only on imported CSS + `vars`.
|
|
81
|
-
* @default 'default'
|
|
82
|
-
*/
|
|
83
|
-
preset?: ThemeStylePresetId;
|
|
84
|
-
/** Fine-grained overrides on top of globals and preset */
|
|
85
|
-
vars?: {
|
|
86
|
-
light?: ThemeCssVarMap;
|
|
87
|
-
dark?: ThemeCssVarMap;
|
|
88
|
-
};
|
|
89
|
-
}
|