@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.
- package/CHANGELOG.md +292 -0
- package/README.md +119 -0
- package/chat/chat-shell/chat-shell.a2ui.json +149 -0
- package/chat/chat-shell/chat-shell.css +10 -0
- package/chat/chat-shell/chat-shell.js +297 -0
- package/chat/chat-shell/chat-shell.yaml +119 -0
- package/chat/chat-shell/css/chat-shell.empty.css +12 -0
- package/chat/chat-shell/css/chat-shell.layout.css +60 -0
- package/chat/chat-shell/css/chat-shell.markdown.css +74 -0
- package/chat/chat-shell/css/chat-shell.messages.css +87 -0
- package/chat/chat-shell/css/chat-shell.streaming.css +30 -0
- package/chat/chat-shell/css/chat-shell.tokens.css +95 -0
- package/chat/index.js +1 -0
- package/editor/editor-shell/css/editor-shell.layout.css +171 -0
- package/editor/editor-shell/css/editor-shell.tokens.css +28 -0
- package/editor/editor-shell/editor-shell.a2ui.json +73 -0
- package/editor/editor-shell/editor-shell.css +6 -0
- package/editor/editor-shell/editor-shell.js +56 -0
- package/editor/editor-shell/editor-shell.yaml +59 -0
- package/editor/index.js +1 -0
- package/index.js +14 -0
- package/package.json +48 -0
- package/runtime/a2ui-root/a2ui-root.a2ui.json +125 -0
- package/runtime/a2ui-root/a2ui-root.js +191 -0
- package/runtime/a2ui-root/a2ui-root.yaml +87 -0
- package/runtime/gen-root/gen-root.a2ui.json +72 -0
- package/runtime/gen-root/gen-root.css +83 -0
- package/runtime/gen-root/gen-root.js +136 -0
- package/runtime/gen-root/gen-root.yaml +43 -0
- package/runtime/index.js +2 -0
- package/shell/admin-shell/admin-shell.a2ui.json +129 -0
- package/shell/admin-shell/admin-shell.css +14 -0
- package/shell/admin-shell/admin-shell.js +261 -0
- package/shell/admin-shell/admin-shell.yaml +89 -0
- package/shell/admin-shell/css/admin-shell.collapsed.css +86 -0
- package/shell/admin-shell/css/admin-shell.helpers.css +42 -0
- package/shell/admin-shell/css/admin-shell.main.css +182 -0
- package/shell/admin-shell/css/admin-shell.shell.css +48 -0
- package/shell/admin-shell/css/admin-shell.sidebar.css +165 -0
- package/shell/admin-shell/css/admin-shell.templates.css +215 -0
- package/shell/admin-shell/css/admin-shell.tokens.css +119 -0
- 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
|
+
}
|