@askrjs/themes 0.0.8 → 0.0.9
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/dist/components/_internal/box-layout.js +1 -1
- package/dist/components/_internal/jsx.js.map +1 -1
- package/dist/components/_internal/layout.js +2 -7
- package/dist/components/_internal/layout.js.map +1 -1
- package/dist/components/_internal/style.js +25 -1
- package/dist/components/_internal/style.js.map +1 -1
- package/dist/components/aspect-ratio/aspect-ratio.js +1 -1
- package/dist/components/navbar/navbar.js +23 -12
- package/dist/components/navbar/navbar.js.map +1 -1
- package/dist/components/shell/shell-responsive.js +50 -27
- package/dist/components/shell/shell-responsive.js.map +1 -1
- package/dist/components/sidebar/sidebar.js +38 -24
- package/dist/components/sidebar/sidebar.js.map +1 -1
- package/dist/components/spacer/spacer.js +1 -1
- package/dist/components/theme/theme.js +47 -10
- package/dist/components/theme/theme.js.map +1 -1
- package/dist/themes/default/index.css +19 -3
- package/package.json +5 -5
- package/src/components/_internal/jsx.ts +18 -6
- package/src/components/_internal/layout.ts +3 -12
- package/src/components/_internal/style.ts +42 -4
- package/src/components/navbar/navbar.tsx +32 -17
- package/src/components/shell/shell-responsive.tsx +82 -66
- package/src/components/sidebar/sidebar.tsx +52 -34
- package/src/components/theme/theme.tsx +80 -12
- package/src/themes/default/styles/display/avatar.css +7 -1
- package/src/themes/default/styles/display/badge.css +2 -1
- package/src/themes/default/styles/display/table.css +2 -0
- package/src/themes/default/styles/forms/select.css +6 -0
- package/src/themes/default/styles/shell/navbar.css +2 -1
- package/templates/theme/styles/display/avatar.css +7 -1
- package/templates/theme/styles/display/badge.css +2 -1
- package/templates/theme/styles/display/table.css +2 -0
- package/templates/theme/styles/forms/select.css +6 -0
- package/templates/theme/styles/shell/navbar.css +2 -1
|
@@ -46,28 +46,92 @@ export function isShellPanelChild(child: unknown, panelComponent: unknown): chil
|
|
|
46
46
|
return isJsxElement(child) && child.type === panelComponent;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function renderShellChildKey(child: unknown, index: number, keyPrefix: string): string {
|
|
50
|
+
return isJsxElement(child) && child.key != null ? String(child.key) : `${keyPrefix}-${index}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function keyMissingShellChildren(children: unknown[], keyPrefix: string): unknown[] {
|
|
54
|
+
let keyedChildren: unknown[] | undefined;
|
|
55
|
+
|
|
56
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
57
|
+
const child = children[index];
|
|
58
|
+
|
|
59
|
+
if (isJsxElement(child) && child.key == null) {
|
|
60
|
+
keyedChildren ??= children.slice(0, index);
|
|
61
|
+
keyedChildren.push({
|
|
62
|
+
...child,
|
|
63
|
+
key: `${keyPrefix}-${index}`,
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
keyedChildren?.push(child);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return keyedChildren ?? children;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hasPanelProps(
|
|
75
|
+
props: Record<string, unknown> | undefined,
|
|
76
|
+
panelProps: ShellResponsivePanelProps,
|
|
77
|
+
): boolean {
|
|
78
|
+
return (
|
|
79
|
+
props?.active === panelProps.active &&
|
|
80
|
+
props?.collapseLabel === panelProps.collapseLabel &&
|
|
81
|
+
props?.onClose === panelProps.onClose &&
|
|
82
|
+
props?.open === panelProps.open &&
|
|
83
|
+
props?.panelId === panelProps.panelId
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function applyShellPanelProps(
|
|
88
|
+
children: unknown[],
|
|
89
|
+
keyPrefix: string,
|
|
90
|
+
panelProps: ShellResponsivePanelProps,
|
|
91
|
+
): unknown[] {
|
|
92
|
+
let panelChildren: unknown[] | undefined;
|
|
93
|
+
|
|
94
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
95
|
+
const child = children[index];
|
|
96
|
+
|
|
97
|
+
if (!isJsxElement(child)) {
|
|
98
|
+
panelChildren?.push(child);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const props = child.props as Record<string, unknown> | undefined;
|
|
103
|
+
|
|
104
|
+
if (hasPanelProps(props, panelProps)) {
|
|
105
|
+
panelChildren?.push(child);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
panelChildren ??= children.slice(0, index);
|
|
110
|
+
panelChildren.push({
|
|
111
|
+
...child,
|
|
112
|
+
key: child.key != null ? child.key : `${keyPrefix}-${index}`,
|
|
113
|
+
props: {
|
|
114
|
+
...props,
|
|
115
|
+
active: panelProps.active,
|
|
116
|
+
collapseLabel: panelProps.collapseLabel,
|
|
117
|
+
onClose: panelProps.onClose,
|
|
118
|
+
open: panelProps.open,
|
|
119
|
+
panelId: panelProps.panelId,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return panelChildren ?? children;
|
|
125
|
+
}
|
|
126
|
+
|
|
49
127
|
export function renderKeyedShellChildren(children: unknown, keyPrefix: string): JSX.Element {
|
|
50
128
|
const childList = toChildArray(children);
|
|
51
|
-
const
|
|
52
|
-
const keyedChildren = needsKeying
|
|
53
|
-
? childList.map((child, index) => {
|
|
54
|
-
if (!isJsxElement(child) || child.key != null) {
|
|
55
|
-
return child;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
...child,
|
|
60
|
-
key: `${keyPrefix}-${index}`,
|
|
61
|
-
};
|
|
62
|
-
})
|
|
63
|
-
: childList;
|
|
129
|
+
const keyedChildren = keyMissingShellChildren(childList, keyPrefix);
|
|
64
130
|
|
|
65
131
|
return (
|
|
66
132
|
<For
|
|
67
133
|
each={() => keyedChildren}
|
|
68
|
-
by={(child, index) =>
|
|
69
|
-
isJsxElement(child) && child.key != null ? String(child.key) : `${keyPrefix}-${index}`
|
|
70
|
-
}
|
|
134
|
+
by={(child, index) => renderShellChildKey(child, index, keyPrefix)}
|
|
71
135
|
>
|
|
72
136
|
{(child) => child as never}
|
|
73
137
|
</For>
|
|
@@ -80,60 +144,12 @@ export function renderKeyedShellPanelChildren(
|
|
|
80
144
|
panelProps: ShellResponsivePanelProps,
|
|
81
145
|
): JSX.Element {
|
|
82
146
|
const childList = toChildArray(children);
|
|
83
|
-
const
|
|
84
|
-
if (!isJsxElement(child)) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const props = child.props as Record<string, unknown> | undefined;
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
props?.active !== panelProps.active ||
|
|
92
|
-
props?.collapseLabel !== panelProps.collapseLabel ||
|
|
93
|
-
props?.onClose !== panelProps.onClose ||
|
|
94
|
-
props?.open !== panelProps.open ||
|
|
95
|
-
props?.panelId !== panelProps.panelId
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
const keyedChildren = needsPanelProps
|
|
99
|
-
? childList.map((child, index) => {
|
|
100
|
-
if (!isJsxElement(child)) {
|
|
101
|
-
return child;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const props = child.props as Record<string, unknown> | undefined;
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
props?.active === panelProps.active &&
|
|
108
|
-
props?.collapseLabel === panelProps.collapseLabel &&
|
|
109
|
-
props?.onClose === panelProps.onClose &&
|
|
110
|
-
props?.open === panelProps.open &&
|
|
111
|
-
props?.panelId === panelProps.panelId
|
|
112
|
-
) {
|
|
113
|
-
return child;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
...child,
|
|
118
|
-
key: child.key != null ? child.key : `${keyPrefix}-${index}`,
|
|
119
|
-
props: {
|
|
120
|
-
...props,
|
|
121
|
-
active: panelProps.active,
|
|
122
|
-
collapseLabel: panelProps.collapseLabel,
|
|
123
|
-
onClose: panelProps.onClose,
|
|
124
|
-
open: panelProps.open,
|
|
125
|
-
panelId: panelProps.panelId,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
})
|
|
129
|
-
: childList;
|
|
147
|
+
const keyedChildren = applyShellPanelProps(childList, keyPrefix, panelProps);
|
|
130
148
|
|
|
131
149
|
return (
|
|
132
150
|
<For
|
|
133
151
|
each={() => keyedChildren}
|
|
134
|
-
by={(child, index) =>
|
|
135
|
-
isJsxElement(child) && child.key != null ? String(child.key) : `${keyPrefix}-${index}`
|
|
136
|
-
}
|
|
152
|
+
by={(child, index) => renderShellChildKey(child, index, keyPrefix)}
|
|
137
153
|
>
|
|
138
154
|
{(child) => child as never}
|
|
139
155
|
</For>
|
|
@@ -19,7 +19,10 @@ import type { CollapseIconPlacement } from "../shell/shell-nav.types";
|
|
|
19
19
|
|
|
20
20
|
type SidebarChildren = {
|
|
21
21
|
brand?: unknown;
|
|
22
|
-
|
|
22
|
+
drawerItems: unknown[];
|
|
23
|
+
drawerSourceItems: unknown[];
|
|
24
|
+
explicitPanelItems: unknown[];
|
|
25
|
+
toggle?: SidebarToggleConfig;
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
type SidebarToggleConfig = {
|
|
@@ -27,38 +30,49 @@ type SidebarToggleConfig = {
|
|
|
27
30
|
expandedIcon?: unknown;
|
|
28
31
|
};
|
|
29
32
|
|
|
33
|
+
function isSidebarToggleChild(child: unknown): boolean {
|
|
34
|
+
return isJsxElement(child) && child.type === SidebarToggle;
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
function splitSidebarChildren(children: unknown[]): SidebarChildren {
|
|
31
|
-
const
|
|
38
|
+
const explicitPanelItems: unknown[] = [];
|
|
39
|
+
const drawerSourceItems: unknown[] = [];
|
|
40
|
+
let drawerItems: unknown[] | undefined;
|
|
41
|
+
let brand: unknown;
|
|
42
|
+
let hasBrand = false;
|
|
43
|
+
let hasToggle = false;
|
|
44
|
+
let toggle: SidebarToggleConfig | undefined;
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
for (const child of children) {
|
|
47
|
+
if (isShellPanelChild(child, SidebarPanel)) {
|
|
48
|
+
explicitPanelItems.push(child);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
52
|
+
if (!hasToggle && isSidebarToggleChild(child)) {
|
|
53
|
+
hasToggle = true;
|
|
54
|
+
toggle = (child as { props?: SidebarToggleConfig } | undefined)?.props;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
return isJsxElement(child) && child.type === SidebarToggle;
|
|
45
|
-
}
|
|
58
|
+
drawerSourceItems.push(child);
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
if (!hasBrand && isJsxElement(child) && child.type === NavBrand) {
|
|
61
|
+
brand = child;
|
|
62
|
+
hasBrand = true;
|
|
63
|
+
drawerItems ??= drawerSourceItems.slice(0, drawerSourceItems.length - 1);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
return { items: children };
|
|
67
|
+
drawerItems?.push(child);
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
const toggleChild = children[toggleIndex] as { props?: SidebarToggleConfig } | undefined;
|
|
58
|
-
|
|
59
70
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
brand: hasBrand ? brand : undefined,
|
|
72
|
+
drawerItems: drawerItems ?? drawerSourceItems,
|
|
73
|
+
drawerSourceItems,
|
|
74
|
+
explicitPanelItems,
|
|
75
|
+
toggle,
|
|
62
76
|
};
|
|
63
77
|
}
|
|
64
78
|
|
|
@@ -152,8 +166,7 @@ export function Sidebar(props: SidebarProps): JSX.Element {
|
|
|
152
166
|
const effectiveCollapseLabel =
|
|
153
167
|
collapseLabel ?? (typeof ariaLabel === "string" ? ariaLabel : undefined) ?? "Menu";
|
|
154
168
|
const childItems = toChildArray(children);
|
|
155
|
-
const
|
|
156
|
-
const contentItems = childItems.filter((child) => !isShellPanelChild(child, SidebarPanel));
|
|
169
|
+
const sidebarChildren = splitSidebarChildren(childItems);
|
|
157
170
|
const isResponsive = breakpoint !== undefined;
|
|
158
171
|
const responsiveCollapsedState = state(isResponsive ? isSidebarCollapsed(breakpoint) : false);
|
|
159
172
|
const drawerOpenState = state(false);
|
|
@@ -202,10 +215,11 @@ export function Sidebar(props: SidebarProps): JSX.Element {
|
|
|
202
215
|
|
|
203
216
|
setRailCollapsed(!isRailCollapsed);
|
|
204
217
|
};
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
const desktopChildren = renderKeyedShellChildren(
|
|
219
|
+
sidebarChildren.drawerSourceItems,
|
|
220
|
+
"sidebar-desktop",
|
|
221
|
+
);
|
|
222
|
+
const drawerChildren = renderKeyedShellChildren(sidebarChildren.drawerItems, "sidebar-drawer");
|
|
209
223
|
|
|
210
224
|
const contextValue = {
|
|
211
225
|
active: () => isResponsive,
|
|
@@ -227,12 +241,16 @@ export function Sidebar(props: SidebarProps): JSX.Element {
|
|
|
227
241
|
panelId,
|
|
228
242
|
};
|
|
229
243
|
const panels =
|
|
230
|
-
explicitPanelItems.length > 0 ? (
|
|
231
|
-
renderKeyedShellPanelChildren(
|
|
244
|
+
sidebarChildren.explicitPanelItems.length > 0 ? (
|
|
245
|
+
renderKeyedShellPanelChildren(
|
|
246
|
+
sidebarChildren.explicitPanelItems,
|
|
247
|
+
"sidebar-panel",
|
|
248
|
+
panelContext,
|
|
249
|
+
)
|
|
232
250
|
) : isResponsive ? (
|
|
233
251
|
<SidebarPanel
|
|
234
252
|
active={isResponsive}
|
|
235
|
-
brand={
|
|
253
|
+
brand={sidebarChildren.brand}
|
|
236
254
|
collapseLabel={effectiveCollapseLabel}
|
|
237
255
|
onClose={closeDrawer}
|
|
238
256
|
onClick={closeDrawerOnNavActivation}
|
|
@@ -305,7 +323,7 @@ export function Sidebar(props: SidebarProps): JSX.Element {
|
|
|
305
323
|
{renderSidebarRailContents(
|
|
306
324
|
effectiveCollapseLabel,
|
|
307
325
|
isRailCollapsed,
|
|
308
|
-
|
|
326
|
+
sidebarChildren.toggle,
|
|
309
327
|
)}
|
|
310
328
|
</button>
|
|
311
329
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineContext, readContext, state } from "@askrjs/askr";
|
|
1
|
+
import { defineContext, getSignal, readContext, state } from "@askrjs/askr";
|
|
2
2
|
import type { JSXElement } from "@askrjs/askr/foundations/structures";
|
|
3
3
|
import { resource } from "@askrjs/askr/resources";
|
|
4
4
|
import { Button } from "@askrjs/ui";
|
|
@@ -65,6 +65,10 @@ export const CAT_THEME_OPTIONS: readonly ThemeOption[] = [
|
|
|
65
65
|
];
|
|
66
66
|
|
|
67
67
|
const DEFAULT_STORAGE_KEY = "askr-theme";
|
|
68
|
+
// Passive mount sync should not overwrite a newer user/provider-initiated change.
|
|
69
|
+
let rootThemeRevision = 0;
|
|
70
|
+
let explicitRootThemeOwner: symbol | undefined;
|
|
71
|
+
const registeredProviderSignals = new WeakSet<AbortSignal>();
|
|
68
72
|
|
|
69
73
|
const ThemeContext = defineContext<ThemeContextValue>({
|
|
70
74
|
theme: () => "system",
|
|
@@ -85,12 +89,29 @@ export function ThemeProvider(props: ThemeProviderProps): JSX.Element {
|
|
|
85
89
|
storageKey = DEFAULT_STORAGE_KEY,
|
|
86
90
|
} = props;
|
|
87
91
|
|
|
92
|
+
const providerId = state<symbol>(Symbol("ThemeProvider"))();
|
|
93
|
+
const providerSignal = getSignal();
|
|
88
94
|
const themeState = state<ThemeName>(readStoredTheme(storageKey) ?? defaultTheme);
|
|
89
95
|
const currentTheme = themeState();
|
|
90
96
|
|
|
97
|
+
if (!registeredProviderSignals.has(providerSignal)) {
|
|
98
|
+
registeredProviderSignals.add(providerSignal);
|
|
99
|
+
providerSignal.addEventListener(
|
|
100
|
+
"abort",
|
|
101
|
+
() => {
|
|
102
|
+
if (explicitRootThemeOwner === providerId) {
|
|
103
|
+
explicitRootThemeOwner = undefined;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{ once: true },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
const setTheme = (nextTheme: ThemeName) => {
|
|
92
111
|
themeState.set(nextTheme);
|
|
93
112
|
writeStoredTheme(storageKey, nextTheme);
|
|
113
|
+
explicitRootThemeOwner = providerId;
|
|
114
|
+
rootThemeRevision += 1;
|
|
94
115
|
syncThemeRoot(nextTheme);
|
|
95
116
|
};
|
|
96
117
|
|
|
@@ -103,14 +124,15 @@ export function ThemeProvider(props: ThemeProviderProps): JSX.Element {
|
|
|
103
124
|
|
|
104
125
|
return (
|
|
105
126
|
<ThemeContext.Scope value={value}>
|
|
106
|
-
<ThemeRootSync theme={currentTheme} />
|
|
127
|
+
<ThemeRootSync providerId={providerId} theme={currentTheme} />
|
|
107
128
|
<div data-slot="theme-provider">{children}</div>
|
|
108
129
|
</ThemeContext.Scope>
|
|
109
130
|
);
|
|
110
131
|
}
|
|
111
132
|
|
|
112
|
-
function ThemeRootSync(props: { theme: ThemeName }): JSX.Element | null {
|
|
113
|
-
const { theme } = props;
|
|
133
|
+
function ThemeRootSync(props: { providerId: symbol; theme: ThemeName }): JSX.Element | null {
|
|
134
|
+
const { providerId, theme } = props;
|
|
135
|
+
const expectedRootThemeRevision = rootThemeRevision;
|
|
114
136
|
|
|
115
137
|
resource(
|
|
116
138
|
({ signal }: { signal: AbortSignal }) => {
|
|
@@ -121,7 +143,11 @@ function ThemeRootSync(props: { theme: ThemeName }): JSX.Element | null {
|
|
|
121
143
|
|
|
122
144
|
const timeoutId = window.setTimeout(() => {
|
|
123
145
|
if (!signal.aborted) {
|
|
124
|
-
syncThemeRoot(theme
|
|
146
|
+
syncThemeRoot(theme, {
|
|
147
|
+
expectedRevision: expectedRootThemeRevision,
|
|
148
|
+
passive: true,
|
|
149
|
+
providerId,
|
|
150
|
+
});
|
|
125
151
|
}
|
|
126
152
|
}, 0);
|
|
127
153
|
|
|
@@ -144,20 +170,23 @@ function ThemeRootSync(props: { theme: ThemeName }): JSX.Element | null {
|
|
|
144
170
|
export function ThemePicker(props: ThemePickerProps): JSX.Element {
|
|
145
171
|
const theme = useTheme();
|
|
146
172
|
const { themes = theme.themes, label = "Theme", ...rest } = props;
|
|
173
|
+
const currentTheme = theme.theme();
|
|
147
174
|
|
|
148
175
|
return (
|
|
149
176
|
<select
|
|
150
177
|
{...rest}
|
|
151
178
|
aria-label={rest["aria-label"] ?? label}
|
|
152
179
|
data-slot="theme-picker"
|
|
153
|
-
value={
|
|
180
|
+
value={currentTheme}
|
|
154
181
|
onChange={(event: Event) => {
|
|
155
|
-
const target = event
|
|
156
|
-
|
|
182
|
+
const target = getThemePickerTarget(event);
|
|
183
|
+
if (target) {
|
|
184
|
+
theme.setTheme(target.value as ThemeName);
|
|
185
|
+
}
|
|
157
186
|
}}
|
|
158
187
|
>
|
|
159
188
|
{themes.map((option) => (
|
|
160
|
-
<option key={option.value} value={option.value}>
|
|
189
|
+
<option key={option.value} value={option.value} selected={option.value === currentTheme}>
|
|
161
190
|
{option.label}
|
|
162
191
|
</option>
|
|
163
192
|
))}
|
|
@@ -165,6 +194,23 @@ export function ThemePicker(props: ThemePickerProps): JSX.Element {
|
|
|
165
194
|
);
|
|
166
195
|
}
|
|
167
196
|
|
|
197
|
+
function getThemePickerTarget(event: Event): HTMLSelectElement | null {
|
|
198
|
+
if (typeof HTMLSelectElement === "undefined") {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const path = typeof event.composedPath === "function" ? event.composedPath() : [];
|
|
203
|
+
const candidates = [event.target, event.currentTarget, ...path];
|
|
204
|
+
|
|
205
|
+
for (const candidate of candidates) {
|
|
206
|
+
if (candidate instanceof HTMLSelectElement) {
|
|
207
|
+
return candidate;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
168
214
|
export function ThemeToggle(props: ThemeToggleProps): JSX.Element {
|
|
169
215
|
const theme = useTheme();
|
|
170
216
|
const {
|
|
@@ -199,7 +245,9 @@ export function ThemeToggle(props: ThemeToggleProps): JSX.Element {
|
|
|
199
245
|
data-next-theme={nextTheme}
|
|
200
246
|
onPress={(event) => {
|
|
201
247
|
onPress?.(event);
|
|
202
|
-
if (!event.defaultPrevented
|
|
248
|
+
if (!event.defaultPrevented && !Object.is(nextTheme, currentTheme)) {
|
|
249
|
+
theme.setTheme(nextTheme);
|
|
250
|
+
}
|
|
203
251
|
}}
|
|
204
252
|
>
|
|
205
253
|
<span data-slot="theme-toggle-content">{content}</span>
|
|
@@ -255,13 +303,32 @@ function cloneThemeToggleIcon(icon: unknown): unknown {
|
|
|
255
303
|
};
|
|
256
304
|
}
|
|
257
305
|
|
|
258
|
-
function syncThemeRoot(
|
|
306
|
+
function syncThemeRoot(
|
|
307
|
+
themeChoice: ThemeName | null | undefined,
|
|
308
|
+
options?: { expectedRevision?: number; passive?: boolean; providerId?: symbol },
|
|
309
|
+
): void {
|
|
259
310
|
if (typeof document === "undefined") {
|
|
260
311
|
return;
|
|
261
312
|
}
|
|
262
313
|
|
|
263
314
|
const html = document.documentElement;
|
|
264
315
|
|
|
316
|
+
if (
|
|
317
|
+
options?.passive &&
|
|
318
|
+
options.expectedRevision !== undefined &&
|
|
319
|
+
options.expectedRevision !== rootThemeRevision
|
|
320
|
+
) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (
|
|
325
|
+
options?.passive &&
|
|
326
|
+
explicitRootThemeOwner !== undefined &&
|
|
327
|
+
explicitRootThemeOwner !== options.providerId
|
|
328
|
+
) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
265
332
|
if (themeChoice == null) {
|
|
266
333
|
html.removeAttribute("data-theme");
|
|
267
334
|
html.removeAttribute("data-theme-choice");
|
|
@@ -280,7 +347,8 @@ function syncThemeRoot(themeChoice: ThemeName | null | undefined): void {
|
|
|
280
347
|
function readStoredTheme(storageKey: string): ThemeName | undefined {
|
|
281
348
|
if (typeof window === "undefined") return undefined;
|
|
282
349
|
try {
|
|
283
|
-
|
|
350
|
+
const storedTheme = window.localStorage.getItem(storageKey);
|
|
351
|
+
return storedTheme ? (storedTheme as ThemeName) : undefined;
|
|
284
352
|
} catch {
|
|
285
353
|
return undefined;
|
|
286
354
|
}
|
|
@@ -19,12 +19,18 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
:where(.avatar-fallback, [data-slot="avatar-fallback"]) {
|
|
22
|
+
box-sizing: border-box;
|
|
22
23
|
display: inline-grid;
|
|
23
24
|
place-items: center;
|
|
24
25
|
inline-size: 100%;
|
|
25
26
|
block-size: 100%;
|
|
27
|
+
min-inline-size: 0;
|
|
28
|
+
max-inline-size: 100%;
|
|
26
29
|
font-size: var(--ak-font-size-sm);
|
|
27
30
|
font-weight: var(--ak-font-weight-semibold);
|
|
28
31
|
line-height: 1;
|
|
29
|
-
overflow
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
overflow-wrap: normal;
|
|
34
|
+
text-overflow: ellipsis;
|
|
35
|
+
white-space: nowrap;
|
|
30
36
|
}
|
|
@@ -125,16 +125,22 @@
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
:where(.select-item, [data-slot="select-item"]) {
|
|
128
|
+
appearance: none;
|
|
128
129
|
display: flex;
|
|
129
130
|
align-items: center;
|
|
131
|
+
inline-size: 100%;
|
|
130
132
|
min-height: var(--ak-density-control-height-md);
|
|
131
133
|
padding: 0 var(--ak-density-control-padding-x-md);
|
|
134
|
+
border: 0;
|
|
132
135
|
border-radius: var(--ak-radius-md);
|
|
136
|
+
background: transparent;
|
|
133
137
|
color: var(--ak-color-text);
|
|
134
138
|
cursor: pointer;
|
|
139
|
+
font-family: inherit;
|
|
135
140
|
font-size: var(--ak-font-size-sm);
|
|
136
141
|
font-weight: var(--ak-font-weight-medium);
|
|
137
142
|
overflow-wrap: anywhere;
|
|
143
|
+
text-align: start;
|
|
138
144
|
transition:
|
|
139
145
|
background var(--ak-duration-fast) var(--ak-ease-standard),
|
|
140
146
|
color var(--ak-duration-fast) var(--ak-ease-standard);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
:where(.navbar, [data-slot="navbar"]) {
|
|
3
3
|
position: relative;
|
|
4
|
+
container-type: inline-size;
|
|
4
5
|
display: flex;
|
|
5
6
|
flex-direction: column;
|
|
6
7
|
align-items: stretch;
|
|
@@ -157,7 +158,7 @@
|
|
|
157
158
|
min-inline-size: 0;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
@
|
|
161
|
+
@container (min-width: 48rem) {
|
|
161
162
|
:where(.navbar-shell, [data-slot="navbar-shell"]):has(
|
|
162
163
|
> :where(.navbar-group[data-align="center"], [data-slot="navbar-group"][data-align="center"])
|
|
163
164
|
) {
|
|
@@ -19,12 +19,18 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
:where(.avatar-fallback, [data-slot="avatar-fallback"]) {
|
|
22
|
+
box-sizing: border-box;
|
|
22
23
|
display: inline-grid;
|
|
23
24
|
place-items: center;
|
|
24
25
|
inline-size: 100%;
|
|
25
26
|
block-size: 100%;
|
|
27
|
+
min-inline-size: 0;
|
|
28
|
+
max-inline-size: 100%;
|
|
26
29
|
font-size: var(--ak-font-size-sm);
|
|
27
30
|
font-weight: var(--ak-font-weight-semibold);
|
|
28
31
|
line-height: 1;
|
|
29
|
-
overflow
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
overflow-wrap: normal;
|
|
34
|
+
text-overflow: ellipsis;
|
|
35
|
+
white-space: nowrap;
|
|
30
36
|
}
|
|
@@ -125,16 +125,22 @@
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
:where(.select-item, [data-slot="select-item"]) {
|
|
128
|
+
appearance: none;
|
|
128
129
|
display: flex;
|
|
129
130
|
align-items: center;
|
|
131
|
+
inline-size: 100%;
|
|
130
132
|
min-height: var(--ak-density-control-height-md);
|
|
131
133
|
padding: 0 var(--ak-density-control-padding-x-md);
|
|
134
|
+
border: 0;
|
|
132
135
|
border-radius: var(--ak-radius-md);
|
|
136
|
+
background: transparent;
|
|
133
137
|
color: var(--ak-color-text);
|
|
134
138
|
cursor: pointer;
|
|
139
|
+
font-family: inherit;
|
|
135
140
|
font-size: var(--ak-font-size-sm);
|
|
136
141
|
font-weight: var(--ak-font-weight-medium);
|
|
137
142
|
overflow-wrap: anywhere;
|
|
143
|
+
text-align: start;
|
|
138
144
|
transition:
|
|
139
145
|
background var(--ak-duration-fast) var(--ak-ease-standard),
|
|
140
146
|
color var(--ak-duration-fast) var(--ak-ease-standard);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
:where(.navbar, [data-slot="navbar"]) {
|
|
3
3
|
position: relative;
|
|
4
|
+
container-type: inline-size;
|
|
4
5
|
display: flex;
|
|
5
6
|
flex-direction: column;
|
|
6
7
|
align-items: stretch;
|
|
@@ -157,7 +158,7 @@
|
|
|
157
158
|
min-inline-size: 0;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
@
|
|
161
|
+
@container (min-width: 48rem) {
|
|
161
162
|
:where(.navbar-shell, [data-slot="navbar-shell"]):has(
|
|
162
163
|
> :where(.navbar-group[data-align="center"], [data-slot="navbar-group"][data-align="center"])
|
|
163
164
|
) {
|