@adia-ai/web-modules 0.0.4

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +119 -0
  3. package/chat/chat-shell/chat-shell.a2ui.json +149 -0
  4. package/chat/chat-shell/chat-shell.css +10 -0
  5. package/chat/chat-shell/chat-shell.js +297 -0
  6. package/chat/chat-shell/chat-shell.yaml +119 -0
  7. package/chat/chat-shell/css/chat-shell.empty.css +12 -0
  8. package/chat/chat-shell/css/chat-shell.layout.css +60 -0
  9. package/chat/chat-shell/css/chat-shell.markdown.css +74 -0
  10. package/chat/chat-shell/css/chat-shell.messages.css +87 -0
  11. package/chat/chat-shell/css/chat-shell.streaming.css +30 -0
  12. package/chat/chat-shell/css/chat-shell.tokens.css +95 -0
  13. package/chat/index.js +1 -0
  14. package/editor/editor-shell/css/editor-shell.layout.css +171 -0
  15. package/editor/editor-shell/css/editor-shell.tokens.css +28 -0
  16. package/editor/editor-shell/editor-shell.a2ui.json +73 -0
  17. package/editor/editor-shell/editor-shell.css +6 -0
  18. package/editor/editor-shell/editor-shell.js +56 -0
  19. package/editor/editor-shell/editor-shell.yaml +59 -0
  20. package/editor/index.js +1 -0
  21. package/index.js +14 -0
  22. package/package.json +48 -0
  23. package/runtime/a2ui-root/a2ui-root.a2ui.json +125 -0
  24. package/runtime/a2ui-root/a2ui-root.js +191 -0
  25. package/runtime/a2ui-root/a2ui-root.yaml +87 -0
  26. package/runtime/gen-root/gen-root.a2ui.json +72 -0
  27. package/runtime/gen-root/gen-root.css +83 -0
  28. package/runtime/gen-root/gen-root.js +136 -0
  29. package/runtime/gen-root/gen-root.yaml +43 -0
  30. package/runtime/index.js +2 -0
  31. package/shell/admin-shell/admin-shell.a2ui.json +129 -0
  32. package/shell/admin-shell/admin-shell.css +14 -0
  33. package/shell/admin-shell/admin-shell.js +261 -0
  34. package/shell/admin-shell/admin-shell.yaml +89 -0
  35. package/shell/admin-shell/css/admin-shell.collapsed.css +86 -0
  36. package/shell/admin-shell/css/admin-shell.helpers.css +42 -0
  37. package/shell/admin-shell/css/admin-shell.main.css +182 -0
  38. package/shell/admin-shell/css/admin-shell.shell.css +48 -0
  39. package/shell/admin-shell/css/admin-shell.sidebar.css +165 -0
  40. package/shell/admin-shell/css/admin-shell.templates.css +215 -0
  41. package/shell/admin-shell/css/admin-shell.tokens.css +119 -0
  42. package/shell/index.js +1 -0
@@ -0,0 +1,129 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/AppShell.json",
4
+ "title": "AppShell",
5
+ "description": "Behavior-only application shell. Wires sidebar toggles, resize handles,\na Cmd+K command palette, and a ResizeObserver that drives responsive\nsidebar collapse. Author supplies the DOM (aside[data-sidebar], main,\ndialog[data-command]); admin-shell binds the interactions.\n",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "AppShell"
18
+ },
19
+ "mode": {
20
+ "description": "Layout variant — default is bordered surface; rounded softens edges; borderless removes the outer chrome.",
21
+ "type": "string",
22
+ "enum": [
23
+ "",
24
+ "rounded",
25
+ "borderless"
26
+ ],
27
+ "default": ""
28
+ }
29
+ },
30
+ "required": [
31
+ "component"
32
+ ],
33
+ "unevaluatedProperties": false,
34
+ "x-adiaui": {
35
+ "anti_patterns": [],
36
+ "category": "layout",
37
+ "events": {
38
+ "command-select": {
39
+ "description": "Forwarded from the command palette when an option is chosen.",
40
+ "detail": {
41
+ "value": "string"
42
+ }
43
+ },
44
+ "sidebar-resize": {
45
+ "description": "Fired as a sidebar is dragged; debounced on the trailing edge.",
46
+ "detail": {
47
+ "sidebar": "string",
48
+ "width": "number"
49
+ }
50
+ },
51
+ "sidebar-toggle": {
52
+ "description": "Fired when a sidebar is collapsed or expanded.",
53
+ "detail": {
54
+ "expanded": "boolean",
55
+ "sidebar": "string"
56
+ }
57
+ }
58
+ },
59
+ "examples": [],
60
+ "keywords": [
61
+ "app-shell",
62
+ "shell",
63
+ "layout",
64
+ "admin",
65
+ "dashboard",
66
+ "sidebar",
67
+ "nav"
68
+ ],
69
+ "name": "AdminShell",
70
+ "related": [
71
+ "Nav",
72
+ "NavGroup",
73
+ "NavItem",
74
+ "Command"
75
+ ],
76
+ "slots": {
77
+ "description": {
78
+ "description": "Secondary metadata inside any chrome bar. Muted + --a-ui-sm size."
79
+ },
80
+ "default": {
81
+ "description": "Author-supplied page DOM. Expected structure — aside[data-sidebar] for navigation, main for content, optional dialog[data-command] for Cmd+K."
82
+ },
83
+ "action": {
84
+ "description": "Trailing control cluster inside any chrome bar. The first [slot=\"action\"] child pushes itself (and siblings) to the end; subsequent siblings flow with gap. Coexists with legacy <span data-spacer> / <div data-actions> hooks for one release — new code should prefer slots."
85
+ },
86
+ "action-leading": {
87
+ "description": "Leading (inline-start) control cluster inside any chrome bar. Pairs with [slot=\"action\"] for dual-cluster chrome (e.g. back button + breadcrumb on the left, primary actions on the right). Replaces the legacy <span data-spacer> hack."
88
+ },
89
+ "heading": {
90
+ "description": "Primary label inside any chrome bar. Medium-weight + strong fg."
91
+ },
92
+ "icon": {
93
+ "description": "Leading glyph inside any chrome bar — > main > header / footer and [data-sidebar] > header / footer. Muted color, flex-align."
94
+ }
95
+ },
96
+ "states": [
97
+ {
98
+ "description": "Default, interactive shell.",
99
+ "name": "idle"
100
+ },
101
+ {
102
+ "description": "Leading sidebar is collapsed; content expands.",
103
+ "attribute": "data-sidebar-leading-collapsed",
104
+ "name": "collapsed-leading"
105
+ },
106
+ {
107
+ "description": "Trailing sidebar (inspector) is collapsed.",
108
+ "attribute": "data-sidebar-trailing-collapsed",
109
+ "name": "collapsed-trailing"
110
+ }
111
+ ],
112
+ "synonyms": {
113
+ "admin": [
114
+ "dashboard",
115
+ "shell",
116
+ "app-shell"
117
+ ],
118
+ "dashboard": [
119
+ "admin",
120
+ "shell",
121
+ "app-shell"
122
+ ]
123
+ },
124
+ "tag": "admin-shell",
125
+ "tokens": {},
126
+ "traits": [],
127
+ "version": 1
128
+ }
129
+ }
@@ -0,0 +1,14 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ admin-shell — Module CSS (admin app layout shell)
3
+
4
+ Split into constituent parts for maintainability.
5
+ Import order matters: tokens first, then structure, then overrides.
6
+ ═══════════════════════════════════════════════════════════════ */
7
+
8
+ @import "./css/admin-shell.tokens.css";
9
+ @import "./css/admin-shell.shell.css";
10
+ @import "./css/admin-shell.main.css";
11
+ @import "./css/admin-shell.sidebar.css";
12
+ @import "./css/admin-shell.collapsed.css";
13
+ @import "./css/admin-shell.templates.css";
14
+ @import "./css/admin-shell.helpers.css";
@@ -0,0 +1,261 @@
1
+ /**
2
+ * <admin-shell mode="rounded borderless">
3
+ * <aside data-sidebar="leading">...</aside> (or <aside-ui slot="leading">)
4
+ * <main>...</main>
5
+ * <aside data-sidebar="trailing">...</aside> (or <aside-ui slot="trailing">)
6
+ * <dialog data-command>...</dialog>
7
+ * </admin-shell>
8
+ *
9
+ * Behavior-only app shell. Stamps no HTML — the page author writes the
10
+ * structure using semantic elements + data attributes. Both the legacy
11
+ * `aside[data-sidebar]` and the slot-vocabulary `aside-ui[slot]`
12
+ * authoring shapes are recognised; #sidebarName() reads from either.
13
+ * The component auto-wires four JS behaviors that CSS can't handle:
14
+ *
15
+ * 1. Sidebar resize (drag handle with snap thresholds)
16
+ * 2. Sidebar toggle (collapse/restore width)
17
+ * 3. Command palette (Cmd+K keyboard shortcut)
18
+ * 4. ResizeObserver (select placement in narrow sidebars)
19
+ *
20
+ * CSS handles everything else: layout, collapse, container queries,
21
+ * sticky headers, scroll containment, mode composition.
22
+ */
23
+
24
+ import { UIElement } from '../../../web-components/core/element.js';
25
+
26
+ const SNAP_THRESHOLD = 96;
27
+ const SNAP_MIN_USABLE = 160;
28
+
29
+ class AdminShell extends UIElement {
30
+ static properties = {
31
+ mode: { type: String, default: '', reflect: true },
32
+ };
33
+
34
+ static template = () => null;
35
+
36
+ #sidebarWidths = new Map();
37
+ #resizeCleanups = [];
38
+ #sidebarRO = null;
39
+ #cmdKeyHandler = null;
40
+
41
+ connected() {
42
+ this.#setupToggles();
43
+ this.#setupResizeHandles();
44
+ this.#setupCommandPalette();
45
+ this.#setupResizeObserver();
46
+ }
47
+
48
+ disconnected() {
49
+ // Clean up resize handles
50
+ for (const cleanup of this.#resizeCleanups) cleanup();
51
+ this.#resizeCleanups = [];
52
+
53
+ // Clean up ResizeObserver
54
+ this.#sidebarRO?.disconnect();
55
+ this.#sidebarRO = null;
56
+
57
+ // Clean up Cmd+K
58
+ if (this.#cmdKeyHandler) {
59
+ document.removeEventListener('keydown', this.#cmdKeyHandler);
60
+ this.#cmdKeyHandler = null;
61
+ }
62
+ }
63
+
64
+ // ── Sidebar persistence ────────────────────────────────────
65
+
66
+ // Selector that matches both legacy raw-HTML and slot-vocabulary forms.
67
+ static #SIDEBAR_SEL = ':is([data-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"])';
68
+
69
+ // Helper: read sidebar name from either authoring shape.
70
+ #sidebarName(el) {
71
+ return el.getAttribute('data-sidebar') ?? el.getAttribute('slot');
72
+ }
73
+
74
+ #persistSidebar(sidebarName, width) {
75
+ try { localStorage.setItem(`adia-sidebar-${sidebarName}`, width); } catch {}
76
+ }
77
+
78
+ #restoreSidebars() {
79
+ for (const sidebar of this.querySelectorAll(AdminShell.#SIDEBAR_SEL)) {
80
+ const name = this.#sidebarName(sidebar);
81
+ try {
82
+ const saved = localStorage.getItem(`adia-sidebar-${name}`);
83
+ if (saved) {
84
+ sidebar.style.width = saved;
85
+ // Only store as the "previous expanded width" if it's actually expanded.
86
+ // If collapsed, keep the default expanded width so toggle can restore it.
87
+ const w = parseFloat(saved);
88
+ if (isNaN(w) || w > SNAP_THRESHOLD) {
89
+ this.#sidebarWidths.set(name, saved);
90
+ }
91
+ }
92
+ } catch {}
93
+ }
94
+ }
95
+
96
+ // ── 1. Sidebar toggle ──────────────────────────────────────
97
+
98
+ #setupToggles() {
99
+ this.#restoreSidebars();
100
+
101
+ for (const btn of this.querySelectorAll('[data-sidebar-toggle]')) {
102
+ const sidebarName = btn.getAttribute('data-sidebar-toggle');
103
+ btn.addEventListener('click', () => {
104
+ const sidebar = this.querySelector(`:is([data-sidebar="${sidebarName}"], aside-ui[slot="${sidebarName}"])`);
105
+ if (!sidebar) return;
106
+
107
+ const isCollapsed = sidebar.getBoundingClientRect().width <= SNAP_THRESHOLD;
108
+
109
+ if (isCollapsed) {
110
+ // Expand: restore previous width
111
+ const prev = this.#sidebarWidths.get(sidebarName);
112
+ sidebar.style.width = prev || '';
113
+ this.#persistSidebar(sidebarName, prev || '');
114
+ } else {
115
+ // Collapse: save current width, set to min
116
+ this.#sidebarWidths.set(sidebarName, sidebar.style.width || getComputedStyle(sidebar).width);
117
+ const minW = getComputedStyle(sidebar).minWidth;
118
+ sidebar.style.width = minW;
119
+ this.#persistSidebar(sidebarName, minW);
120
+ }
121
+
122
+ this.dispatchEvent(new CustomEvent('sidebar-toggle', {
123
+ bubbles: true,
124
+ detail: { sidebar: sidebarName, expanded: !isCollapsed },
125
+ }));
126
+ });
127
+ }
128
+ }
129
+
130
+ // ── 2. Sidebar resize handles ──────────────────────────────
131
+
132
+ #setupResizeHandles() {
133
+ for (const handle of this.querySelectorAll(`${AdminShell.#SIDEBAR_SEL} > [data-resize]`)) {
134
+ const sidebar = handle.parentElement;
135
+ const sidebarName = this.#sidebarName(sidebar);
136
+ const isLeading = sidebarName === 'leading';
137
+
138
+ const onPointerDown = (e) => {
139
+ e.preventDefault();
140
+ handle.setPointerCapture(e.pointerId);
141
+ const startX = e.clientX;
142
+ const startW = sidebar.getBoundingClientRect().width;
143
+ sidebar.setAttribute('data-resizing', '');
144
+ document.documentElement.style.cursor = 'col-resize';
145
+
146
+ const onMove = (e) => {
147
+ const dx = e.clientX - startX;
148
+ const max = parseInt(getComputedStyle(sidebar).getPropertyValue('max-width')) || 480;
149
+ const w = Math.max(48, Math.min(max, startW + (isLeading ? dx : -dx)));
150
+ sidebar.style.width = `${w}px`;
151
+ };
152
+
153
+ const onUp = () => {
154
+ sidebar.removeAttribute('data-resizing');
155
+ document.documentElement.style.cursor = '';
156
+ handle.removeEventListener('pointermove', onMove);
157
+ handle.removeEventListener('pointerup', onUp);
158
+
159
+ // Snap logic
160
+ const w = sidebar.getBoundingClientRect().width;
161
+ if (w <= SNAP_THRESHOLD) {
162
+ sidebar.style.width = getComputedStyle(sidebar).minWidth;
163
+ } else if (w < SNAP_MIN_USABLE) {
164
+ sidebar.style.width = `${SNAP_MIN_USABLE}px`;
165
+ }
166
+
167
+ this.#persistSidebar(sidebarName, sidebar.style.width);
168
+ this.dispatchEvent(new CustomEvent('sidebar-resize', {
169
+ bubbles: true,
170
+ detail: { sidebar: sidebarName, width: sidebar.getBoundingClientRect().width },
171
+ }));
172
+ };
173
+
174
+ handle.addEventListener('pointermove', onMove);
175
+ handle.addEventListener('pointerup', onUp);
176
+ };
177
+
178
+ handle.addEventListener('pointerdown', onPointerDown);
179
+ this.#resizeCleanups.push(() => handle.removeEventListener('pointerdown', onPointerDown));
180
+ }
181
+ }
182
+
183
+ // ── 3. Command palette ─────────────────────────────────────
184
+
185
+ #setupCommandPalette() {
186
+ const dialog = this.querySelector('dialog[data-command]');
187
+ if (!dialog) return;
188
+
189
+ const cmdEl = dialog.querySelector('command-ui');
190
+ const nav = this.querySelector('nav-ui');
191
+
192
+ const openCmd = () => {
193
+ dialog.showModal();
194
+ if (cmdEl) { cmdEl.open = true; cmdEl.value = ''; cmdEl.focus(); }
195
+ };
196
+
197
+ const closeCmd = () => {
198
+ dialog.close();
199
+ if (cmdEl) cmdEl.open = false;
200
+ };
201
+
202
+ // Trigger elements
203
+ for (const trigger of this.querySelectorAll('[data-command-trigger]')) {
204
+ trigger.addEventListener('click', (e) => {
205
+ e.stopPropagation();
206
+ openCmd();
207
+ });
208
+ }
209
+
210
+ // Backdrop click closes
211
+ dialog.addEventListener('click', (e) => {
212
+ if (e.target === dialog) closeCmd();
213
+ });
214
+
215
+ // Command-n events
216
+ if (cmdEl) {
217
+ cmdEl.addEventListener('dismiss', closeCmd);
218
+ cmdEl.addEventListener('select', (e) => {
219
+ closeCmd();
220
+ if (nav) {
221
+ const item = nav.querySelector(`nav-item-ui[value="${e.detail.value}"]`);
222
+ if (item) nav.select(item);
223
+ }
224
+ this.dispatchEvent(new CustomEvent('command-select', {
225
+ bubbles: true,
226
+ detail: e.detail,
227
+ }));
228
+ });
229
+ }
230
+
231
+ // Cmd+K / Ctrl+K
232
+ this.#cmdKeyHandler = (e) => {
233
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
234
+ e.preventDefault();
235
+ dialog.open ? closeCmd() : openCmd();
236
+ }
237
+ };
238
+ document.addEventListener('keydown', this.#cmdKeyHandler);
239
+ }
240
+
241
+ // ── 4. ResizeObserver for select placement ─────────────────
242
+
243
+ #setupResizeObserver() {
244
+ this.#sidebarRO = new ResizeObserver((entries) => {
245
+ for (const entry of entries) {
246
+ const sidebar = entry.target;
247
+ const narrow = entry.contentBoxSize[0].inlineSize <= SNAP_THRESHOLD;
248
+ for (const sel of sidebar.querySelectorAll('select-ui')) {
249
+ sel.setAttribute('placement', narrow ? 'right' : 'bottom-start');
250
+ }
251
+ }
252
+ });
253
+
254
+ for (const sb of this.querySelectorAll(AdminShell.#SIDEBAR_SEL)) {
255
+ this.#sidebarRO.observe(sb);
256
+ }
257
+ }
258
+ }
259
+
260
+ customElements.define('admin-shell', AdminShell);
261
+ export { AdminShell };
@@ -0,0 +1,89 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: AdminShell
3
+ tag: admin-shell
4
+ component: AppShell
5
+ category: layout
6
+ version: 1
7
+ description: |
8
+ Behavior-only application shell. Wires sidebar toggles, resize handles,
9
+ a Cmd+K command palette, and a ResizeObserver that drives responsive
10
+ sidebar collapse. Author supplies the DOM (aside[data-sidebar], main,
11
+ dialog[data-command]); admin-shell binds the interactions.
12
+
13
+ props:
14
+ mode:
15
+ type: string
16
+ default: ""
17
+ enum: ["", rounded, borderless]
18
+ reflect: true
19
+ description: Layout variant — default is bordered surface; rounded softens edges; borderless removes the outer chrome.
20
+
21
+ events:
22
+ sidebar-toggle:
23
+ description: Fired when a sidebar is collapsed or expanded.
24
+ detail:
25
+ sidebar: string
26
+ expanded: boolean
27
+ sidebar-resize:
28
+ description: Fired as a sidebar is dragged; debounced on the trailing edge.
29
+ detail:
30
+ sidebar: string
31
+ width: number
32
+ command-select:
33
+ description: Forwarded from the command palette when an option is chosen.
34
+ detail:
35
+ value: string
36
+
37
+ slots:
38
+ default:
39
+ description: >-
40
+ Author-supplied page DOM. Expected structure — aside[data-sidebar] for
41
+ navigation, main for content, optional dialog[data-command] for Cmd+K.
42
+ icon:
43
+ description: >-
44
+ Leading glyph inside any chrome bar — > main > header / footer and
45
+ [data-sidebar] > header / footer. Muted color, flex-align.
46
+ heading:
47
+ description: >-
48
+ Primary label inside any chrome bar. Medium-weight + strong fg.
49
+ description:
50
+ description: >-
51
+ Secondary metadata inside any chrome bar. Muted + --a-ui-sm size.
52
+ action:
53
+ description: >-
54
+ Trailing control cluster inside any chrome bar. The first
55
+ [slot="action"] child pushes itself (and siblings) to the end;
56
+ subsequent siblings flow with gap. Coexists with legacy
57
+ <span data-spacer> / <div data-actions> hooks for one release —
58
+ new code should prefer slots.
59
+ action-leading:
60
+ description: >-
61
+ Leading (inline-start) control cluster inside any chrome bar.
62
+ Pairs with [slot="action"] for dual-cluster chrome (e.g. back
63
+ button + breadcrumb on the left, primary actions on the right).
64
+ Replaces the legacy <span data-spacer> hack.
65
+
66
+ states:
67
+ - name: idle
68
+ description: Default, interactive shell.
69
+ - name: collapsed-leading
70
+ attribute: data-sidebar-leading-collapsed
71
+ description: Leading sidebar is collapsed; content expands.
72
+ - name: collapsed-trailing
73
+ attribute: data-sidebar-trailing-collapsed
74
+ description: Trailing sidebar (inspector) is collapsed.
75
+
76
+ traits: []
77
+
78
+ a2ui:
79
+ rules:
80
+ - >-
81
+ admin-shell is a behavior wrapper; its children are native HTML landmarks
82
+ (aside, main, header). Don't wrap them in col-ui/row-ui — app-shell.css
83
+ handles grid layout based on [data-sidebar] attributes.
84
+
85
+ keywords: [app-shell, shell, layout, admin, dashboard, sidebar, nav]
86
+ synonyms:
87
+ admin: [dashboard, shell, app-shell]
88
+ dashboard: [admin, shell, app-shell]
89
+ related: [Nav, NavGroup, NavItem, Command]
@@ -0,0 +1,86 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ admin-shell — Sidebar collapsed state
3
+
4
+ @container sidebar (max-width: 96px)
5
+
6
+ All collapse behavior is CSS-driven via container queries.
7
+ JS only handles:
8
+ - Resize drag with snap (≤96px → 48px, 97-159px → 160px)
9
+ - Toggle button (sets width to min/restore)
10
+ - Select placement attribute (bottom-start → right)
11
+ - Nav-group popover-vs-toggle (checks width at click time)
12
+ ═══════════════════════════════════════════════════════════════ */
13
+
14
+ @container sidebar (max-width: 96px) {
15
+ /* Center header/footer content */
16
+ :is(header, header-ui, footer, footer-ui) {
17
+ justify-content: center;
18
+ padding: var(--page-sidebar-px);
19
+ }
20
+
21
+ /* Center section content */
22
+ :is(section, section-ui) {
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ }
27
+
28
+ /* Select: hide text + caret, keep leading icon/avatar */
29
+ select-ui [slot="display"],
30
+ select-ui [slot="caret"] {
31
+ display: none;
32
+ }
33
+
34
+ select-ui [slot="trigger"] {
35
+ justify-content: center;
36
+ padding: 0;
37
+ min-height: var(--page-header-height);
38
+ }
39
+
40
+ /* Select leading: bump icon/avatar size for collapsed state */
41
+ select-ui [slot="leading"] {
42
+ --a-icon-size: var(--page-sidebar-collapsed-icon);
43
+ font-size: var(--page-sidebar-collapsed-icon);
44
+ }
45
+
46
+ select-ui img[slot="leading"] {
47
+ width: var(--page-sidebar-collapsed-avatar);
48
+ height: var(--page-sidebar-collapsed-avatar);
49
+ }
50
+
51
+ /* Nav: hide all text/meta, show only icons */
52
+ nav-ui [slot="text"],
53
+ nav-ui [slot="badge"],
54
+ nav-ui [slot="caret"],
55
+ nav-ui [slot="trailing"],
56
+ nav-ui [data-nav-label],
57
+ nav-ui [data-nav-divider] {
58
+ display: none !important;
59
+ }
60
+
61
+ /* Nav items/group headers: center icon, ensure square hit area */
62
+ nav-group-ui [slot="header"] {
63
+ justify-content: center;
64
+ padding: 0;
65
+ min-height: var(--nav-group-row-height);
66
+ min-width: var(--nav-group-row-height);
67
+ }
68
+
69
+ nav-item-ui {
70
+ justify-content: center;
71
+ padding: 0;
72
+ min-height: var(--nav-item-row-height);
73
+ min-width: var(--nav-item-row-height);
74
+ }
75
+
76
+ /* Button: icon-only mode */
77
+ button-ui {
78
+ --button-px: 0;
79
+ width: auto !important;
80
+ justify-content: center;
81
+ }
82
+
83
+ button-ui [slot="trailing"] {
84
+ display: none;
85
+ }
86
+ }
@@ -0,0 +1,42 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ admin-shell — Layout helpers
3
+
4
+ Generic layout utilities used across admin pages.
5
+ ═══════════════════════════════════════════════════════════════ */
6
+
7
+ /* Flex spacer — pushes siblings to opposite ends */
8
+ [data-spacer] { flex: 1; }
9
+
10
+ /* Inline action group (buttons, icons) */
11
+ [data-actions] {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--page-actions-gap);
15
+ }
16
+
17
+ /* ── Form layout helpers ── */
18
+
19
+ /* 2-column grid (default), 3-column with data-grid="3" */
20
+ [data-grid] {
21
+ display: grid;
22
+ grid-template-columns: 1fr 1fr;
23
+ gap: var(--page-grid-gap);
24
+ }
25
+
26
+ [data-grid="3"] {
27
+ grid-template-columns: 1fr 1fr 1fr;
28
+ }
29
+
30
+ /* Vertical stack */
31
+ [data-col] {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--page-grid-gap);
35
+ }
36
+
37
+ /* Horizontal row */
38
+ [data-row] {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: var(--page-grid-gap);
42
+ }