@happyvertical/smrt-svelte 0.51.39 → 0.53.0
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/AGENTS.md +3 -0
- package/README.md +214 -1
- package/agents/components.md +8 -1
- package/agents/workspace.md +24 -0
- package/dist/Provider.svelte +19 -15
- package/dist/Provider.svelte.d.ts +2 -2
- package/dist/Provider.svelte.d.ts.map +1 -1
- package/dist/__tests__/provider-mode-harness.svelte +14 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts +11 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +1 -0
- package/dist/__tests__/provider-mode-probe.svelte +13 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts +8 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +2 -1
- package/dist/components/forms/FileUpload.svelte +120 -16
- package/dist/components/forms/FileUpload.svelte.d.ts +7 -1
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +118 -8
- package/dist/components/forms/Form.svelte.d.ts +18 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormScope.svelte +202 -0
- package/dist/components/forms/FormScope.svelte.d.ts +22 -0
- package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/FileUpload.native-form.test.js +249 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
- package/dist/components/forms/__tests__/FormScope.test.js +39 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/workspace/README.md +11 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +597 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +257 -1
- package/dist/components/workspace/__tests__/TenantNav.test.js +115 -0
- package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +6 -0
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +1099 -59
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +44 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +116 -13
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -2
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +16 -2
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +2 -0
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +84 -2
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +57 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +156 -6
- package/dist/components/workspace/admin-shell/types.d.ts +115 -1
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +11 -4
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +12 -3
- package/dist/i18n/strings.forms.d.ts +3 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -1
- package/dist/i18n/strings.forms.js +3 -0
- package/dist/i18n/strings.workspace.d.ts +14 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/provider-mode.test.js +86 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -1
- package/dist/state/app-state.svelte.js +8 -8
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
- package/dist/web/__tests__/webmcp-ui.test.js +18 -0
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +4 -1
- package/dist/web/link-surface.svelte.d.ts +14 -0
- package/dist/web/link-surface.svelte.d.ts.map +1 -0
- package/dist/web/link-surface.svelte.js +49 -0
- package/dist/web/list-surface.svelte.d.ts +80 -0
- package/dist/web/list-surface.svelte.d.ts.map +1 -0
- package/dist/web/list-surface.svelte.js +309 -0
- package/dist/web/step-surface.svelte.d.ts +17 -0
- package/dist/web/step-surface.svelte.d.ts.map +1 -0
- package/dist/web/step-surface.svelte.js +56 -0
- package/dist/web/webmcp-ui-context.d.ts +7 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -1
- package/dist/web/webmcp-ui-context.js +10 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +16 -3
- package/package.json +17 -17
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
import type { ShellState as ModuleShellState } from './state.svelte.js';
|
|
3
3
|
import type { PanelEdge as ModulePanelEdge } from './types.js';
|
|
4
4
|
|
|
5
|
+
function expandedSize(shell: ModuleShellState, edge: ModulePanelEdge): string {
|
|
6
|
+
const resized = shell.panelSize(edge);
|
|
7
|
+
return resized !== null
|
|
8
|
+
? `${resized}px`
|
|
9
|
+
: shell.config.panels[edge].expandedSize;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
function trackFor(shell: ModuleShellState, edge: ModulePanelEdge): string {
|
|
6
13
|
const state = shell.panels[edge];
|
|
7
14
|
const config = shell.config.panels[edge];
|
|
8
15
|
if (state === 'hidden') return '0rem';
|
|
9
16
|
if (state === 'collapsed') return config.collapsedSize;
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function expandedSize(shell: ModuleShellState, edge: ModulePanelEdge): string {
|
|
15
|
-
return shell.config.panels[edge].expandedSize;
|
|
17
|
+
if (shell.presentationFor(edge) === 'overlay') return config.collapsedSize;
|
|
18
|
+
return expandedSize(shell, edge);
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
function collapsedSize(shell: ModuleShellState, edge: ModulePanelEdge): string {
|
|
@@ -37,9 +40,10 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
37
40
|
</script>
|
|
38
41
|
|
|
39
42
|
<script lang="ts">
|
|
43
|
+
import { swipeDismiss } from '@happyvertical/smrt-ui/feedback';
|
|
40
44
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
41
45
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
42
|
-
import { onMount, untrack } from 'svelte';
|
|
46
|
+
import { onMount, tick, untrack } from 'svelte';
|
|
43
47
|
import type { Snippet } from 'svelte';
|
|
44
48
|
import { M } from '../../../i18n/strings.workspace.js';
|
|
45
49
|
import ActivityBadge from './ActivityBadge.svelte';
|
|
@@ -48,12 +52,22 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
48
52
|
formatHotkeyBinding,
|
|
49
53
|
shellActionFromKeyboardEvent,
|
|
50
54
|
} from './hotkeys.js';
|
|
51
|
-
import {
|
|
55
|
+
import { type BottomBarMode, bottomBarMode } from './mobile-shell.js';
|
|
56
|
+
import {
|
|
57
|
+
installKeyboardWatcher,
|
|
58
|
+
installScrollChrome,
|
|
59
|
+
installShellOverlayMedia,
|
|
60
|
+
installShellViewport,
|
|
61
|
+
watchFormActionBar,
|
|
62
|
+
} from './mobile-shell-dom.js';
|
|
63
|
+
import { clampPanelSize, resolveHotkey } from './settings.js';
|
|
52
64
|
import { createShellState, type ShellState } from './state.svelte.js';
|
|
53
65
|
import type {
|
|
66
|
+
AdminShellPhoneOptions,
|
|
54
67
|
AdminShellProps,
|
|
55
68
|
PanelEdge,
|
|
56
69
|
ShellFocusTool,
|
|
70
|
+
ShellViewport,
|
|
57
71
|
} from './types.js';
|
|
58
72
|
|
|
59
73
|
interface Props extends AdminShellProps {
|
|
@@ -61,6 +75,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
61
75
|
state?: ShellState;
|
|
62
76
|
/** Content for the top app bar. */
|
|
63
77
|
appBar?: Snippet;
|
|
78
|
+
/** Account content kept in the app bar in every left-panel state. */
|
|
79
|
+
account?: Snippet;
|
|
64
80
|
/** Content for the main app panel. */
|
|
65
81
|
appPanel?: Snippet;
|
|
66
82
|
/** Content for the left tenant rail. */
|
|
@@ -87,11 +103,49 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
87
103
|
bottomRightCorner?: Snippet;
|
|
88
104
|
/** Content for the keyboard shortcuts overlay. */
|
|
89
105
|
shortcutsOverlay?: Snippet;
|
|
106
|
+
/**
|
|
107
|
+
* A full-width header row above every edge (`#smrt-admin-shell-header`,
|
|
108
|
+
* 3.5rem; override `--smrt-admin-shell-header-size`). Shown at every
|
|
109
|
+
* width except on phones when `phoneTopBar` is given.
|
|
110
|
+
*/
|
|
111
|
+
header?: Snippet<[{ viewport: ShellViewport }]>;
|
|
112
|
+
/**
|
|
113
|
+
* Phone-only context bar over the top of the main region. Hides while
|
|
114
|
+
* scrolling down and returns on the first scroll up (see
|
|
115
|
+
* `phone.hideOnScroll`); pinned while `pinChrome` or a drawer is open.
|
|
116
|
+
*/
|
|
117
|
+
phoneTopBar?: Snippet<[{ hidden: boolean }]>;
|
|
118
|
+
/**
|
|
119
|
+
* Phone-only bottom bar row. Replaced by a form's action bar
|
|
120
|
+
* (`[data-form-action-bar]` inside main) and hidden while the on-screen
|
|
121
|
+
* keyboard is open.
|
|
122
|
+
*/
|
|
123
|
+
phoneBottomBar?: Snippet;
|
|
124
|
+
/**
|
|
125
|
+
* Host surfaces laid over the shell above the phone bottom bar (phone
|
|
126
|
+
* sheets, status strips). The layer ignores pointer events; its direct
|
|
127
|
+
* children receive them.
|
|
128
|
+
*/
|
|
129
|
+
overlays?: Snippet<[{ viewport: ShellViewport; bottomBar: BottomBarMode }]>;
|
|
130
|
+
/** Phone behavior switches (scrim, swipe to close, hide on scroll). */
|
|
131
|
+
phone?: AdminShellPhoneOptions;
|
|
132
|
+
/** Keep the phone top bar shown (e.g. while a banner is visible). */
|
|
133
|
+
pinChrome?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Current location. When it changes on a phone, open side drawers and
|
|
136
|
+
* sheets close and the phone top bar comes back.
|
|
137
|
+
*/
|
|
138
|
+
path?: string;
|
|
90
139
|
children: Snippet;
|
|
91
140
|
}
|
|
92
141
|
|
|
93
142
|
let {
|
|
94
143
|
title = 'SMRT',
|
|
144
|
+
homeHref,
|
|
145
|
+
showTenantToggle = true,
|
|
146
|
+
logoSrc,
|
|
147
|
+
logoAlt = '',
|
|
148
|
+
brand,
|
|
95
149
|
subtitle = '',
|
|
96
150
|
config,
|
|
97
151
|
settings,
|
|
@@ -99,6 +153,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
99
153
|
storageKey = 'smrt-admin-shell',
|
|
100
154
|
state: providedState,
|
|
101
155
|
appBar,
|
|
156
|
+
account,
|
|
102
157
|
appPanel,
|
|
103
158
|
tenantRail,
|
|
104
159
|
tenantPanel,
|
|
@@ -112,6 +167,13 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
112
167
|
bottomLeftCorner,
|
|
113
168
|
bottomRightCorner,
|
|
114
169
|
shortcutsOverlay,
|
|
170
|
+
header,
|
|
171
|
+
phoneTopBar,
|
|
172
|
+
phoneBottomBar,
|
|
173
|
+
overlays,
|
|
174
|
+
phone,
|
|
175
|
+
pinChrome = false,
|
|
176
|
+
path,
|
|
115
177
|
children,
|
|
116
178
|
}: Props = $props();
|
|
117
179
|
|
|
@@ -138,8 +200,180 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
138
200
|
}
|
|
139
201
|
const shortcutEdges: PanelEdge[] = ['top', 'left', 'bottom', 'right'];
|
|
140
202
|
|
|
203
|
+
const SIDE_EDGES = ['left', 'right'] as const;
|
|
204
|
+
type SideEdge = (typeof SIDE_EDGES)[number];
|
|
205
|
+
|
|
206
|
+
let mainElement: HTMLElement | undefined = $state();
|
|
207
|
+
let keyboardOpen = $state(false);
|
|
208
|
+
let formActions = $state(false);
|
|
209
|
+
let chromeHidden = $state(false);
|
|
210
|
+
let resizing = $state<SideEdge | null>(null);
|
|
211
|
+
const sideElements = $state<Partial<Record<SideEdge, HTMLElement>>>({});
|
|
212
|
+
let scrollChrome: ReturnType<typeof installScrollChrome> | null = null;
|
|
213
|
+
|
|
214
|
+
const viewport = $derived(shell.viewport);
|
|
215
|
+
const isPhone = $derived(viewport === 'phone');
|
|
216
|
+
const narrow = $derived(shell.viewport === 'phone');
|
|
217
|
+
$effect(() => {
|
|
218
|
+
const panel = sideElements.left;
|
|
219
|
+
if (!narrow || !edgeExpanded('left') || !panel) return;
|
|
220
|
+
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
221
|
+
let cancelled = false;
|
|
222
|
+
void tick().then(async () => {
|
|
223
|
+
// Inherited visibility also transitions on primitive buttons; wait for
|
|
224
|
+
// the drawer and its controls to be focusable before moving focus.
|
|
225
|
+
const animations = panel.getAnimations?.({ subtree: true }) ?? [];
|
|
226
|
+
await Promise.all(animations
|
|
227
|
+
.filter((animation) => animation.playState !== 'paused' && Number.isFinite(animation.effect?.getComputedTiming().endTime ?? 0))
|
|
228
|
+
.map((animation) => animation.finished.catch(() => {})));
|
|
229
|
+
if (cancelled || !narrow || !edgeExpanded('left') || !panel.isConnected) return;
|
|
230
|
+
panel.querySelector<HTMLElement>('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')?.focus();
|
|
231
|
+
});
|
|
232
|
+
return () => { cancelled = true; queueMicrotask(() => { if (opener?.isConnected) opener.focus(); }); };
|
|
233
|
+
});
|
|
234
|
+
const showPhoneTop = $derived(isPhone && Boolean(phoneTopBar));
|
|
235
|
+
const showHeader = $derived(Boolean(header) && !showPhoneTop);
|
|
236
|
+
const bottomBar = $derived<BottomBarMode>(
|
|
237
|
+
isPhone && phoneBottomBar
|
|
238
|
+
? bottomBarMode({ formActions, keyboardOpen })
|
|
239
|
+
: 'none',
|
|
240
|
+
);
|
|
241
|
+
const phoneSurfaceOpen = $derived(
|
|
242
|
+
isPhone &&
|
|
243
|
+
SIDE_EDGES.some(
|
|
244
|
+
(edge) => panelState(edge) === 'expanded' && shell.isEdgeShown(edge),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
const chromeStyle = $derived(
|
|
248
|
+
[
|
|
249
|
+
`--smrt-admin-shell-header-track: ${showHeader ? 'var(--smrt-admin-shell-header-size)' : '0rem'}`,
|
|
250
|
+
`--smrt-admin-shell-phone-bar-track: ${bottomBar === 'nav' ? 'var(--smrt-admin-shell-phone-bar-size)' : '0rem'}`,
|
|
251
|
+
].join('; '),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Tablet/desktop: the side edge that is open as an overlay (its
|
|
256
|
+
* `overlayMedia` matches), laid over the page above a scrim.
|
|
257
|
+
*/
|
|
258
|
+
const overlayEdge = $derived<SideEdge | null>(
|
|
259
|
+
isPhone
|
|
260
|
+
? null
|
|
261
|
+
: (SIDE_EDGES.find(
|
|
262
|
+
(edge) =>
|
|
263
|
+
panelState(edge) === 'expanded' &&
|
|
264
|
+
shell.isEdgeShown(edge) &&
|
|
265
|
+
shell.presentationFor(edge) === 'overlay',
|
|
266
|
+
) ?? null),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// An overlay takes focus when it opens (unless something inside already
|
|
270
|
+
// has it) and hands it back to whatever opened it when it closes.
|
|
271
|
+
let overlayReturnFocus: HTMLElement | null = null;
|
|
272
|
+
let lastOverlayEdge: SideEdge | null = null;
|
|
273
|
+
/**
|
|
274
|
+
* An overlay that was just closed and is sliding back out: it keeps its
|
|
275
|
+
* expanded look (inert, above a fading scrim) until the animation ends.
|
|
276
|
+
*/
|
|
277
|
+
let closingEdge = $state<SideEdge | null>(null);
|
|
278
|
+
let closingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
279
|
+
|
|
280
|
+
function endClosing(): void {
|
|
281
|
+
if (closingTimer) clearTimeout(closingTimer);
|
|
282
|
+
closingTimer = null;
|
|
283
|
+
closingEdge = null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function prefersReducedMotion(): boolean {
|
|
287
|
+
return (
|
|
288
|
+
typeof window !== 'undefined' &&
|
|
289
|
+
typeof window.matchMedia === 'function' &&
|
|
290
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** An edge shown open: expanded, or an overlay still sliding out. */
|
|
295
|
+
function shownOpen(edge: PanelEdge): boolean {
|
|
296
|
+
return edgeExpanded(edge) || closingEdge === edge;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
$effect(() => {
|
|
300
|
+
const edge = overlayEdge;
|
|
301
|
+
untrack(() => {
|
|
302
|
+
if (edge === lastOverlayEdge) return;
|
|
303
|
+
const previous = lastOverlayEdge;
|
|
304
|
+
lastOverlayEdge = edge;
|
|
305
|
+
if (typeof document === 'undefined') return;
|
|
306
|
+
if (edge) {
|
|
307
|
+
endClosing();
|
|
308
|
+
const active = document.activeElement;
|
|
309
|
+
overlayReturnFocus =
|
|
310
|
+
active instanceof HTMLElement && active !== document.body
|
|
311
|
+
? active
|
|
312
|
+
: null;
|
|
313
|
+
// Effects run after the DOM update, so the panel is already shown.
|
|
314
|
+
const panel = sideElements[edge];
|
|
315
|
+
if (panel && !panel.contains(document.activeElement)) {
|
|
316
|
+
panel.focus({ preventScroll: true });
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const panel = previous ? sideElements[previous] : undefined;
|
|
321
|
+
const active = document.activeElement;
|
|
322
|
+
const lost =
|
|
323
|
+
!active || active === document.body || Boolean(panel?.contains(active));
|
|
324
|
+
if (lost && overlayReturnFocus?.isConnected) {
|
|
325
|
+
overlayReturnFocus.focus({ preventScroll: true });
|
|
326
|
+
}
|
|
327
|
+
overlayReturnFocus = null;
|
|
328
|
+
// Closed (not just docked by a wider window): slide back out.
|
|
329
|
+
if (
|
|
330
|
+
previous &&
|
|
331
|
+
panelState(previous) !== 'expanded' &&
|
|
332
|
+
shell.presentationFor(previous) === 'overlay' &&
|
|
333
|
+
!isPhone &&
|
|
334
|
+
!prefersReducedMotion()
|
|
335
|
+
) {
|
|
336
|
+
endClosing();
|
|
337
|
+
closingEdge = previous;
|
|
338
|
+
// Fallback when animationend never fires (hidden tab, no CSS).
|
|
339
|
+
closingTimer = setTimeout(endClosing, 600);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
function onEdgeAnimationEnd(event: AnimationEvent, edge: SideEdge): void {
|
|
345
|
+
if (closingEdge === edge && event.target === event.currentTarget) {
|
|
346
|
+
endClosing();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function collapsePhoneSurfaces(): void {
|
|
351
|
+
for (const edge of SIDE_EDGES) {
|
|
352
|
+
if (panelState(edge) === 'expanded' && shell.isEdgeShown(edge)) {
|
|
353
|
+
shell.collapsePanel(edge);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
141
358
|
onMount(() => {
|
|
142
|
-
|
|
359
|
+
const offViewport = installShellViewport(shell);
|
|
360
|
+
const offOverlayMedia = installShellOverlayMedia(shell);
|
|
361
|
+
void shell.hydrate().then(() => shell.closeOverlaidEdges());
|
|
362
|
+
const offKeyboard = phoneBottomBar
|
|
363
|
+
? installKeyboardWatcher((open) => (keyboardOpen = open))
|
|
364
|
+
: () => {};
|
|
365
|
+
const offFormActions =
|
|
366
|
+
phoneBottomBar && mainElement
|
|
367
|
+
? watchFormActionBar(mainElement, (present) => (formActions = present))
|
|
368
|
+
: () => {};
|
|
369
|
+
scrollChrome =
|
|
370
|
+
phoneTopBar && phone?.hideOnScroll !== false && mainElement
|
|
371
|
+
? installScrollChrome(mainElement, {
|
|
372
|
+
onChange: (hidden) => (chromeHidden = hidden),
|
|
373
|
+
isPinned: () =>
|
|
374
|
+
pinChrome || phoneSurfaceOpen || shell.viewport !== 'phone',
|
|
375
|
+
})
|
|
376
|
+
: null;
|
|
143
377
|
|
|
144
378
|
function handleKeydown(event: KeyboardEvent): void {
|
|
145
379
|
if (event.key === 'Escape') {
|
|
@@ -167,9 +401,150 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
167
401
|
}
|
|
168
402
|
|
|
169
403
|
window.addEventListener('keydown', handleKeydown);
|
|
170
|
-
return () =>
|
|
404
|
+
return () => {
|
|
405
|
+
window.removeEventListener('keydown', handleKeydown);
|
|
406
|
+
offViewport();
|
|
407
|
+
offOverlayMedia();
|
|
408
|
+
if (closingTimer) clearTimeout(closingTimer);
|
|
409
|
+
offKeyboard();
|
|
410
|
+
offFormActions();
|
|
411
|
+
scrollChrome?.destroy();
|
|
412
|
+
scrollChrome = null;
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Pinning (a banner appears, a drawer opens, leaving phone) shows the bar.
|
|
417
|
+
$effect(() => {
|
|
418
|
+
if (pinChrome || phoneSurfaceOpen || !isPhone) chromeHidden = false;
|
|
171
419
|
});
|
|
172
420
|
|
|
421
|
+
// Navigating on a phone closes drawers/sheets and brings the top bar back.
|
|
422
|
+
let lastPath: string | undefined;
|
|
423
|
+
$effect(() => {
|
|
424
|
+
const current = path;
|
|
425
|
+
untrack(() => {
|
|
426
|
+
if (lastPath !== undefined && current !== lastPath) {
|
|
427
|
+
if (shell.viewport === 'phone') collapsePhoneSurfaces();
|
|
428
|
+
scrollChrome?.reset();
|
|
429
|
+
chromeHidden = false;
|
|
430
|
+
}
|
|
431
|
+
lastPath = current;
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// ---- Resizable side edges ------------------------------------------------
|
|
436
|
+
|
|
437
|
+
function remInPx(): number {
|
|
438
|
+
if (typeof window === 'undefined') return 16;
|
|
439
|
+
const size = Number.parseFloat(
|
|
440
|
+
window.getComputedStyle(document.documentElement).fontSize,
|
|
441
|
+
);
|
|
442
|
+
return Number.isFinite(size) && size > 0 ? size : 16;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function cssLengthToPx(length: string): number | null {
|
|
446
|
+
const match = /^\s*(-?\d*\.?\d+)(px|rem|em)?\s*$/.exec(length);
|
|
447
|
+
if (!match) return null;
|
|
448
|
+
const value = Number.parseFloat(match[1]);
|
|
449
|
+
return match[2] === 'rem' || match[2] === 'em' ? value * remInPx() : value;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The edge's expanded width in px: the resized width, else the configured
|
|
454
|
+
* `expandedSize` (measured when it is not a plain px/rem length).
|
|
455
|
+
*/
|
|
456
|
+
function currentSize(edge: SideEdge): number {
|
|
457
|
+
const limits = shell.resizeLimits(edge);
|
|
458
|
+
const resized = shell.panelSize(edge);
|
|
459
|
+
if (resized !== null) return resized;
|
|
460
|
+
const width =
|
|
461
|
+
cssLengthToPx(shell.config.panels[edge].expandedSize) ??
|
|
462
|
+
sideElements[edge]?.getBoundingClientRect().width ??
|
|
463
|
+
0;
|
|
464
|
+
return limits ? clampPanelSize(width, limits) : Math.round(width);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function canResize(edge: SideEdge): boolean {
|
|
468
|
+
return (
|
|
469
|
+
!isPhone &&
|
|
470
|
+
edgeExpanded(edge) &&
|
|
471
|
+
shell.presentationFor(edge) === 'push' &&
|
|
472
|
+
shell.resizeLimits(edge) !== null
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function onResizeKeydown(event: KeyboardEvent, edge: SideEdge): void {
|
|
477
|
+
const limits = shell.resizeLimits(edge);
|
|
478
|
+
if (!limits) return;
|
|
479
|
+
const step = limits.step * (event.shiftKey ? 4 : 1);
|
|
480
|
+
const grow = edge === 'left' ? 'ArrowRight' : 'ArrowLeft';
|
|
481
|
+
const shrink = edge === 'left' ? 'ArrowLeft' : 'ArrowRight';
|
|
482
|
+
let next: number | null;
|
|
483
|
+
if (event.key === grow) next = currentSize(edge) + step;
|
|
484
|
+
else if (event.key === shrink) next = currentSize(edge) - step;
|
|
485
|
+
else if (event.key === 'Home') next = limits.min;
|
|
486
|
+
else if (event.key === 'End') next = limits.max;
|
|
487
|
+
else if (event.key === 'Enter') next = null;
|
|
488
|
+
else return;
|
|
489
|
+
event.preventDefault();
|
|
490
|
+
shell.setPanelSize(edge, next);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Tears down the drag in progress, if any (see onResizePointerdown). */
|
|
494
|
+
let stopActiveResize: ((commit: boolean) => void) | null = null;
|
|
495
|
+
|
|
496
|
+
// Unmounting mid-drag must not leave the handle's listeners driving a shell
|
|
497
|
+
// state that outlives this component, nor a stale `resizing` flag.
|
|
498
|
+
$effect(() => () => stopActiveResize?.(false));
|
|
499
|
+
|
|
500
|
+
function onResizePointerdown(event: PointerEvent, edge: SideEdge): void {
|
|
501
|
+
if (event.button !== 0 || !shell.resizeLimits(edge)) return;
|
|
502
|
+
event.preventDefault();
|
|
503
|
+
stopActiveResize?.(true);
|
|
504
|
+
const handle = event.currentTarget as HTMLElement;
|
|
505
|
+
const pointerId = event.pointerId;
|
|
506
|
+
if (pointerId !== undefined) handle.setPointerCapture?.(pointerId);
|
|
507
|
+
const startX = event.clientX;
|
|
508
|
+
const startSize = currentSize(edge);
|
|
509
|
+
let latest = startSize;
|
|
510
|
+
resizing = edge;
|
|
511
|
+
|
|
512
|
+
function onMove(move: PointerEvent): void {
|
|
513
|
+
const dx = move.clientX - startX;
|
|
514
|
+
latest = startSize + (edge === 'left' ? dx : -dx);
|
|
515
|
+
shell.setPanelSize(edge, latest, { persist: false });
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function stop(commit: boolean): void {
|
|
519
|
+
if (stopActiveResize !== stop) return;
|
|
520
|
+
stopActiveResize = null;
|
|
521
|
+
handle.removeEventListener('pointermove', onMove);
|
|
522
|
+
handle.removeEventListener('pointerup', onEnd);
|
|
523
|
+
handle.removeEventListener('pointercancel', onEnd);
|
|
524
|
+
handle.removeEventListener('lostpointercapture', onEnd);
|
|
525
|
+
if (pointerId !== undefined && handle.hasPointerCapture?.(pointerId)) {
|
|
526
|
+
handle.releasePointerCapture(pointerId);
|
|
527
|
+
}
|
|
528
|
+
resizing = null;
|
|
529
|
+
if (commit && latest !== startSize) shell.setPanelSize(edge, latest);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// pointerup/pointercancel end a drag normally; losing the capture (a
|
|
533
|
+
// system gesture, the element leaving the DOM) ends it too, or the shell
|
|
534
|
+
// would stay in its resizing state with nothing to release it.
|
|
535
|
+
function onEnd(): void {
|
|
536
|
+
stop(true);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
stopActiveResize = stop;
|
|
540
|
+
handle.addEventListener('pointermove', onMove);
|
|
541
|
+
handle.addEventListener('pointerup', onEnd);
|
|
542
|
+
handle.addEventListener('pointercancel', onEnd);
|
|
543
|
+
handle.addEventListener('lostpointercapture', onEnd);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const footerInHeader = $derived(!account && !!tenantFooter && (!shell.isEdgeShown('left') || !edgeExpanded('left')));
|
|
547
|
+
|
|
173
548
|
const layoutStyle = $derived(buildLayoutStyle(shell));
|
|
174
549
|
|
|
175
550
|
// Top/bottom edges reserve grid tracks for corner snippets. When a corner
|
|
@@ -194,6 +569,18 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
194
569
|
return panelState(edge) === 'expanded';
|
|
195
570
|
}
|
|
196
571
|
|
|
572
|
+
/** Collapsed edge whose panel content stays mounted (`keepMounted`). */
|
|
573
|
+
function keepsContent(edge: PanelEdge): boolean {
|
|
574
|
+
return (
|
|
575
|
+
panelState(edge) === 'collapsed' &&
|
|
576
|
+
shell.config.panels[edge].keepMounted === true
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function panelMounted(edge: PanelEdge): boolean {
|
|
581
|
+
return shownOpen(edge) || keepsContent(edge);
|
|
582
|
+
}
|
|
583
|
+
|
|
197
584
|
function labelFor(edge: PanelEdge): string {
|
|
198
585
|
return shell.config.panels[edge].label;
|
|
199
586
|
}
|
|
@@ -261,18 +648,43 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
261
648
|
variant="ghost"
|
|
262
649
|
size="sm"
|
|
263
650
|
class="smrt-admin-shell__edge-toggle"
|
|
651
|
+
aria-label={edge === 'left' ? t(M[edgeExpanded(edge) ? 'ui.admin_shell.collapse_panel' : 'ui.admin_shell.expand_panel'], { panel: labelFor(edge) }) : undefined}
|
|
264
652
|
aria-expanded={edgeExpanded(edge)}
|
|
265
653
|
aria-controls={`smrt-admin-shell-${edge}-panel`}
|
|
266
654
|
onclick={() => shell.togglePanel(edge)}
|
|
267
655
|
>
|
|
268
|
-
<span>{labelFor(edge)}</span>
|
|
269
|
-
{#if showsHotkeyFor(edge)}
|
|
656
|
+
{#if edge === 'left' && !edgeExpanded(edge)}<span aria-hidden="true">›</span>{:else}<span>{labelFor(edge)}</span>{/if}
|
|
657
|
+
{#if showsHotkeyFor(edge) && (edge !== 'left' || edgeExpanded(edge))}
|
|
270
658
|
<kbd class="smrt-admin-shell__edge-toggle-kbd">{hotkeyFor(edge)}</kbd>
|
|
271
659
|
{/if}
|
|
272
660
|
<ActivityBadge {edge} />
|
|
273
661
|
</Button>
|
|
274
662
|
{/snippet}
|
|
275
663
|
|
|
664
|
+
{#snippet resizer(edge: SideEdge)}
|
|
665
|
+
{#if canResize(edge)}
|
|
666
|
+
{@const limits = shell.resizeLimits(edge)}
|
|
667
|
+
<!-- A focusable separator is the WAI-ARIA window-splitter widget; Svelte's
|
|
668
|
+
role table lists `separator` as non-interactive only in its static form. -->
|
|
669
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
670
|
+
<div
|
|
671
|
+
class="smrt-admin-shell__resizer smrt-admin-shell__resizer--{edge}"
|
|
672
|
+
role="separator"
|
|
673
|
+
aria-orientation="vertical"
|
|
674
|
+
aria-controls={`smrt-admin-shell-${edge}-panel`}
|
|
675
|
+
aria-label={t(M['ui.admin_shell.resize_panel'], { label: labelFor(edge) })}
|
|
676
|
+
aria-valuemin={limits?.min}
|
|
677
|
+
aria-valuemax={limits?.max}
|
|
678
|
+
aria-valuenow={currentSize(edge)}
|
|
679
|
+
tabindex="0"
|
|
680
|
+
data-testid={`admin-shell-resizer-${edge}`}
|
|
681
|
+
onpointerdown={(event) => onResizePointerdown(event, edge)}
|
|
682
|
+
onkeydown={(event) => onResizeKeydown(event, edge)}
|
|
683
|
+
ondblclick={() => shell.setPanelSize(edge, null)}
|
|
684
|
+
></div>
|
|
685
|
+
{/if}
|
|
686
|
+
{/snippet}
|
|
687
|
+
|
|
276
688
|
{#snippet focusContent(tool: ShellFocusTool | null)}
|
|
277
689
|
{#if focusPanel}
|
|
278
690
|
{@render focusPanel({ tool })}
|
|
@@ -288,14 +700,52 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
288
700
|
{/if}
|
|
289
701
|
{/snippet}
|
|
290
702
|
|
|
703
|
+
{#snippet brandContent(compact: boolean)}
|
|
704
|
+
{#if brand}
|
|
705
|
+
{@render brand({ compact })}
|
|
706
|
+
{:else}
|
|
707
|
+
{#if logoSrc}<img class="smrt-admin-shell__logo" src={logoSrc} alt={logoAlt} />{/if}
|
|
708
|
+
{#if compact}
|
|
709
|
+
{#if !logoSrc}<span aria-hidden="true">{title.charAt(0)}</span>{/if}
|
|
710
|
+
{:else}
|
|
711
|
+
<div class="smrt-admin-shell__brand-text"><strong>{title}</strong>{#if subtitle}<span>{subtitle}</span>{/if}</div>
|
|
712
|
+
{/if}
|
|
713
|
+
{/if}
|
|
714
|
+
{/snippet}
|
|
715
|
+
|
|
716
|
+
{#snippet shellBrand(compact = false)}
|
|
717
|
+
{#if homeHref}
|
|
718
|
+
<a class="smrt-admin-shell__brand smrt-admin-shell__brand-link" class:compact href={homeHref} aria-label={title || t(M['ui.admin_shell.home'])}>
|
|
719
|
+
{@render brandContent(compact)}
|
|
720
|
+
</a>
|
|
721
|
+
{:else}
|
|
722
|
+
<div class="smrt-admin-shell__brand" class:compact>{@render brandContent(compact)}</div>
|
|
723
|
+
{/if}
|
|
724
|
+
{/snippet}
|
|
725
|
+
|
|
291
726
|
<div
|
|
292
727
|
class="smrt-admin-shell"
|
|
293
728
|
data-top-state={panelState('top')}
|
|
294
729
|
data-left-state={panelState('left')}
|
|
295
730
|
data-right-state={panelState('right')}
|
|
296
731
|
data-bottom-state={panelState('bottom')}
|
|
297
|
-
|
|
732
|
+
data-viewport={viewport}
|
|
733
|
+
data-bottom-bar={bottomBar}
|
|
734
|
+
data-phone-top={showPhoneTop ? '' : undefined}
|
|
735
|
+
data-chrome-hidden={chromeHidden ? '' : undefined}
|
|
736
|
+
data-resizing={resizing ?? undefined}
|
|
737
|
+
style={`${layoutStyle}; ${chromeStyle}`}
|
|
298
738
|
>
|
|
739
|
+
{#if showHeader}
|
|
740
|
+
<div
|
|
741
|
+
id="smrt-admin-shell-header"
|
|
742
|
+
class="smrt-admin-shell__header"
|
|
743
|
+
data-testid="admin-shell-header"
|
|
744
|
+
>
|
|
745
|
+
{@render header?.({ viewport })}
|
|
746
|
+
</div>
|
|
747
|
+
{/if}
|
|
748
|
+
|
|
299
749
|
{#if panelState('top') !== 'hidden'}
|
|
300
750
|
<header
|
|
301
751
|
id="smrt-admin-shell-top-panel"
|
|
@@ -316,24 +766,30 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
316
766
|
{#if appBar}
|
|
317
767
|
{@render appBar()}
|
|
318
768
|
{:else}
|
|
319
|
-
|
|
320
|
-
<strong>{title}</strong>
|
|
321
|
-
{#if subtitle}
|
|
322
|
-
<span>{subtitle}</span>
|
|
323
|
-
{/if}
|
|
324
|
-
</div>
|
|
769
|
+
{@render shellBrand()}
|
|
325
770
|
{@render edgeToggle('top')}
|
|
326
771
|
{/if}
|
|
772
|
+
{#if shell.config.panels.left.initial !== 'hidden' && (!isPhone || shell.phonePresentation('left') !== 'hidden')}
|
|
773
|
+
<div class="smrt-admin-shell__tenant-opener" class:restore-hidden={panelState('left') === 'hidden'}>
|
|
774
|
+
<Button variant="ghost" size="sm" aria-label={t(M['ui.admin_shell.menu'])} aria-expanded={edgeExpanded('left')} aria-controls="smrt-admin-shell-left-panel" onclick={() => shell.setPanelState('left', edgeExpanded('left') ? 'collapsed' : 'expanded')}>{t(M['ui.admin_shell.menu'])}</Button>
|
|
775
|
+
</div>
|
|
776
|
+
{/if}
|
|
777
|
+
{#if account || footerInHeader}
|
|
778
|
+
<div class="smrt-admin-shell__account">
|
|
779
|
+
{#if account}{@render account()}{:else if tenantFooter}{@render tenantFooter()}{/if}
|
|
780
|
+
</div>
|
|
781
|
+
{/if}
|
|
327
782
|
</div>
|
|
328
783
|
{#if topRightCorner}
|
|
329
784
|
<div class="smrt-admin-shell__corner smrt-admin-shell__corner--top-right">
|
|
330
785
|
{@render topRightCorner()}
|
|
331
786
|
</div>
|
|
332
787
|
{/if}
|
|
333
|
-
{#if
|
|
788
|
+
{#if panelMounted('top')}
|
|
334
789
|
<section
|
|
335
790
|
class="smrt-admin-shell__drawer smrt-admin-shell__drawer--top"
|
|
336
791
|
aria-label={labelFor('top')}
|
|
792
|
+
hidden={!edgeExpanded('top')}
|
|
337
793
|
>
|
|
338
794
|
{#if appPanel}
|
|
339
795
|
{@render appPanel()}
|
|
@@ -347,54 +803,145 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
347
803
|
</header>
|
|
348
804
|
{/if}
|
|
349
805
|
|
|
350
|
-
{#if
|
|
806
|
+
{#if shell.isEdgeShown('left')}
|
|
807
|
+
<!-- tabindex -1 only while overlaid, so the panel can take focus on open -->
|
|
808
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
351
809
|
<aside
|
|
352
810
|
id="smrt-admin-shell-left-panel"
|
|
353
811
|
class="smrt-admin-shell__edge smrt-admin-shell__edge--left"
|
|
354
|
-
data-state={panelState('left')}
|
|
355
|
-
data-
|
|
812
|
+
data-state={closingEdge === 'left' ? 'expanded' : panelState('left')}
|
|
813
|
+
data-closing={closingEdge === 'left' ? '' : undefined}
|
|
814
|
+
data-presentation={shell.presentationFor('left')}
|
|
356
815
|
role="navigation"
|
|
357
816
|
aria-label={labelFor('left')}
|
|
817
|
+
tabindex={overlayEdge === 'left' ? -1 : undefined}
|
|
818
|
+
inert={(isPhone && !edgeExpanded('left')) ||
|
|
819
|
+
overlayEdge === 'right' ||
|
|
820
|
+
closingEdge === 'left'}
|
|
821
|
+
onanimationend={(event) => onEdgeAnimationEnd(event, 'left')}
|
|
822
|
+
bind:this={sideElements.left}
|
|
823
|
+
use:swipeDismiss={{
|
|
824
|
+
direction: 'left',
|
|
825
|
+
onDismiss: () => shell.collapsePanel('left'),
|
|
826
|
+
enabled: isPhone && Boolean(phone?.swipeToClose) && edgeExpanded('left'),
|
|
827
|
+
}}
|
|
358
828
|
>
|
|
829
|
+
{@render resizer('left')}
|
|
359
830
|
<div class="smrt-admin-shell__rail">
|
|
360
|
-
{#if edgeExpanded('left')}
|
|
361
|
-
|
|
831
|
+
{#if !edgeExpanded('left') && (homeHref || logoSrc || brand)}{@render shellBrand(true)}{/if}
|
|
832
|
+
{#if showTenantToggle}{@render edgeToggle('left')}{/if}
|
|
833
|
+
{#if shownOpen('left') || (tenantPanel && keepsContent('left'))}
|
|
834
|
+
<div
|
|
835
|
+
class="smrt-admin-shell__tenant-stack"
|
|
836
|
+
hidden={!shownOpen('left')}
|
|
837
|
+
>
|
|
362
838
|
<div class="smrt-admin-shell__tenant-content">
|
|
363
839
|
{#if tenantPanel}
|
|
364
840
|
{@render tenantPanel()}
|
|
365
841
|
{:else if tenantRail}
|
|
366
842
|
{@render tenantRail()}
|
|
367
|
-
{:else}
|
|
368
|
-
{@render edgeToggle('left')}
|
|
369
843
|
{/if}
|
|
370
844
|
</div>
|
|
371
|
-
{#if tenantFooter}
|
|
845
|
+
{#if tenantFooter && !footerInHeader}
|
|
372
846
|
<div class="smrt-admin-shell__tenant-footer">
|
|
373
847
|
{@render tenantFooter()}
|
|
374
848
|
</div>
|
|
375
849
|
{/if}
|
|
376
850
|
</div>
|
|
377
|
-
{
|
|
851
|
+
{/if}
|
|
852
|
+
{#if !shownOpen('left') && tenantRail}
|
|
378
853
|
{@render tenantRail()}
|
|
379
|
-
{:else}
|
|
380
|
-
{@render edgeToggle('left')}
|
|
381
854
|
{/if}
|
|
382
855
|
</div>
|
|
383
856
|
</aside>
|
|
384
857
|
{/if}
|
|
385
858
|
|
|
386
|
-
<main
|
|
859
|
+
<main
|
|
860
|
+
id="smrt-admin-shell-main"
|
|
861
|
+
class="smrt-admin-shell__main"
|
|
862
|
+
bind:this={mainElement}
|
|
863
|
+
inert={overlayEdge !== null}
|
|
864
|
+
>
|
|
387
865
|
{@render children()}
|
|
388
866
|
</main>
|
|
389
867
|
|
|
390
|
-
{#if
|
|
868
|
+
{#if showPhoneTop}
|
|
869
|
+
<div class="smrt-admin-shell__phone-top" data-testid="admin-shell-phone-top">
|
|
870
|
+
{@render phoneTopBar?.({ hidden: chromeHidden })}
|
|
871
|
+
</div>
|
|
872
|
+
{/if}
|
|
873
|
+
|
|
874
|
+
{#if isPhone && phone?.scrim && phoneSurfaceOpen}
|
|
875
|
+
<!-- raw-primitive-allow: click-catching backdrop behind a phone drawer -->
|
|
876
|
+
<button
|
|
877
|
+
type="button"
|
|
878
|
+
class="smrt-admin-shell__scrim"
|
|
879
|
+
tabindex="-1"
|
|
880
|
+
aria-label={t(M['ui.admin_shell.close_panel'], {
|
|
881
|
+
label: labelFor(edgeExpanded('left') && shell.isEdgeShown('left') ? 'left' : 'right'),
|
|
882
|
+
})}
|
|
883
|
+
data-testid="admin-shell-scrim"
|
|
884
|
+
onclick={collapsePhoneSurfaces}
|
|
885
|
+
></button>
|
|
886
|
+
{/if}
|
|
887
|
+
|
|
888
|
+
{#if overlayEdge || closingEdge}
|
|
889
|
+
{@const scrimEdge = (overlayEdge ?? closingEdge) as SideEdge}
|
|
890
|
+
<!-- raw-primitive-allow: click-catching backdrop behind an overlaid side panel -->
|
|
891
|
+
<button
|
|
892
|
+
type="button"
|
|
893
|
+
class="smrt-admin-shell__overlay-scrim"
|
|
894
|
+
tabindex="-1"
|
|
895
|
+
aria-label={t(M['ui.admin_shell.close_panel'], { label: labelFor(scrimEdge) })}
|
|
896
|
+
data-testid="admin-shell-overlay-scrim"
|
|
897
|
+
data-closing={overlayEdge ? undefined : ''}
|
|
898
|
+
onclick={() => {
|
|
899
|
+
if (overlayEdge) shell.collapsePanel(scrimEdge);
|
|
900
|
+
}}
|
|
901
|
+
></button>
|
|
902
|
+
{/if}
|
|
903
|
+
|
|
904
|
+
{#if shell.isEdgeShown('right')}
|
|
905
|
+
{@const rightPhone = isPhone ? shell.phonePresentation('right') : undefined}
|
|
906
|
+
<!-- tabindex -1 only while overlaid, so the panel can take focus on open -->
|
|
907
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
391
908
|
<aside
|
|
392
909
|
id="smrt-admin-shell-right-panel"
|
|
393
910
|
class="smrt-admin-shell__edge smrt-admin-shell__edge--right"
|
|
394
|
-
data-state={panelState('right')}
|
|
395
|
-
data-
|
|
911
|
+
data-state={closingEdge === 'right' ? 'expanded' : panelState('right')}
|
|
912
|
+
data-closing={closingEdge === 'right' ? '' : undefined}
|
|
913
|
+
data-presentation={shell.presentationFor('right')}
|
|
914
|
+
data-phone={rightPhone}
|
|
396
915
|
aria-label={labelFor('right')}
|
|
916
|
+
tabindex={overlayEdge === 'right' ? -1 : undefined}
|
|
917
|
+
inert={(isPhone && !edgeExpanded('right')) ||
|
|
918
|
+
overlayEdge === 'left' ||
|
|
919
|
+
closingEdge === 'right'}
|
|
920
|
+
onanimationend={(event) => onEdgeAnimationEnd(event, 'right')}
|
|
921
|
+
bind:this={sideElements.right}
|
|
922
|
+
use:swipeDismiss={{
|
|
923
|
+
direction: 'right',
|
|
924
|
+
onDismiss: () => shell.collapsePanel('right'),
|
|
925
|
+
enabled:
|
|
926
|
+
rightPhone === 'drawer' &&
|
|
927
|
+
Boolean(phone?.swipeToClose) &&
|
|
928
|
+
edgeExpanded('right'),
|
|
929
|
+
}}
|
|
397
930
|
>
|
|
931
|
+
{@render resizer('right')}
|
|
932
|
+
{#if rightPhone === 'sheet'}
|
|
933
|
+
<div
|
|
934
|
+
class="smrt-admin-shell__sheet-grabber"
|
|
935
|
+
aria-hidden="true"
|
|
936
|
+
use:swipeDismiss={{
|
|
937
|
+
direction: 'down',
|
|
938
|
+
onDismiss: () => shell.collapsePanel('right'),
|
|
939
|
+
enabled: edgeExpanded('right'),
|
|
940
|
+
}}
|
|
941
|
+
>
|
|
942
|
+
<span></span>
|
|
943
|
+
</div>
|
|
944
|
+
{/if}
|
|
398
945
|
<div class="smrt-admin-shell__rail">
|
|
399
946
|
{#if focusRail}
|
|
400
947
|
{@render focusRail()}
|
|
@@ -430,8 +977,11 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
430
977
|
{@render edgeToggle('right')}
|
|
431
978
|
{/if}
|
|
432
979
|
</div>
|
|
433
|
-
{#if
|
|
434
|
-
<div
|
|
980
|
+
{#if panelMounted('right')}
|
|
981
|
+
<div
|
|
982
|
+
class="smrt-admin-shell__panel smrt-admin-shell__panel--right"
|
|
983
|
+
hidden={!shownOpen('right')}
|
|
984
|
+
>
|
|
435
985
|
{#key shell.activeFocusToolId}
|
|
436
986
|
{@render focusContent(resolveActiveFocusTool())}
|
|
437
987
|
{/key}
|
|
@@ -457,21 +1007,19 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
457
1007
|
class="smrt-admin-shell__band smrt-admin-shell__band--bottom"
|
|
458
1008
|
style:--band-column={2}
|
|
459
1009
|
>
|
|
460
|
-
{#if systemBar}
|
|
461
|
-
|
|
462
|
-
{:else}
|
|
463
|
-
{@render edgeToggle('bottom')}
|
|
464
|
-
{/if}
|
|
1010
|
+
{#if systemBar}{@render systemBar()}{/if}
|
|
1011
|
+
{@render edgeToggle('bottom')}
|
|
465
1012
|
</div>
|
|
466
1013
|
{#if bottomRightCorner}
|
|
467
1014
|
<div class="smrt-admin-shell__corner smrt-admin-shell__corner--bottom-right">
|
|
468
1015
|
{@render bottomRightCorner()}
|
|
469
1016
|
</div>
|
|
470
1017
|
{/if}
|
|
471
|
-
{#if
|
|
1018
|
+
{#if panelMounted('bottom')}
|
|
472
1019
|
<section
|
|
473
1020
|
class="smrt-admin-shell__drawer smrt-admin-shell__drawer--bottom"
|
|
474
1021
|
aria-label={labelFor('bottom')}
|
|
1022
|
+
hidden={!edgeExpanded('bottom')}
|
|
475
1023
|
>
|
|
476
1024
|
{#if systemPanel}
|
|
477
1025
|
{@render systemPanel()}
|
|
@@ -485,6 +1033,18 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
485
1033
|
</footer>
|
|
486
1034
|
{/if}
|
|
487
1035
|
|
|
1036
|
+
{#if overlays}
|
|
1037
|
+
<div class="smrt-admin-shell__overlays">
|
|
1038
|
+
{@render overlays({ viewport, bottomBar })}
|
|
1039
|
+
</div>
|
|
1040
|
+
{/if}
|
|
1041
|
+
|
|
1042
|
+
{#if bottomBar === 'nav'}
|
|
1043
|
+
<div class="smrt-admin-shell__phone-bar" data-testid="admin-shell-phone-bar">
|
|
1044
|
+
{@render phoneBottomBar?.()}
|
|
1045
|
+
</div>
|
|
1046
|
+
{/if}
|
|
1047
|
+
|
|
488
1048
|
{#if shortcutsOpen}
|
|
489
1049
|
<div
|
|
490
1050
|
class="smrt-admin-shell__shortcuts"
|
|
@@ -537,19 +1097,63 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
537
1097
|
--smrt-admin-shell-right-collapsed: 4.25rem;
|
|
538
1098
|
--smrt-admin-shell-left-expanded: 16rem;
|
|
539
1099
|
--smrt-admin-shell-right-expanded: 20rem;
|
|
1100
|
+
--smrt-admin-shell-header-size: 3.5rem;
|
|
1101
|
+
--smrt-admin-shell-header-track: 0rem;
|
|
1102
|
+
--smrt-admin-shell-phone-top-size: 3.5rem;
|
|
1103
|
+
--smrt-admin-shell-phone-bar-size: calc(3.5rem + env(safe-area-inset-bottom));
|
|
1104
|
+
--smrt-admin-shell-phone-bar-track: 0rem;
|
|
1105
|
+
--smrt-admin-shell-chrome-duration: var(--smrt-duration-short4, 200ms);
|
|
540
1106
|
position: relative;
|
|
541
1107
|
display: grid;
|
|
542
1108
|
grid-template-columns:
|
|
543
1109
|
var(--smrt-admin-shell-left-track) minmax(0, 1fr)
|
|
544
1110
|
var(--smrt-admin-shell-right-track);
|
|
1111
|
+
/* header · top edge · body · bottom edge · phone bottom bar. The extra
|
|
1112
|
+
rows are 0 unless the header / phone bar render. */
|
|
545
1113
|
grid-template-rows:
|
|
1114
|
+
var(--smrt-admin-shell-header-track)
|
|
546
1115
|
var(--smrt-admin-shell-top-track) minmax(0, 1fr)
|
|
547
|
-
var(--smrt-admin-shell-bottom-track)
|
|
1116
|
+
var(--smrt-admin-shell-bottom-track)
|
|
1117
|
+
var(--smrt-admin-shell-phone-bar-track);
|
|
548
1118
|
min-block-size: 100svh;
|
|
549
1119
|
block-size: 100svh;
|
|
550
|
-
|
|
1120
|
+
/* Themeable: an app sets --smrt-admin-shell-background on the shell or an
|
|
1121
|
+
ancestor (a tinted page behind cards, for instance). */
|
|
1122
|
+
background: var(--smrt-admin-shell-background, var(--smrt-color-surface));
|
|
551
1123
|
color: var(--smrt-color-on-surface);
|
|
552
1124
|
overflow: hidden;
|
|
1125
|
+
/* Themed, thin scrollbars for every scroller inside the shell. Override
|
|
1126
|
+
--smrt-admin-shell-scrollbar-track / -thumb / -thumb-hover to retint. */
|
|
1127
|
+
scrollbar-color: var(--smrt-admin-shell-scrollbar-thumb, color-mix(in srgb, var(--smrt-color-on-surface) 26%, transparent))
|
|
1128
|
+
var(--smrt-admin-shell-scrollbar-track, color-mix(in srgb, var(--smrt-color-surface-container-low) 88%, transparent));
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.smrt-admin-shell :global(*) {
|
|
1132
|
+
scrollbar-width: thin;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.smrt-admin-shell :global(*)::-webkit-scrollbar {
|
|
1136
|
+
width: 0.7rem;
|
|
1137
|
+
height: 0.7rem;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.smrt-admin-shell :global(*)::-webkit-scrollbar-track {
|
|
1141
|
+
background: var(--smrt-admin-shell-scrollbar-track, color-mix(in srgb, var(--smrt-color-surface-container-low) 88%, transparent));
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
.smrt-admin-shell :global(*)::-webkit-scrollbar-thumb {
|
|
1145
|
+
background-color: var(--smrt-admin-shell-scrollbar-thumb, color-mix(in srgb, var(--smrt-color-on-surface) 26%, transparent));
|
|
1146
|
+
background-clip: padding-box;
|
|
1147
|
+
border: 2px solid transparent;
|
|
1148
|
+
border-radius: var(--smrt-radius-full, 999px);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.smrt-admin-shell :global(*)::-webkit-scrollbar-thumb:hover {
|
|
1152
|
+
background-color: var(--smrt-admin-shell-scrollbar-thumb-hover, color-mix(in srgb, var(--smrt-color-on-surface) 38%, transparent));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.smrt-admin-shell :global(*)::-webkit-scrollbar-corner {
|
|
1156
|
+
background: transparent;
|
|
553
1157
|
}
|
|
554
1158
|
|
|
555
1159
|
.smrt-admin-shell__edge {
|
|
@@ -560,9 +1164,30 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
560
1164
|
border-color: var(--smrt-color-outline-variant);
|
|
561
1165
|
}
|
|
562
1166
|
|
|
563
|
-
.smrt-admin-
|
|
1167
|
+
.smrt-admin-shell__header {
|
|
564
1168
|
grid-column: 1 / -1;
|
|
565
1169
|
grid-row: 1;
|
|
1170
|
+
display: flex;
|
|
1171
|
+
align-items: center;
|
|
1172
|
+
gap: var(--smrt-spacing-2);
|
|
1173
|
+
box-sizing: border-box;
|
|
1174
|
+
min-inline-size: 0;
|
|
1175
|
+
min-block-size: 0;
|
|
1176
|
+
padding: 0 var(--smrt-spacing-3) 0 var(--smrt-spacing-2);
|
|
1177
|
+
/* Clip long titles sideways only: menus and popovers opened from the
|
|
1178
|
+
header's actions (a notifications bell, an account menu) must be able
|
|
1179
|
+
to drop below it. `clip` (unlike `hidden`) allows a visible block axis. */
|
|
1180
|
+
overflow-x: clip;
|
|
1181
|
+
overflow-y: visible;
|
|
1182
|
+
border-block-end: 1px solid var(--smrt-color-outline-variant);
|
|
1183
|
+
background: var(--smrt-color-surface-container-low);
|
|
1184
|
+
color: var(--smrt-color-on-surface);
|
|
1185
|
+
z-index: 30;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.smrt-admin-shell__edge--top {
|
|
1189
|
+
grid-column: 1 / -1;
|
|
1190
|
+
grid-row: 2;
|
|
566
1191
|
display: grid;
|
|
567
1192
|
grid-template-columns: var(
|
|
568
1193
|
--edge-columns,
|
|
@@ -574,15 +1199,17 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
574
1199
|
}
|
|
575
1200
|
|
|
576
1201
|
.smrt-admin-shell__edge--left {
|
|
1202
|
+
position: relative;
|
|
577
1203
|
grid-column: 1;
|
|
578
|
-
grid-row:
|
|
1204
|
+
grid-row: 3;
|
|
579
1205
|
border-inline-end: 1px solid var(--smrt-color-outline-variant);
|
|
580
1206
|
z-index: 20;
|
|
581
1207
|
}
|
|
582
1208
|
|
|
583
1209
|
.smrt-admin-shell__edge--right {
|
|
1210
|
+
position: relative;
|
|
584
1211
|
grid-column: 3;
|
|
585
|
-
grid-row:
|
|
1212
|
+
grid-row: 3;
|
|
586
1213
|
border-inline-start: 1px solid var(--smrt-color-outline-variant);
|
|
587
1214
|
z-index: 20;
|
|
588
1215
|
}
|
|
@@ -596,7 +1223,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
596
1223
|
|
|
597
1224
|
.smrt-admin-shell__edge--bottom {
|
|
598
1225
|
grid-column: 1 / -1;
|
|
599
|
-
grid-row:
|
|
1226
|
+
grid-row: 4;
|
|
600
1227
|
display: grid;
|
|
601
1228
|
grid-template-columns: var(
|
|
602
1229
|
--edge-columns,
|
|
@@ -609,7 +1236,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
609
1236
|
|
|
610
1237
|
.smrt-admin-shell__main {
|
|
611
1238
|
grid-column: 2;
|
|
612
|
-
grid-row:
|
|
1239
|
+
grid-row: 3;
|
|
613
1240
|
min-width: 0;
|
|
614
1241
|
min-height: 0;
|
|
615
1242
|
overflow: auto;
|
|
@@ -631,9 +1258,17 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
631
1258
|
}
|
|
632
1259
|
|
|
633
1260
|
.smrt-admin-shell__brand {
|
|
634
|
-
display:
|
|
1261
|
+
display: flex;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
gap: var(--smrt-spacing-2);
|
|
635
1264
|
min-width: 0;
|
|
1265
|
+
color: inherit;
|
|
1266
|
+
text-decoration: none;
|
|
636
1267
|
}
|
|
1268
|
+
.smrt-admin-shell__brand-text { display: grid; min-width: 0; }
|
|
1269
|
+
.smrt-admin-shell__brand.compact { justify-content: center; margin-block-end: var(--smrt-spacing-2); }
|
|
1270
|
+
.smrt-admin-shell__brand-link:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
|
|
1271
|
+
.smrt-admin-shell__logo { inline-size: 2rem; block-size: 2rem; object-fit: contain; flex-shrink: 0; }
|
|
637
1272
|
|
|
638
1273
|
.smrt-admin-shell__brand strong,
|
|
639
1274
|
.smrt-admin-shell__brand span {
|
|
@@ -656,6 +1291,15 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
656
1291
|
padding: var(--smrt-spacing-3);
|
|
657
1292
|
}
|
|
658
1293
|
|
|
1294
|
+
/* A right rail never pads wider than its own track: a rail collapsed to 0
|
|
1295
|
+
(an app that shows nothing there) paints no padding. */
|
|
1296
|
+
.smrt-admin-shell__edge--right .smrt-admin-shell__rail {
|
|
1297
|
+
padding-inline: min(
|
|
1298
|
+
var(--smrt-spacing-3),
|
|
1299
|
+
calc(var(--smrt-admin-shell-right-collapsed) / 4)
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
659
1303
|
.smrt-admin-shell__focus-rail {
|
|
660
1304
|
display: grid;
|
|
661
1305
|
/* Center tracks, not just items: buttons may be wider than the
|
|
@@ -704,13 +1348,43 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
704
1348
|
overflow: hidden;
|
|
705
1349
|
}
|
|
706
1350
|
|
|
1351
|
+
.smrt-admin-shell__account {
|
|
1352
|
+
min-inline-size: 0;
|
|
1353
|
+
max-inline-size: min(20rem, 45vw);
|
|
1354
|
+
flex: 1 1 12rem;
|
|
1355
|
+
}
|
|
1356
|
+
.smrt-admin-shell__account :global(.smrt-workspace-account-menu .dropdown__menu) {
|
|
1357
|
+
top: 100%; bottom: auto; right: 0; left: auto;
|
|
1358
|
+
inline-size: max-content;
|
|
1359
|
+
max-inline-size: calc(100vw - 2 * var(--smrt-spacing-4));
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.smrt-admin-shell__tenant-opener { display: none; flex-shrink: 0; }
|
|
1363
|
+
.smrt-admin-shell__tenant-opener.restore-hidden { display: block; }
|
|
1364
|
+
.smrt-admin-shell__edge--left[data-state='collapsed'] .smrt-admin-shell__rail { padding-inline: var(--smrt-spacing-2); }
|
|
1365
|
+
|
|
1366
|
+
.smrt-admin-shell__edge--left .smrt-admin-shell__rail {
|
|
1367
|
+
display: flex;
|
|
1368
|
+
flex-direction: column;
|
|
1369
|
+
gap: var(--smrt-spacing-2);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/* A collapsed `keepMounted` edge keeps its panel in the DOM but hidden;
|
|
1373
|
+
the panel classes set `display`, which would otherwise win over the
|
|
1374
|
+
`hidden` attribute. */
|
|
1375
|
+
.smrt-admin-shell__tenant-stack[hidden],
|
|
1376
|
+
.smrt-admin-shell__panel[hidden],
|
|
1377
|
+
.smrt-admin-shell__drawer[hidden] {
|
|
1378
|
+
display: none !important;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
707
1381
|
.smrt-admin-shell__tenant-stack {
|
|
1382
|
+
flex: 1;
|
|
708
1383
|
display: grid;
|
|
709
1384
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
710
1385
|
gap: var(--smrt-spacing-3);
|
|
711
1386
|
min-width: 0;
|
|
712
1387
|
min-height: 0;
|
|
713
|
-
block-size: 100%;
|
|
714
1388
|
}
|
|
715
1389
|
|
|
716
1390
|
.smrt-admin-shell__tenant-content {
|
|
@@ -764,14 +1438,19 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
764
1438
|
}
|
|
765
1439
|
|
|
766
1440
|
.smrt-admin-shell__drawer--top {
|
|
767
|
-
inset-block-start:
|
|
1441
|
+
inset-block-start: calc(
|
|
1442
|
+
var(--smrt-admin-shell-header-track) + var(--smrt-admin-shell-top-track)
|
|
1443
|
+
);
|
|
768
1444
|
inset-inline: var(--smrt-admin-shell-left-track)
|
|
769
1445
|
var(--smrt-admin-shell-right-track);
|
|
770
1446
|
max-block-size: var(--smrt-admin-shell-top-expanded);
|
|
771
1447
|
}
|
|
772
1448
|
|
|
773
1449
|
.smrt-admin-shell__drawer--bottom {
|
|
774
|
-
inset-block-end:
|
|
1450
|
+
inset-block-end: calc(
|
|
1451
|
+
var(--smrt-admin-shell-bottom-track) +
|
|
1452
|
+
var(--smrt-admin-shell-phone-bar-track)
|
|
1453
|
+
);
|
|
775
1454
|
/* The system scope is the application footer. Its expanded drawer must
|
|
776
1455
|
remain footer-wide while it rises above every desktop pane. */
|
|
777
1456
|
inset-inline: 0;
|
|
@@ -782,7 +1461,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
782
1461
|
|
|
783
1462
|
@keyframes smrt-admin-shell-bottom-drawer-in {
|
|
784
1463
|
from {
|
|
785
|
-
|
|
1464
|
+
clip-path: inset(100% 0 0);
|
|
786
1465
|
}
|
|
787
1466
|
}
|
|
788
1467
|
|
|
@@ -848,20 +1527,335 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
848
1527
|
font-family: var(--smrt-font-family-mono);
|
|
849
1528
|
}
|
|
850
1529
|
|
|
1530
|
+
/* ---- Resizable side edges ------------------------------------------ */
|
|
1531
|
+
|
|
1532
|
+
.smrt-admin-shell__resizer {
|
|
1533
|
+
position: absolute;
|
|
1534
|
+
inset-block: 0;
|
|
1535
|
+
z-index: 2;
|
|
1536
|
+
inline-size: 0.75rem;
|
|
1537
|
+
cursor: col-resize;
|
|
1538
|
+
touch-action: none;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.smrt-admin-shell__resizer--left {
|
|
1542
|
+
inset-inline-end: -0.375rem;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.smrt-admin-shell__resizer--right {
|
|
1546
|
+
inset-inline-start: -0.375rem;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
.smrt-admin-shell__resizer::after {
|
|
1550
|
+
content: '';
|
|
1551
|
+
position: absolute;
|
|
1552
|
+
inset-block: 0;
|
|
1553
|
+
inset-inline-start: calc(50% - 1px);
|
|
1554
|
+
inline-size: 2px;
|
|
1555
|
+
background: var(--smrt-color-primary);
|
|
1556
|
+
opacity: 0;
|
|
1557
|
+
transition: opacity var(--smrt-duration-short2, 100ms)
|
|
1558
|
+
var(--smrt-easing-standard, ease);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.smrt-admin-shell__resizer:hover::after,
|
|
1562
|
+
.smrt-admin-shell__resizer:focus-visible::after,
|
|
1563
|
+
.smrt-admin-shell[data-resizing] .smrt-admin-shell__resizer::after {
|
|
1564
|
+
opacity: 1;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
.smrt-admin-shell__resizer:focus-visible {
|
|
1568
|
+
outline: none;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.smrt-admin-shell[data-resizing] {
|
|
1572
|
+
cursor: col-resize;
|
|
1573
|
+
user-select: none;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
/* ---- Phone chrome ----------------------------------------------------- */
|
|
1577
|
+
|
|
1578
|
+
.smrt-admin-shell__phone-top {
|
|
1579
|
+
grid-column: 1 / -1;
|
|
1580
|
+
grid-row: 3;
|
|
1581
|
+
align-self: start;
|
|
1582
|
+
z-index: 10;
|
|
1583
|
+
min-inline-size: 0;
|
|
1584
|
+
transition: transform var(--smrt-admin-shell-chrome-duration)
|
|
1585
|
+
var(--smrt-easing-standard, ease);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.smrt-admin-shell[data-chrome-hidden] .smrt-admin-shell__phone-top {
|
|
1589
|
+
transform: translateY(-100%);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.smrt-admin-shell[data-phone-top] .smrt-admin-shell__main {
|
|
1593
|
+
padding-block-start: var(--smrt-admin-shell-phone-top-size);
|
|
1594
|
+
scroll-padding-block-start: var(--smrt-admin-shell-phone-top-size);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.smrt-admin-shell__phone-bar {
|
|
1598
|
+
grid-column: 1 / -1;
|
|
1599
|
+
grid-row: 5;
|
|
1600
|
+
z-index: 30;
|
|
1601
|
+
min-inline-size: 0;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
.smrt-admin-shell__overlays {
|
|
1605
|
+
position: relative;
|
|
1606
|
+
grid-column: 1 / -1;
|
|
1607
|
+
grid-row: 1 / 5;
|
|
1608
|
+
z-index: 35;
|
|
1609
|
+
min-inline-size: 0;
|
|
1610
|
+
min-block-size: 0;
|
|
1611
|
+
pointer-events: none;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.smrt-admin-shell__overlays > :global(*) {
|
|
1615
|
+
pointer-events: auto;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
.smrt-admin-shell__scrim {
|
|
1619
|
+
grid-column: 1 / -1;
|
|
1620
|
+
grid-row: 1 / 5;
|
|
1621
|
+
z-index: 19;
|
|
1622
|
+
margin: 0;
|
|
1623
|
+
padding: 0;
|
|
1624
|
+
border: 0;
|
|
1625
|
+
background: var(--smrt-color-scrim);
|
|
1626
|
+
opacity: 0.36;
|
|
1627
|
+
cursor: pointer;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/* Tablet/desktop overlay: an expanded side edge whose `overlayMedia`
|
|
1631
|
+
matches slides over the page instead of taking a grid track. */
|
|
1632
|
+
.smrt-admin-shell__edge--left[data-presentation='overlay'][data-state='expanded'],
|
|
1633
|
+
.smrt-admin-shell__edge--right[data-presentation='overlay'][data-state='expanded'] {
|
|
1634
|
+
grid-column: 1 / -1;
|
|
1635
|
+
z-index: 25;
|
|
1636
|
+
box-shadow: var(
|
|
1637
|
+
--smrt-elevation-3,
|
|
1638
|
+
0 0.5rem 1.5rem color-mix(in srgb, var(--smrt-color-shadow, #000) 24%, transparent)
|
|
1639
|
+
);
|
|
1640
|
+
outline: none;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.smrt-admin-shell__edge--left[data-presentation='overlay'][data-state='expanded'] {
|
|
1644
|
+
justify-self: start;
|
|
1645
|
+
inline-size: min(var(--smrt-admin-shell-left-expanded), 100%);
|
|
1646
|
+
animation: smrt-admin-shell-slide-from-left
|
|
1647
|
+
var(--smrt-admin-shell-chrome-duration) ease-out;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.smrt-admin-shell__edge--right[data-presentation='overlay'][data-state='expanded'] {
|
|
1651
|
+
justify-self: end;
|
|
1652
|
+
inline-size: min(var(--smrt-admin-shell-right-expanded), 100%);
|
|
1653
|
+
animation: smrt-admin-shell-slide-from-right
|
|
1654
|
+
var(--smrt-admin-shell-chrome-duration) ease-out;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.smrt-admin-shell__overlay-scrim {
|
|
1658
|
+
grid-column: 1 / -1;
|
|
1659
|
+
grid-row: 3;
|
|
1660
|
+
z-index: 22;
|
|
1661
|
+
margin: 0;
|
|
1662
|
+
padding: 0;
|
|
1663
|
+
border: 0;
|
|
1664
|
+
background: var(--smrt-color-scrim);
|
|
1665
|
+
opacity: 0.24;
|
|
1666
|
+
cursor: pointer;
|
|
1667
|
+
animation: smrt-admin-shell-scrim-in var(--smrt-admin-shell-chrome-duration)
|
|
1668
|
+
ease-out;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.smrt-admin-shell__edge--left[data-presentation='overlay'][data-closing] {
|
|
1672
|
+
animation: smrt-admin-shell-slide-to-left
|
|
1673
|
+
var(--smrt-admin-shell-chrome-duration) ease-in forwards;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.smrt-admin-shell__edge--right[data-presentation='overlay'][data-closing] {
|
|
1677
|
+
animation: smrt-admin-shell-slide-to-right
|
|
1678
|
+
var(--smrt-admin-shell-chrome-duration) ease-in forwards;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.smrt-admin-shell__overlay-scrim[data-closing] {
|
|
1682
|
+
pointer-events: none;
|
|
1683
|
+
animation: smrt-admin-shell-scrim-out
|
|
1684
|
+
var(--smrt-admin-shell-chrome-duration) ease-in forwards;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
@keyframes smrt-admin-shell-slide-to-right {
|
|
1688
|
+
to {
|
|
1689
|
+
transform: translateX(100%);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
@keyframes smrt-admin-shell-slide-to-left {
|
|
1694
|
+
to {
|
|
1695
|
+
transform: translateX(-100%);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
@keyframes smrt-admin-shell-scrim-out {
|
|
1700
|
+
to {
|
|
1701
|
+
opacity: 0;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
@keyframes smrt-admin-shell-slide-from-right {
|
|
1706
|
+
from {
|
|
1707
|
+
transform: translateX(100%);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
@keyframes smrt-admin-shell-slide-from-left {
|
|
1712
|
+
from {
|
|
1713
|
+
transform: translateX(-100%);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
@keyframes smrt-admin-shell-scrim-in {
|
|
1718
|
+
from {
|
|
1719
|
+
opacity: 0;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1724
|
+
.smrt-admin-shell__edge--left[data-presentation='overlay'][data-state='expanded'],
|
|
1725
|
+
.smrt-admin-shell__edge--right[data-presentation='overlay'][data-state='expanded'],
|
|
1726
|
+
.smrt-admin-shell__edge[data-presentation='overlay'][data-closing],
|
|
1727
|
+
.smrt-admin-shell__overlay-scrim {
|
|
1728
|
+
animation: none;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.smrt-admin-shell__sheet-grabber {
|
|
1733
|
+
display: grid;
|
|
1734
|
+
place-items: center;
|
|
1735
|
+
block-size: 1.5rem;
|
|
1736
|
+
touch-action: pan-x;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.smrt-admin-shell__sheet-grabber span {
|
|
1740
|
+
inline-size: 2.5rem;
|
|
1741
|
+
block-size: 0.25rem;
|
|
1742
|
+
border-radius: var(--smrt-radius-full);
|
|
1743
|
+
background: var(--smrt-color-outline-variant);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/* Phone page contracts: breadcrumbs give way to the top bar, and so does
|
|
1747
|
+
the page title (visually hidden: screen readers and the outline keep
|
|
1748
|
+
it); page tabs stick under it and slide away with it; a form's action
|
|
1749
|
+
row is fixed to the bottom and replaces the bottom bar. */
|
|
1750
|
+
.smrt-admin-shell[data-viewport='phone'] :global([data-shell-breadcrumbs]) {
|
|
1751
|
+
display: none;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
.smrt-admin-shell[data-phone-top] :global([data-shell-page-title]) {
|
|
1755
|
+
position: absolute;
|
|
1756
|
+
inline-size: 1px;
|
|
1757
|
+
block-size: 1px;
|
|
1758
|
+
margin: -1px;
|
|
1759
|
+
padding: 0;
|
|
1760
|
+
overflow: hidden;
|
|
1761
|
+
clip-path: inset(50%);
|
|
1762
|
+
white-space: nowrap;
|
|
1763
|
+
border: 0;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/* Tabs inside a page header (a content item's tabs under its title) stick
|
|
1767
|
+
like any page tabs: the header's boxes step aside on phones so the tabs'
|
|
1768
|
+
sticky range is the page, not the header. */
|
|
1769
|
+
.smrt-admin-shell[data-phone-top]
|
|
1770
|
+
:global([data-page-header]:has([data-shell-tabs])),
|
|
1771
|
+
.smrt-admin-shell[data-phone-top]
|
|
1772
|
+
:global([data-page-header-extra]:has([data-shell-tabs])) {
|
|
1773
|
+
display: contents;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
.smrt-admin-shell[data-phone-top] :global([data-shell-tabs]) {
|
|
1777
|
+
position: sticky;
|
|
1778
|
+
inset-block-start: var(--smrt-admin-shell-phone-top-size);
|
|
1779
|
+
z-index: 6;
|
|
1780
|
+
background: var(--smrt-color-surface);
|
|
1781
|
+
transition: transform var(--smrt-admin-shell-chrome-duration)
|
|
1782
|
+
var(--smrt-easing-standard, ease);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
.smrt-admin-shell[data-phone-top][data-chrome-hidden]
|
|
1786
|
+
:global([data-shell-tabs]) {
|
|
1787
|
+
transform: translateY(
|
|
1788
|
+
calc(-100% - var(--smrt-admin-shell-phone-top-size))
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
.smrt-admin-shell[data-viewport='phone'] :global([data-form-action-bar]) {
|
|
1793
|
+
position: fixed;
|
|
1794
|
+
inset: auto 0 0;
|
|
1795
|
+
z-index: 12;
|
|
1796
|
+
display: flex;
|
|
1797
|
+
flex-flow: row nowrap;
|
|
1798
|
+
align-items: center;
|
|
1799
|
+
gap: var(--smrt-spacing-2);
|
|
1800
|
+
margin: 0;
|
|
1801
|
+
padding: var(--smrt-spacing-2) var(--smrt-spacing-4)
|
|
1802
|
+
calc(var(--smrt-spacing-2) + env(safe-area-inset-bottom));
|
|
1803
|
+
border-block-start: 1px solid var(--smrt-color-outline-variant);
|
|
1804
|
+
background: var(--smrt-color-surface);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
.smrt-admin-shell[data-viewport='phone'] :global([data-form-action-bar] > *) {
|
|
1808
|
+
flex: 1 1 0;
|
|
1809
|
+
min-block-size: 2.75rem;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
:global(:root[data-keyboard-open])
|
|
1813
|
+
.smrt-admin-shell[data-viewport='phone']
|
|
1814
|
+
:global([data-form-action-bar]) {
|
|
1815
|
+
display: none;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.smrt-admin-shell[data-bottom-bar='form'] .smrt-admin-shell__main {
|
|
1819
|
+
padding-block-end: calc(5rem + env(safe-area-inset-bottom));
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* The phone bottom bar sits at the bottom of the dynamic viewport so the
|
|
1823
|
+
browser's collapsing toolbar never hides it. */
|
|
1824
|
+
.smrt-admin-shell[data-bottom-bar='nav'],
|
|
1825
|
+
.smrt-admin-shell[data-bottom-bar='form'] {
|
|
1826
|
+
min-block-size: 100dvh;
|
|
1827
|
+
block-size: 100dvh;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
851
1830
|
@media (max-width: 48rem) {
|
|
852
1831
|
.smrt-admin-shell {
|
|
853
1832
|
grid-template-columns: 0 minmax(0, 1fr) 0;
|
|
854
1833
|
grid-template-rows:
|
|
1834
|
+
var(--smrt-admin-shell-header-track)
|
|
855
1835
|
var(--smrt-admin-shell-top-track) minmax(0, 1fr)
|
|
856
|
-
var(--smrt-admin-shell-bottom-track)
|
|
1836
|
+
var(--smrt-admin-shell-bottom-track)
|
|
1837
|
+
var(--smrt-admin-shell-phone-bar-track);
|
|
857
1838
|
}
|
|
858
1839
|
|
|
1840
|
+
.smrt-admin-shell__resizer {
|
|
1841
|
+
display: none;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
.smrt-admin-shell__tenant-opener { display: block; }
|
|
1845
|
+
.smrt-admin-shell__edge--left:not([data-state='expanded']),
|
|
1846
|
+
.smrt-admin-shell__edge--right:not([data-state='expanded']) { visibility: hidden; }
|
|
859
1847
|
.smrt-admin-shell__edge--left,
|
|
860
1848
|
.smrt-admin-shell__edge--right {
|
|
861
1849
|
position: absolute;
|
|
862
1850
|
grid-area: auto;
|
|
863
|
-
inset-block:
|
|
864
|
-
|
|
1851
|
+
inset-block: calc(
|
|
1852
|
+
var(--smrt-admin-shell-header-track) +
|
|
1853
|
+
var(--smrt-admin-shell-top-track)
|
|
1854
|
+
)
|
|
1855
|
+
calc(
|
|
1856
|
+
var(--smrt-admin-shell-bottom-track) +
|
|
1857
|
+
var(--smrt-admin-shell-phone-bar-track)
|
|
1858
|
+
);
|
|
865
1859
|
inline-size: min(22rem, 86vw);
|
|
866
1860
|
transform: translateX(-100%);
|
|
867
1861
|
transition: transform var(--smrt-duration-short2)
|
|
@@ -882,6 +1876,46 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
882
1876
|
transform: translateX(0);
|
|
883
1877
|
}
|
|
884
1878
|
|
|
1879
|
+
/* Right edge as a bottom sheet. */
|
|
1880
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'] {
|
|
1881
|
+
display: grid;
|
|
1882
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1883
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
1884
|
+
inset-inline: 0;
|
|
1885
|
+
inset-block-start: calc(
|
|
1886
|
+
var(--smrt-admin-shell-header-track) +
|
|
1887
|
+
var(--smrt-admin-shell-top-track) + var(--smrt-spacing-8)
|
|
1888
|
+
);
|
|
1889
|
+
inline-size: auto;
|
|
1890
|
+
border-inline-start: 0;
|
|
1891
|
+
border-start-start-radius: var(--smrt-radius-lg);
|
|
1892
|
+
border-start-end-radius: var(--smrt-radius-lg);
|
|
1893
|
+
background: var(--smrt-color-surface);
|
|
1894
|
+
box-shadow: var(--smrt-elevation-3);
|
|
1895
|
+
transform: translateY(100%);
|
|
1896
|
+
visibility: hidden;
|
|
1897
|
+
transition:
|
|
1898
|
+
transform var(--smrt-duration-short2) var(--smrt-easing-standard),
|
|
1899
|
+
visibility 0s linear var(--smrt-duration-short2);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'][data-state='expanded'] {
|
|
1903
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1904
|
+
transform: none;
|
|
1905
|
+
visibility: visible;
|
|
1906
|
+
transition: transform var(--smrt-duration-short2)
|
|
1907
|
+
var(--smrt-easing-standard);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'] .smrt-admin-shell__rail {
|
|
1911
|
+
display: none;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'] .smrt-admin-shell__panel--right {
|
|
1915
|
+
grid-column: 1;
|
|
1916
|
+
grid-row: 2;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
885
1919
|
.smrt-admin-shell__edge--top,
|
|
886
1920
|
.smrt-admin-shell__edge--bottom {
|
|
887
1921
|
grid-template-columns: minmax(0, 1fr);
|
|
@@ -917,8 +1951,14 @@ function buildLayoutStyle(shell: ModuleShellState): string {
|
|
|
917
1951
|
}
|
|
918
1952
|
|
|
919
1953
|
@media (prefers-reduced-motion: reduce) {
|
|
1954
|
+
.smrt-admin-shell {
|
|
1955
|
+
--smrt-admin-shell-chrome-duration: 0ms;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
920
1958
|
.smrt-admin-shell__edge--left,
|
|
921
|
-
.smrt-admin-shell__edge--right
|
|
1959
|
+
.smrt-admin-shell__edge--right,
|
|
1960
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'],
|
|
1961
|
+
.smrt-admin-shell__edge--right[data-phone='sheet'][data-state='expanded'] {
|
|
922
1962
|
transition: none;
|
|
923
1963
|
}
|
|
924
1964
|
|