@adia-ai/web-components 0.0.24 → 0.0.25
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/components/app-shell/app-shell.a2ui.json +136 -0
- package/components/app-shell/app-shell.css +16 -0
- package/components/app-shell/app-shell.js +202 -0
- package/components/app-shell/app-shell.yaml +183 -0
- package/components/aside/aside.a2ui.json +84 -0
- package/components/aside/aside.yaml +100 -0
- package/components/footer/footer.a2ui.json +1 -1
- package/components/footer/footer.yaml +1 -1
- package/components/header/header.a2ui.json +2 -2
- package/components/header/header.yaml +2 -2
- package/components/index.js +2 -0
- package/components/page/page.a2ui.json +107 -0
- package/components/page/page.css +68 -0
- package/components/page/page.js +88 -0
- package/components/page/page.yaml +148 -0
- package/components/section/section.a2ui.json +1 -1
- package/components/section/section.yaml +1 -1
- package/package.json +1 -1
- package/patterns/app-shell/app-shell.css +12 -0
- package/styles/components.css +2 -0
|
@@ -0,0 +1,136 @@
|
|
|
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": "Application shell layout. Holds a leading nav rail, a main column\n(topbar + scroll area + statusbar), an optional trailing inspector,\nand an optional command-palette `<dialog>`. The component wires\nsidebar toggle, sidebar resize, and Cmd+K shortcut on top of the\nlong-lived CSS pattern that already targets `app-shell-ui` as a tag.\nChildren use native HTML semantic elements (`<aside data-sidebar>`,\n`<main>`, `<header>`, `<section>`, `<footer>`); the existing\nCSS scopes by tag.\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
|
+
"cmdK": {
|
|
17
|
+
"description": "Installs a global Cmd+K / Ctrl+K listener that opens the inner\n`<dialog data-command>` via `showModal()`. No-op when no such\ndialog is present.\n",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
21
|
+
"component": {
|
|
22
|
+
"const": "AppShell"
|
|
23
|
+
},
|
|
24
|
+
"leadingCollapsed": {
|
|
25
|
+
"description": "Reflects to the leading aside's `[data-collapsed]`. Toggled\nprogrammatically (`shell.toggleLeading()`) or via a click on any\nelement with `[data-sidebar-toggle=\"leading\"]` inside the shell.\n",
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false
|
|
28
|
+
},
|
|
29
|
+
"leadingMaxWidth": {
|
|
30
|
+
"description": "Maximum leading-aside width (px) when resizing.",
|
|
31
|
+
"type": "number",
|
|
32
|
+
"default": 480
|
|
33
|
+
},
|
|
34
|
+
"leadingMinWidth": {
|
|
35
|
+
"description": "Minimum leading-aside width (px) when resizing.",
|
|
36
|
+
"type": "number",
|
|
37
|
+
"default": 48
|
|
38
|
+
},
|
|
39
|
+
"mode": {
|
|
40
|
+
"description": "Space-separated list of layout modes. `rounded` adds border-radius\nto the scroll section; `borderless` removes chrome borders (content\nborders persist). Pass both for a soft, edgeless shell.\n",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"default": ""
|
|
43
|
+
},
|
|
44
|
+
"trailingCollapsed": {
|
|
45
|
+
"description": "Reflects to the trailing aside's `[data-collapsed]`. Toggled via\n`shell.toggleTrailing()` or `[data-sidebar-toggle=\"trailing\"]`.\n",
|
|
46
|
+
"type": "boolean",
|
|
47
|
+
"default": false
|
|
48
|
+
},
|
|
49
|
+
"trailingMaxWidth": {
|
|
50
|
+
"description": "Maximum trailing-aside width (px) when resizing.",
|
|
51
|
+
"type": "number",
|
|
52
|
+
"default": 480
|
|
53
|
+
},
|
|
54
|
+
"trailingMinWidth": {
|
|
55
|
+
"description": "Minimum trailing-aside width (px) when resizing.",
|
|
56
|
+
"type": "number",
|
|
57
|
+
"default": 48
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"required": [
|
|
61
|
+
"component"
|
|
62
|
+
],
|
|
63
|
+
"unevaluatedProperties": false,
|
|
64
|
+
"x-adiaui": {
|
|
65
|
+
"anti_patterns": [],
|
|
66
|
+
"category": "container",
|
|
67
|
+
"events": {
|
|
68
|
+
"command-close": {
|
|
69
|
+
"description": "Fired after the command-palette `<dialog>` closes."
|
|
70
|
+
},
|
|
71
|
+
"command-open": {
|
|
72
|
+
"description": "Fired after the command-palette `<dialog>` opens."
|
|
73
|
+
},
|
|
74
|
+
"sidebar-toggle": {
|
|
75
|
+
"description": "Fired after a sidebar's collapsed state flips. Detail:\n`{ side: 'leading'|'trailing', collapsed: boolean }`.\n"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"examples": [
|
|
79
|
+
{
|
|
80
|
+
"description": "Documentation shell — leading nav, topbar with breadcrumb, scroll content, statusbar.",
|
|
81
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"AppShell\",\n \"mode\": \"rounded\",\n \"cmdK\": true,\n \"children\": [\"leading\", \"main\", \"cmd\"]\n },\n {\n \"id\": \"leading\",\n \"component\": \"Aside\",\n \"data-sidebar\": \"leading\",\n \"children\": [\"nav-hdr\", \"nav-list\", \"nav-ftr\"]\n },\n {\n \"id\": \"nav-hdr\",\n \"component\": \"Header\",\n \"children\": []\n },\n {\n \"id\": \"nav-list\",\n \"component\": \"Section\",\n \"children\": []\n },\n {\n \"id\": \"nav-ftr\",\n \"component\": \"Footer\",\n \"children\": []\n },\n {\n \"id\": \"main\",\n \"component\": \"Column\",\n \"children\": [\"topbar\", \"scroll\", \"statusbar\"]\n },\n {\n \"id\": \"topbar\",\n \"component\": \"Header\",\n \"children\": []\n },\n {\n \"id\": \"scroll\",\n \"component\": \"Section\",\n \"scroll\": true,\n \"children\": []\n },\n {\n \"id\": \"statusbar\",\n \"component\": \"Footer\",\n \"children\": []\n },\n {\n \"id\": \"cmd\",\n \"component\": \"Modal\",\n \"data-command\": true,\n \"children\": []\n }\n]",
|
|
82
|
+
"name": "docs-shell"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"keywords": [
|
|
86
|
+
"app-shell",
|
|
87
|
+
"shell",
|
|
88
|
+
"layout",
|
|
89
|
+
"sidebar",
|
|
90
|
+
"topbar",
|
|
91
|
+
"command-palette",
|
|
92
|
+
"cmdK",
|
|
93
|
+
"dashboard-shell"
|
|
94
|
+
],
|
|
95
|
+
"name": "AdiaAppShell",
|
|
96
|
+
"related": [
|
|
97
|
+
"aside",
|
|
98
|
+
"header",
|
|
99
|
+
"footer",
|
|
100
|
+
"section",
|
|
101
|
+
"drawer",
|
|
102
|
+
"modal",
|
|
103
|
+
"command",
|
|
104
|
+
"app-nav",
|
|
105
|
+
"section-nav"
|
|
106
|
+
],
|
|
107
|
+
"slots": {
|
|
108
|
+
"default": {
|
|
109
|
+
"description": "Composes from native HTML children: `<aside data-sidebar=\"leading\">`,\n`<main>` (with `<header>`, `<section>`, `<footer>` inside),\noptional `<aside data-sidebar=\"trailing\">`, optional\n`<dialog data-command>`. The existing pattern CSS scopes each\nchild by tag + attribute; no `slot=` attributes needed.\n"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"states": [
|
|
113
|
+
{
|
|
114
|
+
"description": "Default, ready for interaction.",
|
|
115
|
+
"name": "idle"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"description": "Leading aside collapsed via attribute / API.",
|
|
119
|
+
"name": "leading-collapsed"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"description": "Trailing aside collapsed via attribute / API.",
|
|
123
|
+
"name": "trailing-collapsed"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"description": "Command-palette dialog is showing.",
|
|
127
|
+
"name": "command-open"
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"synonyms": {},
|
|
131
|
+
"tag": "app-shell-ui",
|
|
132
|
+
"tokens": {},
|
|
133
|
+
"traits": [],
|
|
134
|
+
"version": 1
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
<app-shell-ui> component CSS
|
|
3
|
+
|
|
4
|
+
The substantive CSS lives in `patterns/app-shell/css/*.css` —
|
|
5
|
+
that surface predates this component and already targets
|
|
6
|
+
`app-shell-ui` as a tag. We re-import it here so consumers
|
|
7
|
+
importing this component's CSS get the full styling without
|
|
8
|
+
duplication. A future cleanup may invert the relationship
|
|
9
|
+
(move the CSS into this folder and have the pattern import
|
|
10
|
+
from here, or retire the pattern folder entirely once all
|
|
11
|
+
consumers have migrated to the component).
|
|
12
|
+
|
|
13
|
+
Companion ADR: .brain/adrs/0009-promote-app-shell-and-page-to-components.md
|
|
14
|
+
═══════════════════════════════════════════════════════════════ */
|
|
15
|
+
|
|
16
|
+
@import "../../patterns/app-shell/app-shell.css";
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <app-shell-ui> — Application shell layout component.
|
|
3
|
+
*
|
|
4
|
+
* Promotes the long-lived `patterns/app-shell/` CSS pattern (which already
|
|
5
|
+
* targeted `app-shell-ui` as a tag) to a real custom element. CSS continues
|
|
6
|
+
* to live in `patterns/app-shell/app-shell.css` (re-imported from this
|
|
7
|
+
* component's CSS); the JS layer adds:
|
|
8
|
+
*
|
|
9
|
+
* - sidebar toggle (delegated [data-sidebar-toggle="leading|trailing"])
|
|
10
|
+
* - sidebar resize (pointer drag on [data-resize])
|
|
11
|
+
* - command-palette wiring (delegated [data-command-trigger] +
|
|
12
|
+
* optional Cmd+K global shortcut when [cmd-k])
|
|
13
|
+
*
|
|
14
|
+
* Authoring shape (consumer markup, unchanged from the pre-existing pattern):
|
|
15
|
+
*
|
|
16
|
+
* <app-shell-ui mode="rounded borderless" cmd-k>
|
|
17
|
+
* <aside data-sidebar="leading"> <-- nav rail
|
|
18
|
+
* <header>...</header>
|
|
19
|
+
* <section>...</section>
|
|
20
|
+
* <footer>...</footer>
|
|
21
|
+
* <div data-resize></div>
|
|
22
|
+
* </aside>
|
|
23
|
+
* <main>
|
|
24
|
+
* <header>...</header> <-- topbar
|
|
25
|
+
* <section>...</section> <-- scroll area
|
|
26
|
+
* <footer>...</footer> <-- statusbar
|
|
27
|
+
* </main>
|
|
28
|
+
* <aside data-sidebar="trailing">...</aside> <!-- optional inspector -->
|
|
29
|
+
* <dialog data-command> <!-- optional cmd palette -->
|
|
30
|
+
* <command-ui>...</command-ui>
|
|
31
|
+
* </dialog>
|
|
32
|
+
* </app-shell-ui>
|
|
33
|
+
*
|
|
34
|
+
* Attributes:
|
|
35
|
+
* mode — space-separated list: "rounded" | "borderless" | both.
|
|
36
|
+
* leading-collapsed — boolean, reflects to leading aside's [data-collapsed].
|
|
37
|
+
* trailing-collapsed — boolean, reflects to trailing aside's [data-collapsed].
|
|
38
|
+
* cmd-k — boolean, installs Cmd+K / Ctrl+K global listener
|
|
39
|
+
* that opens the inner <dialog data-command> via
|
|
40
|
+
* showModal(). No-op when no such dialog is present.
|
|
41
|
+
* leading-min-width — minimum width when resizing leading sidebar (px).
|
|
42
|
+
* leading-max-width — maximum width (px).
|
|
43
|
+
* trailing-min-width / trailing-max-width — same for trailing.
|
|
44
|
+
*
|
|
45
|
+
* API:
|
|
46
|
+
* shell.toggleLeading() shell.toggleTrailing()
|
|
47
|
+
* shell.openCommand() shell.closeCommand()
|
|
48
|
+
*
|
|
49
|
+
* Events (bubbling):
|
|
50
|
+
* sidebar-toggle { detail: { side: 'leading'|'trailing', collapsed: boolean } }
|
|
51
|
+
* command-open no detail
|
|
52
|
+
* command-close no detail
|
|
53
|
+
*
|
|
54
|
+
* Spec: docs/specs/genui-multiturn-architecture.md (orthogonal — gen-UI uses
|
|
55
|
+
* this layer). ADR: .brain/adrs/0009-promote-app-shell-and-page-to-components.md.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { AdiaElement } from '../../core/element.js';
|
|
59
|
+
|
|
60
|
+
const DEFAULT_MIN_WIDTH = 48;
|
|
61
|
+
const DEFAULT_MAX_WIDTH = 480;
|
|
62
|
+
|
|
63
|
+
class AdiaAppShell extends AdiaElement {
|
|
64
|
+
static properties = {
|
|
65
|
+
mode: { type: String, default: '', reflect: true },
|
|
66
|
+
leadingCollapsed: { type: Boolean, default: false, attribute: 'leading-collapsed', reflect: true },
|
|
67
|
+
trailingCollapsed: { type: Boolean, default: false, attribute: 'trailing-collapsed', reflect: true },
|
|
68
|
+
cmdK: { type: Boolean, default: false, attribute: 'cmd-k', reflect: true },
|
|
69
|
+
leadingMinWidth: { type: Number, default: DEFAULT_MIN_WIDTH, attribute: 'leading-min-width' },
|
|
70
|
+
leadingMaxWidth: { type: Number, default: DEFAULT_MAX_WIDTH, attribute: 'leading-max-width' },
|
|
71
|
+
trailingMinWidth: { type: Number, default: DEFAULT_MIN_WIDTH, attribute: 'trailing-min-width' },
|
|
72
|
+
trailingMaxWidth: { type: Number, default: DEFAULT_MAX_WIDTH, attribute: 'trailing-max-width' },
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
static template = () => null;
|
|
76
|
+
|
|
77
|
+
#bound = false;
|
|
78
|
+
#drag = null; // { aside, startX, startW, side }
|
|
79
|
+
|
|
80
|
+
connected() {
|
|
81
|
+
if (!this.#bound) {
|
|
82
|
+
this.#bound = true;
|
|
83
|
+
this.addEventListener('click', this.#onClick);
|
|
84
|
+
this.addEventListener('pointerdown', this.#onPointerDown);
|
|
85
|
+
}
|
|
86
|
+
document.addEventListener('keydown', this.#onKeydown);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
disconnected() {
|
|
90
|
+
document.removeEventListener('keydown', this.#onKeydown);
|
|
91
|
+
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
92
|
+
document.removeEventListener('pointerup', this.#onResizeUp);
|
|
93
|
+
this.#drag = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render() {
|
|
97
|
+
const leading = this.querySelector(':scope > aside[data-sidebar="leading"]');
|
|
98
|
+
if (leading) leading.toggleAttribute('data-collapsed', !!this.leadingCollapsed);
|
|
99
|
+
const trailing = this.querySelector(':scope > aside[data-sidebar="trailing"]');
|
|
100
|
+
if (trailing) trailing.toggleAttribute('data-collapsed', !!this.trailingCollapsed);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── API ─────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
toggleLeading() {
|
|
106
|
+
this.leadingCollapsed = !this.leadingCollapsed;
|
|
107
|
+
this.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
108
|
+
bubbles: true,
|
|
109
|
+
detail: { side: 'leading', collapsed: this.leadingCollapsed },
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
toggleTrailing() {
|
|
114
|
+
this.trailingCollapsed = !this.trailingCollapsed;
|
|
115
|
+
this.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
116
|
+
bubbles: true,
|
|
117
|
+
detail: { side: 'trailing', collapsed: this.trailingCollapsed },
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
openCommand() {
|
|
122
|
+
const dialog = this.querySelector(':scope > dialog[data-command]');
|
|
123
|
+
if (!dialog || dialog.open) return;
|
|
124
|
+
dialog.showModal();
|
|
125
|
+
this.dispatchEvent(new CustomEvent('command-open', { bubbles: true }));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
closeCommand() {
|
|
129
|
+
const dialog = this.querySelector(':scope > dialog[data-command]');
|
|
130
|
+
if (!dialog || !dialog.open) return;
|
|
131
|
+
dialog.close();
|
|
132
|
+
this.dispatchEvent(new CustomEvent('command-close', { bubbles: true }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Delegated click ─────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
#onClick = (e) => {
|
|
138
|
+
const toggle = e.target.closest('[data-sidebar-toggle]');
|
|
139
|
+
if (toggle && this.contains(toggle)) {
|
|
140
|
+
const side = toggle.getAttribute('data-sidebar-toggle');
|
|
141
|
+
if (side === 'leading') { this.toggleLeading(); return; }
|
|
142
|
+
if (side === 'trailing') { this.toggleTrailing(); return; }
|
|
143
|
+
}
|
|
144
|
+
const trigger = e.target.closest('[data-command-trigger]');
|
|
145
|
+
if (trigger && this.contains(trigger)) {
|
|
146
|
+
this.openCommand();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// ── Cmd+K / Ctrl+K ──────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
#onKeydown = (e) => {
|
|
153
|
+
if (!this.cmdK) return;
|
|
154
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
this.openCommand();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// ── Resize handle ───────────────────────────────────────────────
|
|
161
|
+
// Pointerdown on a [data-resize] inside one of the asides starts a drag.
|
|
162
|
+
// Leading aside grows when dragged right; trailing aside grows when
|
|
163
|
+
// dragged left (mirrors `<pane-ui>[side]` semantics).
|
|
164
|
+
|
|
165
|
+
#onPointerDown = (e) => {
|
|
166
|
+
const handle = e.target.closest('[data-resize]');
|
|
167
|
+
if (!handle) return;
|
|
168
|
+
const aside = handle.closest(':scope > aside[data-sidebar]');
|
|
169
|
+
if (!aside || aside.parentElement !== this) return;
|
|
170
|
+
|
|
171
|
+
const side = aside.getAttribute('data-sidebar'); // 'leading' | 'trailing'
|
|
172
|
+
this.#drag = {
|
|
173
|
+
aside,
|
|
174
|
+
side,
|
|
175
|
+
startX: e.clientX,
|
|
176
|
+
startW: aside.getBoundingClientRect().width,
|
|
177
|
+
};
|
|
178
|
+
handle.setPointerCapture?.(e.pointerId);
|
|
179
|
+
document.addEventListener('pointermove', this.#onResizeMove);
|
|
180
|
+
document.addEventListener('pointerup', this.#onResizeUp, { once: true });
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
#onResizeMove = (e) => {
|
|
185
|
+
if (!this.#drag) return;
|
|
186
|
+
const { aside, side, startX, startW } = this.#drag;
|
|
187
|
+
const dx = e.clientX - startX;
|
|
188
|
+
const min = side === 'leading' ? this.leadingMinWidth : this.trailingMinWidth;
|
|
189
|
+
const max = side === 'leading' ? this.leadingMaxWidth : this.trailingMaxWidth;
|
|
190
|
+
const next = Math.max(min, Math.min(max, startW + (side === 'leading' ? dx : -dx)));
|
|
191
|
+
aside.style.width = `${next}px`;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
#onResizeUp = () => {
|
|
195
|
+
this.#drag = null;
|
|
196
|
+
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
customElements.define('app-shell-ui', AdiaAppShell);
|
|
201
|
+
|
|
202
|
+
export { AdiaAppShell };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: AdiaAppShell
|
|
4
|
+
tag: app-shell-ui
|
|
5
|
+
component: AppShell
|
|
6
|
+
category: container
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Application shell layout. Holds a leading nav rail, a main column
|
|
10
|
+
(topbar + scroll area + statusbar), an optional trailing inspector,
|
|
11
|
+
and an optional command-palette `<dialog>`. The component wires
|
|
12
|
+
sidebar toggle, sidebar resize, and Cmd+K shortcut on top of the
|
|
13
|
+
long-lived CSS pattern that already targets `app-shell-ui` as a tag.
|
|
14
|
+
Children use native HTML semantic elements (`<aside data-sidebar>`,
|
|
15
|
+
`<main>`, `<header>`, `<section>`, `<footer>`); the existing
|
|
16
|
+
CSS scopes by tag.
|
|
17
|
+
props:
|
|
18
|
+
mode:
|
|
19
|
+
description: |
|
|
20
|
+
Space-separated list of layout modes. `rounded` adds border-radius
|
|
21
|
+
to the scroll section; `borderless` removes chrome borders (content
|
|
22
|
+
borders persist). Pass both for a soft, edgeless shell.
|
|
23
|
+
type: string
|
|
24
|
+
default: ""
|
|
25
|
+
reflect: true
|
|
26
|
+
leadingCollapsed:
|
|
27
|
+
description: |
|
|
28
|
+
Reflects to the leading aside's `[data-collapsed]`. Toggled
|
|
29
|
+
programmatically (`shell.toggleLeading()`) or via a click on any
|
|
30
|
+
element with `[data-sidebar-toggle="leading"]` inside the shell.
|
|
31
|
+
type: boolean
|
|
32
|
+
default: false
|
|
33
|
+
attribute: leading-collapsed
|
|
34
|
+
reflect: true
|
|
35
|
+
trailingCollapsed:
|
|
36
|
+
description: |
|
|
37
|
+
Reflects to the trailing aside's `[data-collapsed]`. Toggled via
|
|
38
|
+
`shell.toggleTrailing()` or `[data-sidebar-toggle="trailing"]`.
|
|
39
|
+
type: boolean
|
|
40
|
+
default: false
|
|
41
|
+
attribute: trailing-collapsed
|
|
42
|
+
reflect: true
|
|
43
|
+
cmdK:
|
|
44
|
+
description: |
|
|
45
|
+
Installs a global Cmd+K / Ctrl+K listener that opens the inner
|
|
46
|
+
`<dialog data-command>` via `showModal()`. No-op when no such
|
|
47
|
+
dialog is present.
|
|
48
|
+
type: boolean
|
|
49
|
+
default: false
|
|
50
|
+
attribute: cmd-k
|
|
51
|
+
reflect: true
|
|
52
|
+
leadingMinWidth:
|
|
53
|
+
description: Minimum leading-aside width (px) when resizing.
|
|
54
|
+
type: number
|
|
55
|
+
default: 48
|
|
56
|
+
attribute: leading-min-width
|
|
57
|
+
leadingMaxWidth:
|
|
58
|
+
description: Maximum leading-aside width (px) when resizing.
|
|
59
|
+
type: number
|
|
60
|
+
default: 480
|
|
61
|
+
attribute: leading-max-width
|
|
62
|
+
trailingMinWidth:
|
|
63
|
+
description: Minimum trailing-aside width (px) when resizing.
|
|
64
|
+
type: number
|
|
65
|
+
default: 48
|
|
66
|
+
attribute: trailing-min-width
|
|
67
|
+
trailingMaxWidth:
|
|
68
|
+
description: Maximum trailing-aside width (px) when resizing.
|
|
69
|
+
type: number
|
|
70
|
+
default: 480
|
|
71
|
+
attribute: trailing-max-width
|
|
72
|
+
events:
|
|
73
|
+
sidebar-toggle:
|
|
74
|
+
description: |
|
|
75
|
+
Fired after a sidebar's collapsed state flips. Detail:
|
|
76
|
+
`{ side: 'leading'|'trailing', collapsed: boolean }`.
|
|
77
|
+
command-open:
|
|
78
|
+
description: Fired after the command-palette `<dialog>` opens.
|
|
79
|
+
command-close:
|
|
80
|
+
description: Fired after the command-palette `<dialog>` closes.
|
|
81
|
+
slots:
|
|
82
|
+
default:
|
|
83
|
+
description: |
|
|
84
|
+
Composes from native HTML children: `<aside data-sidebar="leading">`,
|
|
85
|
+
`<main>` (with `<header>`, `<section>`, `<footer>` inside),
|
|
86
|
+
optional `<aside data-sidebar="trailing">`, optional
|
|
87
|
+
`<dialog data-command>`. The existing pattern CSS scopes each
|
|
88
|
+
child by tag + attribute; no `slot=` attributes needed.
|
|
89
|
+
states:
|
|
90
|
+
- name: idle
|
|
91
|
+
description: Default, ready for interaction.
|
|
92
|
+
- name: leading-collapsed
|
|
93
|
+
description: Leading aside collapsed via attribute / API.
|
|
94
|
+
- name: trailing-collapsed
|
|
95
|
+
description: Trailing aside collapsed via attribute / API.
|
|
96
|
+
- name: command-open
|
|
97
|
+
description: Command-palette dialog is showing.
|
|
98
|
+
traits: []
|
|
99
|
+
tokens: {}
|
|
100
|
+
a2ui:
|
|
101
|
+
rules: []
|
|
102
|
+
anti_patterns: []
|
|
103
|
+
examples:
|
|
104
|
+
- name: docs-shell
|
|
105
|
+
description: Documentation shell — leading nav, topbar with breadcrumb, scroll content, statusbar.
|
|
106
|
+
a2ui: >-
|
|
107
|
+
[
|
|
108
|
+
{
|
|
109
|
+
"id": "root",
|
|
110
|
+
"component": "AppShell",
|
|
111
|
+
"mode": "rounded",
|
|
112
|
+
"cmdK": true,
|
|
113
|
+
"children": ["leading", "main", "cmd"]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "leading",
|
|
117
|
+
"component": "Aside",
|
|
118
|
+
"data-sidebar": "leading",
|
|
119
|
+
"children": ["nav-hdr", "nav-list", "nav-ftr"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "nav-hdr",
|
|
123
|
+
"component": "Header",
|
|
124
|
+
"children": []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "nav-list",
|
|
128
|
+
"component": "Section",
|
|
129
|
+
"children": []
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "nav-ftr",
|
|
133
|
+
"component": "Footer",
|
|
134
|
+
"children": []
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "main",
|
|
138
|
+
"component": "Column",
|
|
139
|
+
"children": ["topbar", "scroll", "statusbar"]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "topbar",
|
|
143
|
+
"component": "Header",
|
|
144
|
+
"children": []
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "scroll",
|
|
148
|
+
"component": "Section",
|
|
149
|
+
"scroll": true,
|
|
150
|
+
"children": []
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"id": "statusbar",
|
|
154
|
+
"component": "Footer",
|
|
155
|
+
"children": []
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "cmd",
|
|
159
|
+
"component": "Modal",
|
|
160
|
+
"data-command": true,
|
|
161
|
+
"children": []
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
keywords:
|
|
165
|
+
- app-shell
|
|
166
|
+
- shell
|
|
167
|
+
- layout
|
|
168
|
+
- sidebar
|
|
169
|
+
- topbar
|
|
170
|
+
- command-palette
|
|
171
|
+
- cmdK
|
|
172
|
+
- dashboard-shell
|
|
173
|
+
synonyms: {}
|
|
174
|
+
related:
|
|
175
|
+
- aside
|
|
176
|
+
- header
|
|
177
|
+
- footer
|
|
178
|
+
- section
|
|
179
|
+
- drawer
|
|
180
|
+
- modal
|
|
181
|
+
- command
|
|
182
|
+
- app-nav
|
|
183
|
+
- section-nav
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/Aside.json",
|
|
4
|
+
"title": "Aside",
|
|
5
|
+
"description": "Side region — styled by closest container parent (AppShell / Drawer / Card).\nHolds nav rails, secondary actions, or supplementary content. CSS-only slot\nstub: no own behavior; the container parent reads `[collapsible]` and\n`[width]` to wire collapse + width. For interactive resize / collapse\nbehavior, compose with `<pane-ui>` inside: aside = semantic role,\npane = resizable / collapsible primitive.\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
|
+
"collapsible": {
|
|
17
|
+
"description": "Marks the aside as collapsible. The container parent (AppShell, Drawer)\nreads this and wires the toggle affordance + ARIA + state. The aside\nitself ships no collapse logic.\n",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
21
|
+
"component": {
|
|
22
|
+
"const": "Aside"
|
|
23
|
+
},
|
|
24
|
+
"width": {
|
|
25
|
+
"description": "Token-bound width hint. The container parent reads this to set the\naside column width — `rail` (compact icon-only nav), `panel` (full\nnav text), `wide` (workspace pane). Empty string defers to parent default.\n",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": [
|
|
28
|
+
"",
|
|
29
|
+
"rail",
|
|
30
|
+
"panel",
|
|
31
|
+
"wide"
|
|
32
|
+
],
|
|
33
|
+
"default": ""
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"component"
|
|
38
|
+
],
|
|
39
|
+
"unevaluatedProperties": false,
|
|
40
|
+
"x-adiaui": {
|
|
41
|
+
"anti_patterns": [],
|
|
42
|
+
"category": "container",
|
|
43
|
+
"events": {},
|
|
44
|
+
"examples": [
|
|
45
|
+
{
|
|
46
|
+
"description": "Card laid out as a header / aside / section, with the aside holding a nav list.",
|
|
47
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\"hdr\", \"side\", \"main\"]\n },\n {\n \"id\": \"hdr\",\n \"component\": \"Header\",\n \"children\": [\"title\"]\n },\n {\n \"id\": \"title\",\n \"component\": \"Text\",\n \"slot\": \"heading\",\n \"textContent\": \"Settings\"\n },\n {\n \"id\": \"side\",\n \"component\": \"Aside\",\n \"width\": \"rail\",\n \"children\": [\"nav\"]\n },\n {\n \"id\": \"nav\",\n \"component\": \"List\",\n \"children\": []\n },\n {\n \"id\": \"main\",\n \"component\": \"Section\",\n \"children\": []\n }\n]",
|
|
48
|
+
"name": "card-with-side-nav"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"keywords": [
|
|
52
|
+
"aside",
|
|
53
|
+
"sidebar",
|
|
54
|
+
"rail",
|
|
55
|
+
"panel",
|
|
56
|
+
"side-region",
|
|
57
|
+
"nav-rail"
|
|
58
|
+
],
|
|
59
|
+
"name": "AdiaAside",
|
|
60
|
+
"related": [
|
|
61
|
+
"app-shell",
|
|
62
|
+
"drawer",
|
|
63
|
+
"pane",
|
|
64
|
+
"section",
|
|
65
|
+
"header"
|
|
66
|
+
],
|
|
67
|
+
"slots": {
|
|
68
|
+
"default": {
|
|
69
|
+
"description": "Default slot — primary child content (typically `<list-ui>`, `<tree-ui>`, or app-nav primitives)."
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"states": [
|
|
73
|
+
{
|
|
74
|
+
"description": "Default, ready for interaction.",
|
|
75
|
+
"name": "idle"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"synonyms": {},
|
|
79
|
+
"tag": "aside-ui",
|
|
80
|
+
"tokens": {},
|
|
81
|
+
"traits": [],
|
|
82
|
+
"version": 1
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: AdiaAside
|
|
4
|
+
tag: aside-ui
|
|
5
|
+
component: Aside
|
|
6
|
+
category: container
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Side region — styled by closest container parent (AppShell / Drawer / Card).
|
|
10
|
+
Holds nav rails, secondary actions, or supplementary content. CSS-only slot
|
|
11
|
+
stub: no own behavior; the container parent reads `[collapsible]` and
|
|
12
|
+
`[width]` to wire collapse + width. For interactive resize / collapse
|
|
13
|
+
behavior, compose with `<pane-ui>` inside: aside = semantic role,
|
|
14
|
+
pane = resizable / collapsible primitive.
|
|
15
|
+
props:
|
|
16
|
+
collapsible:
|
|
17
|
+
description: |
|
|
18
|
+
Marks the aside as collapsible. The container parent (AppShell, Drawer)
|
|
19
|
+
reads this and wires the toggle affordance + ARIA + state. The aside
|
|
20
|
+
itself ships no collapse logic.
|
|
21
|
+
type: boolean
|
|
22
|
+
default: false
|
|
23
|
+
reflect: true
|
|
24
|
+
width:
|
|
25
|
+
description: |
|
|
26
|
+
Token-bound width hint. The container parent reads this to set the
|
|
27
|
+
aside column width — `rail` (compact icon-only nav), `panel` (full
|
|
28
|
+
nav text), `wide` (workspace pane). Empty string defers to parent default.
|
|
29
|
+
type: string
|
|
30
|
+
default: ""
|
|
31
|
+
enum:
|
|
32
|
+
- ""
|
|
33
|
+
- rail
|
|
34
|
+
- panel
|
|
35
|
+
- wide
|
|
36
|
+
reflect: true
|
|
37
|
+
events: {}
|
|
38
|
+
slots:
|
|
39
|
+
default:
|
|
40
|
+
description: "Default slot — primary child content (typically `<list-ui>`, `<tree-ui>`, or app-nav primitives)."
|
|
41
|
+
states:
|
|
42
|
+
- name: idle
|
|
43
|
+
description: Default, ready for interaction.
|
|
44
|
+
traits: []
|
|
45
|
+
tokens: {}
|
|
46
|
+
a2ui:
|
|
47
|
+
rules: []
|
|
48
|
+
anti_patterns: []
|
|
49
|
+
examples:
|
|
50
|
+
- name: card-with-side-nav
|
|
51
|
+
description: Card laid out as a header / aside / section, with the aside holding a nav list.
|
|
52
|
+
a2ui: >-
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
"id": "root",
|
|
56
|
+
"component": "Card",
|
|
57
|
+
"children": ["hdr", "side", "main"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "hdr",
|
|
61
|
+
"component": "Header",
|
|
62
|
+
"children": ["title"]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "title",
|
|
66
|
+
"component": "Text",
|
|
67
|
+
"slot": "heading",
|
|
68
|
+
"textContent": "Settings"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "side",
|
|
72
|
+
"component": "Aside",
|
|
73
|
+
"width": "rail",
|
|
74
|
+
"children": ["nav"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "nav",
|
|
78
|
+
"component": "List",
|
|
79
|
+
"children": []
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "main",
|
|
83
|
+
"component": "Section",
|
|
84
|
+
"children": []
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
keywords:
|
|
88
|
+
- aside
|
|
89
|
+
- sidebar
|
|
90
|
+
- rail
|
|
91
|
+
- panel
|
|
92
|
+
- side-region
|
|
93
|
+
- nav-rail
|
|
94
|
+
synonyms: {}
|
|
95
|
+
related:
|
|
96
|
+
- app-shell
|
|
97
|
+
- drawer
|
|
98
|
+
- pane
|
|
99
|
+
- section
|
|
100
|
+
- header
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Footer.json",
|
|
4
4
|
"title": "Footer",
|
|
5
|
-
"description": "Card
|
|
5
|
+
"description": "Footer — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Contains actions, pagination, or summary. Typically holds Buttons.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -6,7 +6,7 @@ tag: footer-ui
|
|
|
6
6
|
component: Footer
|
|
7
7
|
category: container
|
|
8
8
|
version: 1
|
|
9
|
-
description: Card
|
|
9
|
+
description: Footer — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Contains actions, pagination, or summary. Typically holds Buttons.
|
|
10
10
|
props:
|
|
11
11
|
justify:
|
|
12
12
|
description: Horizontal alignment of children
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Header.json",
|
|
4
4
|
"title": "Header",
|
|
5
|
-
"description": "Card
|
|
5
|
+
"description": "Header — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Contains heading text and optional action slot.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"const": "Header"
|
|
18
18
|
},
|
|
19
19
|
"padding": {
|
|
20
|
-
"description": "Bare attribute — enables default header padding.
|
|
20
|
+
"description": "Bare attribute — enables default header padding. The container parent's own `padding` prop sets the scale.",
|
|
21
21
|
"type": "boolean",
|
|
22
22
|
"default": false
|
|
23
23
|
}
|
|
@@ -6,10 +6,10 @@ tag: header-ui
|
|
|
6
6
|
component: Header
|
|
7
7
|
category: container
|
|
8
8
|
version: 1
|
|
9
|
-
description: Card
|
|
9
|
+
description: Header — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Contains heading text and optional action slot.
|
|
10
10
|
props:
|
|
11
11
|
padding:
|
|
12
|
-
description: Bare attribute — enables default header padding.
|
|
12
|
+
description: Bare attribute — enables default header padding. The container parent's own `padding` prop sets the scale.
|
|
13
13
|
type: boolean
|
|
14
14
|
default: false
|
|
15
15
|
reflect: true
|
package/components/index.js
CHANGED
|
@@ -24,6 +24,8 @@ export { AdiaSegmented } from './segmented/segmented.js';
|
|
|
24
24
|
export { AdiaRange } from './range/range.js';
|
|
25
25
|
export { AdiaTree, AdiaTreeItem } from './tree/tree.js';
|
|
26
26
|
export { AdiaPane } from './pane/pane.js';
|
|
27
|
+
export { AdiaAppShell } from './app-shell/app-shell.js';
|
|
28
|
+
export { AdiaPage } from './page/page.js';
|
|
27
29
|
export { AdiaChatInput } from './chat/chat-input.js';
|
|
28
30
|
export { AdiaChat } from './chat/chat.js';
|
|
29
31
|
export { AdiaDrawer } from './drawer/drawer.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/Page.json",
|
|
4
|
+
"title": "Page",
|
|
5
|
+
"description": "Page container. Holds page-level chrome — header / content / footer —\nand manages max-width clamps, padding scale, optional scroll-container,\nand an optional sticky-header sentinel. Compose with the slot\nprimitives (`<header-ui>`, `<section-ui>`, `<footer-ui>`); the page's\n@scope rules style them. Drop in directly, or nest inside an\n`<app-shell-ui>`'s main column.\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": "Page"
|
|
18
|
+
},
|
|
19
|
+
"maxWidth": {
|
|
20
|
+
"description": "Token-bound max-width clamp. `prose` (65ch) for reading pages,\n`narrow` (80ch) for tight forms, `wide` (1080px) for data-rich\npages, `full` for unconstrained. Empty defers to parent / 100%.\nCentered horizontally via `margin-inline: auto`.\n",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": [
|
|
23
|
+
"",
|
|
24
|
+
"prose",
|
|
25
|
+
"narrow",
|
|
26
|
+
"wide",
|
|
27
|
+
"full"
|
|
28
|
+
],
|
|
29
|
+
"default": ""
|
|
30
|
+
},
|
|
31
|
+
"padding": {
|
|
32
|
+
"description": "Page-padding scale from the spacing system. Accepts `0`–`8`\n(mapped to `--a-space-N`). Empty (no value) applies the\n`--page-padding-default` token; `0` removes padding.\n",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"default": ""
|
|
35
|
+
},
|
|
36
|
+
"scroll": {
|
|
37
|
+
"description": "Sets the page as a scroll container. `overflow-y: auto`, full\nheight, contained overscroll. Use when the page IS the scroll\nsurface (standalone pages); leave off when nested inside a parent\nthat already manages scroll (e.g. inside an `<app-shell-ui>`'s\nmain `<section>`).\n",
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": false
|
|
40
|
+
},
|
|
41
|
+
"stickyHeader": {
|
|
42
|
+
"description": "Installs an IntersectionObserver sentinel before the first\n`<header>` / `<header-ui>` child. When the sentinel scrolls out\nof view the page gains `[data-header-stuck]`, which the CSS\nuses to add a border + shadow to the header. No-op when no\nheader is present.\n",
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"default": false
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": [
|
|
48
|
+
"component"
|
|
49
|
+
],
|
|
50
|
+
"unevaluatedProperties": false,
|
|
51
|
+
"x-adiaui": {
|
|
52
|
+
"anti_patterns": [],
|
|
53
|
+
"category": "container",
|
|
54
|
+
"events": {},
|
|
55
|
+
"examples": [
|
|
56
|
+
{
|
|
57
|
+
"description": "Reading page with sticky header, 65ch column, padding scale 6.",
|
|
58
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Page\",\n \"stickyHeader\": true,\n \"maxWidth\": \"prose\",\n \"padding\": \"6\",\n \"children\": [\"hdr\", \"body\"]\n },\n {\n \"id\": \"hdr\",\n \"component\": \"Header\",\n \"children\": [\"title\"]\n },\n {\n \"id\": \"title\",\n \"component\": \"Text\",\n \"variant\": \"display\",\n \"textContent\": \"Reading Page\"\n },\n {\n \"id\": \"body\",\n \"component\": \"Section\",\n \"children\": []\n }\n]",
|
|
59
|
+
"name": "prose-page"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"description": "Wide-clamp dashboard page acting as a scroll container.",
|
|
63
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Page\",\n \"scroll\": true,\n \"maxWidth\": \"wide\",\n \"padding\": \"4\",\n \"children\": [\"hdr\", \"body\"]\n },\n {\n \"id\": \"hdr\",\n \"component\": \"Header\",\n \"children\": []\n },\n {\n \"id\": \"body\",\n \"component\": \"Section\",\n \"children\": []\n }\n]",
|
|
64
|
+
"name": "dashboard-page"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"keywords": [
|
|
68
|
+
"page",
|
|
69
|
+
"layout",
|
|
70
|
+
"container",
|
|
71
|
+
"scroll",
|
|
72
|
+
"sticky-header",
|
|
73
|
+
"max-width",
|
|
74
|
+
"padding",
|
|
75
|
+
"prose-page",
|
|
76
|
+
"dashboard-page"
|
|
77
|
+
],
|
|
78
|
+
"name": "AdiaPage",
|
|
79
|
+
"related": [
|
|
80
|
+
"app-shell",
|
|
81
|
+
"card",
|
|
82
|
+
"section",
|
|
83
|
+
"header",
|
|
84
|
+
"footer"
|
|
85
|
+
],
|
|
86
|
+
"slots": {
|
|
87
|
+
"default": {
|
|
88
|
+
"description": "Composes from the slot primitives — `<header-ui>` (page header),\n`<section-ui>` (main content), optional `<footer-ui>`. Native\n`<header>` / `<section>` / `<footer>` also work; the @scope rules\ntarget both via `:where(header, header-ui)`.\n"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"states": [
|
|
92
|
+
{
|
|
93
|
+
"description": "Default, ready for interaction.",
|
|
94
|
+
"name": "idle"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"description": "Header has scrolled past the sentinel; visual cue applied.",
|
|
98
|
+
"name": "header-stuck"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"synonyms": {},
|
|
102
|
+
"tag": "page-ui",
|
|
103
|
+
"tokens": {},
|
|
104
|
+
"traits": [],
|
|
105
|
+
"version": 1
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@scope (page-ui) {
|
|
2
|
+
:where(:scope) {
|
|
3
|
+
/* ── Max-width clamps ── */
|
|
4
|
+
--page-max-width-prose: 65ch;
|
|
5
|
+
--page-max-width-narrow: 80ch;
|
|
6
|
+
--page-max-width-wide: 1080px;
|
|
7
|
+
--page-max-width-full: 100%;
|
|
8
|
+
|
|
9
|
+
/* ── Padding default (when [padding] is set without a value) ── */
|
|
10
|
+
--page-padding-default: var(--a-space-6);
|
|
11
|
+
|
|
12
|
+
/* ── Surfaces ── */
|
|
13
|
+
--page-bg: var(--a-canvas-0);
|
|
14
|
+
--page-fg: var(--a-fg);
|
|
15
|
+
|
|
16
|
+
/* ── Sticky-header chrome (when [data-header-stuck]) ── */
|
|
17
|
+
--page-sticky-bg: var(--a-canvas-0);
|
|
18
|
+
--page-sticky-border: 1px solid var(--a-border-subtle);
|
|
19
|
+
--page-sticky-shadow: var(--a-shadow-sm);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:scope {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
display: block;
|
|
25
|
+
width: 100%;
|
|
26
|
+
background: var(--page-bg);
|
|
27
|
+
color: var(--page-fg);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── max-width clamps ── */
|
|
31
|
+
:scope[max-width="prose"] { max-width: var(--page-max-width-prose); margin-inline: auto; }
|
|
32
|
+
:scope[max-width="narrow"] { max-width: var(--page-max-width-narrow); margin-inline: auto; }
|
|
33
|
+
:scope[max-width="wide"] { max-width: var(--page-max-width-wide); margin-inline: auto; }
|
|
34
|
+
:scope[max-width="full"] { max-width: var(--page-max-width-full); }
|
|
35
|
+
|
|
36
|
+
/* ── Padding scale (mirrors --a-space-N) ── */
|
|
37
|
+
:scope[padding=""] { padding: var(--page-padding-default); }
|
|
38
|
+
:scope[padding="0"] { padding: 0; }
|
|
39
|
+
:scope[padding="1"] { padding: var(--a-space-1); }
|
|
40
|
+
:scope[padding="2"] { padding: var(--a-space-2); }
|
|
41
|
+
:scope[padding="3"] { padding: var(--a-space-3); }
|
|
42
|
+
:scope[padding="4"] { padding: var(--a-space-4); }
|
|
43
|
+
:scope[padding="5"] { padding: var(--a-space-5); }
|
|
44
|
+
:scope[padding="6"] { padding: var(--a-space-6); }
|
|
45
|
+
:scope[padding="7"] { padding: var(--a-space-7); }
|
|
46
|
+
:scope[padding="8"] { padding: var(--a-space-8); }
|
|
47
|
+
|
|
48
|
+
/* ── Scroll container ── */
|
|
49
|
+
:scope[scroll] {
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
height: 100%;
|
|
52
|
+
overscroll-behavior: contain;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Sticky-header support ── */
|
|
56
|
+
:scope[sticky-header] > :where(header, header-ui) {
|
|
57
|
+
position: sticky;
|
|
58
|
+
top: 0;
|
|
59
|
+
z-index: 1;
|
|
60
|
+
background: var(--page-sticky-bg);
|
|
61
|
+
transition: border-color 150ms ease, box-shadow 150ms ease;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:scope[data-header-stuck] > :where(header, header-ui) {
|
|
65
|
+
border-block-end: var(--page-sticky-border);
|
|
66
|
+
box-shadow: var(--page-sticky-shadow);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <page-ui> — Page container.
|
|
3
|
+
*
|
|
4
|
+
* Holds page-level chrome — header / content / footer — and manages
|
|
5
|
+
* max-width clamps, padding scale, optional scroll-container, and an
|
|
6
|
+
* optional sticky-header sentinel. Compose with the slot primitives
|
|
7
|
+
* (`<header-ui>`, `<section-ui>`, `<footer-ui>`); the page's @scope
|
|
8
|
+
* rules style them.
|
|
9
|
+
*
|
|
10
|
+
* Authoring:
|
|
11
|
+
* <page-ui sticky-header max-width="prose" padding="6">
|
|
12
|
+
* <header-ui>...</header-ui> <!-- page title + actions -->
|
|
13
|
+
* <section-ui>...</section-ui> <!-- main content -->
|
|
14
|
+
* <footer-ui>...</footer-ui> <!-- optional -->
|
|
15
|
+
* </page-ui>
|
|
16
|
+
*
|
|
17
|
+
* Attributes:
|
|
18
|
+
* scroll — boolean. Page is the scroll container (overflow-y: auto).
|
|
19
|
+
* max-width — '' | 'prose' (65ch) | 'narrow' (80ch) | 'wide' (1080px) | 'full' (100%).
|
|
20
|
+
* padding — '' | '0'..'8' (mapped to --a-space-N).
|
|
21
|
+
* sticky-header — boolean. Installs IntersectionObserver sentinel before the
|
|
22
|
+
* first <header> / <header-ui> child; sets [data-header-stuck]
|
|
23
|
+
* on the page when the sentinel scrolls out of view.
|
|
24
|
+
*
|
|
25
|
+
* ADR: .brain/adrs/0009-promote-app-shell-and-page-to-components.md.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { AdiaElement } from '../../core/element.js';
|
|
29
|
+
|
|
30
|
+
class AdiaPage extends AdiaElement {
|
|
31
|
+
static properties = {
|
|
32
|
+
scroll: { type: Boolean, default: false, reflect: true },
|
|
33
|
+
maxWidth: { type: String, default: '', attribute: 'max-width', reflect: true },
|
|
34
|
+
padding: { type: String, default: '', reflect: true },
|
|
35
|
+
stickyHeader: { type: Boolean, default: false, attribute: 'sticky-header', reflect: true },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
static template = () => null;
|
|
39
|
+
|
|
40
|
+
#sentinel = null;
|
|
41
|
+
#observer = null;
|
|
42
|
+
|
|
43
|
+
connected() {
|
|
44
|
+
if (this.stickyHeader) this.#installSticky();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnected() {
|
|
48
|
+
this.#teardownSticky();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
render() {
|
|
52
|
+
// Sticky-header attribute changes between renders → install / tear down.
|
|
53
|
+
if (this.stickyHeader && !this.#sentinel) this.#installSticky();
|
|
54
|
+
if (!this.stickyHeader && this.#sentinel) this.#teardownSticky();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#installSticky() {
|
|
58
|
+
const header = this.querySelector(':scope > :is(header, header-ui)');
|
|
59
|
+
if (!header) return;
|
|
60
|
+
|
|
61
|
+
if (!this.#sentinel) {
|
|
62
|
+
this.#sentinel = document.createElement('div');
|
|
63
|
+
this.#sentinel.setAttribute('data-page-sentinel', '');
|
|
64
|
+
this.#sentinel.style.cssText = 'height: 0; width: 0; pointer-events: none;';
|
|
65
|
+
header.insertAdjacentElement('beforebegin', this.#sentinel);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!this.#observer) {
|
|
69
|
+
this.#observer = new IntersectionObserver((entries) => {
|
|
70
|
+
this.toggleAttribute('data-header-stuck', !entries[0].isIntersecting);
|
|
71
|
+
}, { threshold: 0 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.#observer.observe(this.#sentinel);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#teardownSticky() {
|
|
78
|
+
this.#observer?.disconnect();
|
|
79
|
+
this.#observer = null;
|
|
80
|
+
this.#sentinel?.remove();
|
|
81
|
+
this.#sentinel = null;
|
|
82
|
+
this.removeAttribute('data-header-stuck');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
customElements.define('page-ui', AdiaPage);
|
|
87
|
+
|
|
88
|
+
export { AdiaPage };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: AdiaPage
|
|
4
|
+
tag: page-ui
|
|
5
|
+
component: Page
|
|
6
|
+
category: container
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Page container. Holds page-level chrome — header / content / footer —
|
|
10
|
+
and manages max-width clamps, padding scale, optional scroll-container,
|
|
11
|
+
and an optional sticky-header sentinel. Compose with the slot
|
|
12
|
+
primitives (`<header-ui>`, `<section-ui>`, `<footer-ui>`); the page's
|
|
13
|
+
@scope rules style them. Drop in directly, or nest inside an
|
|
14
|
+
`<app-shell-ui>`'s main column.
|
|
15
|
+
props:
|
|
16
|
+
scroll:
|
|
17
|
+
description: |
|
|
18
|
+
Sets the page as a scroll container. `overflow-y: auto`, full
|
|
19
|
+
height, contained overscroll. Use when the page IS the scroll
|
|
20
|
+
surface (standalone pages); leave off when nested inside a parent
|
|
21
|
+
that already manages scroll (e.g. inside an `<app-shell-ui>`'s
|
|
22
|
+
main `<section>`).
|
|
23
|
+
type: boolean
|
|
24
|
+
default: false
|
|
25
|
+
reflect: true
|
|
26
|
+
maxWidth:
|
|
27
|
+
description: |
|
|
28
|
+
Token-bound max-width clamp. `prose` (65ch) for reading pages,
|
|
29
|
+
`narrow` (80ch) for tight forms, `wide` (1080px) for data-rich
|
|
30
|
+
pages, `full` for unconstrained. Empty defers to parent / 100%.
|
|
31
|
+
Centered horizontally via `margin-inline: auto`.
|
|
32
|
+
type: string
|
|
33
|
+
default: ""
|
|
34
|
+
enum:
|
|
35
|
+
- ""
|
|
36
|
+
- prose
|
|
37
|
+
- narrow
|
|
38
|
+
- wide
|
|
39
|
+
- full
|
|
40
|
+
attribute: max-width
|
|
41
|
+
reflect: true
|
|
42
|
+
padding:
|
|
43
|
+
description: |
|
|
44
|
+
Page-padding scale from the spacing system. Accepts `0`–`8`
|
|
45
|
+
(mapped to `--a-space-N`). Empty (no value) applies the
|
|
46
|
+
`--page-padding-default` token; `0` removes padding.
|
|
47
|
+
type: string
|
|
48
|
+
default: ""
|
|
49
|
+
reflect: true
|
|
50
|
+
stickyHeader:
|
|
51
|
+
description: |
|
|
52
|
+
Installs an IntersectionObserver sentinel before the first
|
|
53
|
+
`<header>` / `<header-ui>` child. When the sentinel scrolls out
|
|
54
|
+
of view the page gains `[data-header-stuck]`, which the CSS
|
|
55
|
+
uses to add a border + shadow to the header. No-op when no
|
|
56
|
+
header is present.
|
|
57
|
+
type: boolean
|
|
58
|
+
default: false
|
|
59
|
+
attribute: sticky-header
|
|
60
|
+
reflect: true
|
|
61
|
+
events: {}
|
|
62
|
+
slots:
|
|
63
|
+
default:
|
|
64
|
+
description: |
|
|
65
|
+
Composes from the slot primitives — `<header-ui>` (page header),
|
|
66
|
+
`<section-ui>` (main content), optional `<footer-ui>`. Native
|
|
67
|
+
`<header>` / `<section>` / `<footer>` also work; the @scope rules
|
|
68
|
+
target both via `:where(header, header-ui)`.
|
|
69
|
+
states:
|
|
70
|
+
- name: idle
|
|
71
|
+
description: Default, ready for interaction.
|
|
72
|
+
- name: header-stuck
|
|
73
|
+
description: Header has scrolled past the sentinel; visual cue applied.
|
|
74
|
+
traits: []
|
|
75
|
+
tokens: {}
|
|
76
|
+
a2ui:
|
|
77
|
+
rules: []
|
|
78
|
+
anti_patterns: []
|
|
79
|
+
examples:
|
|
80
|
+
- name: prose-page
|
|
81
|
+
description: Reading page with sticky header, 65ch column, padding scale 6.
|
|
82
|
+
a2ui: >-
|
|
83
|
+
[
|
|
84
|
+
{
|
|
85
|
+
"id": "root",
|
|
86
|
+
"component": "Page",
|
|
87
|
+
"stickyHeader": true,
|
|
88
|
+
"maxWidth": "prose",
|
|
89
|
+
"padding": "6",
|
|
90
|
+
"children": ["hdr", "body"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "hdr",
|
|
94
|
+
"component": "Header",
|
|
95
|
+
"children": ["title"]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "title",
|
|
99
|
+
"component": "Text",
|
|
100
|
+
"variant": "display",
|
|
101
|
+
"textContent": "Reading Page"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "body",
|
|
105
|
+
"component": "Section",
|
|
106
|
+
"children": []
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
- name: dashboard-page
|
|
110
|
+
description: Wide-clamp dashboard page acting as a scroll container.
|
|
111
|
+
a2ui: >-
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
"id": "root",
|
|
115
|
+
"component": "Page",
|
|
116
|
+
"scroll": true,
|
|
117
|
+
"maxWidth": "wide",
|
|
118
|
+
"padding": "4",
|
|
119
|
+
"children": ["hdr", "body"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "hdr",
|
|
123
|
+
"component": "Header",
|
|
124
|
+
"children": []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "body",
|
|
128
|
+
"component": "Section",
|
|
129
|
+
"children": []
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
keywords:
|
|
133
|
+
- page
|
|
134
|
+
- layout
|
|
135
|
+
- container
|
|
136
|
+
- scroll
|
|
137
|
+
- sticky-header
|
|
138
|
+
- max-width
|
|
139
|
+
- padding
|
|
140
|
+
- prose-page
|
|
141
|
+
- dashboard-page
|
|
142
|
+
synonyms: {}
|
|
143
|
+
related:
|
|
144
|
+
- app-shell
|
|
145
|
+
- card
|
|
146
|
+
- section
|
|
147
|
+
- header
|
|
148
|
+
- footer
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Section.json",
|
|
4
4
|
"title": "Section",
|
|
5
|
-
"description": "Content section
|
|
5
|
+
"description": "Content section — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Groups related content between Header and Footer. `[scroll]` only kicks in inside Card / Drawer / Modal — Page and AppShell own their own scroll containers.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -6,7 +6,7 @@ tag: section-ui
|
|
|
6
6
|
component: Section
|
|
7
7
|
category: container
|
|
8
8
|
version: 1
|
|
9
|
-
description: Content section
|
|
9
|
+
description: Content section — styled by closest container parent (Card / Drawer / Modal / Page / AppShell). Groups related content between Header and Footer. `[scroll]` only kicks in inside Card / Drawer / Modal — Page and AppShell own their own scroll containers.
|
|
10
10
|
props:
|
|
11
11
|
scroll:
|
|
12
12
|
description: Enable overflow scrolling
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/* ═══════════════════════════════════════════════════════════════
|
|
2
2
|
App Shell — <app-shell-ui> pattern component CSS
|
|
3
3
|
|
|
4
|
+
⚠ DEPRECATED PATH (2026-04-29): preserved as an alias for one
|
|
5
|
+
release while consumers migrate. The CSS now lives behind the
|
|
6
|
+
<app-shell-ui> custom element at
|
|
7
|
+
packages/web-components/components/app-shell/ and ships via
|
|
8
|
+
packages/web-components/styles/components.css.
|
|
9
|
+
|
|
10
|
+
Migration: drop the explicit <link rel="stylesheet"> to this file —
|
|
11
|
+
styles/components.css now bundles the same content via the new
|
|
12
|
+
component.
|
|
13
|
+
|
|
14
|
+
Companion ADR: ../../../../.brain/adrs/0009-promote-app-shell-and-page-to-components.md.
|
|
15
|
+
|
|
4
16
|
Split into constituent parts for maintainability.
|
|
5
17
|
Import order matters: tokens first, then structure, then overrides.
|
|
6
18
|
═══════════════════════════════════════════════════════════════ */
|
package/styles/components.css
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
@import "../components/range/range.css";
|
|
27
27
|
@import "../components/tree/tree.css";
|
|
28
28
|
@import "../components/pane/pane.css";
|
|
29
|
+
@import "../components/app-shell/app-shell.css";
|
|
30
|
+
@import "../components/page/page.css";
|
|
29
31
|
@import "../components/chat/chat-input.css";
|
|
30
32
|
@import "../components/chat/chat.css";
|
|
31
33
|
@import "../components/drawer/drawer.css";
|