@adia-ai/web-components 0.0.4 → 0.0.6
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/card/card.css +9 -0
- package/components/chat/chat-input.css +9 -3
- package/components/chat/chat-input.js +9 -2
- package/components/drawer/drawer.a2ui.json +4 -1
- package/components/drawer/drawer.css +9 -0
- package/components/drawer/drawer.yaml +9 -0
- package/components/pane/pane.a2ui.json +10 -0
- package/components/pane/pane.css +38 -4
- package/components/pane/pane.js +13 -1
- package/components/pane/pane.yaml +12 -0
- package/package.json +1 -1
- package/patterns/adia-editor/adia-editor.a2ui.json +3 -0
- package/patterns/adia-editor/adia-editor.yaml +6 -0
- package/patterns/adia-editor/css/adia-editor.layout.css +18 -0
- package/patterns/app-nav-group/app-nav-group.css +3 -0
- package/patterns/app-shell/app-shell.a2ui.json +15 -0
- package/patterns/app-shell/app-shell.yaml +23 -0
- package/patterns/app-shell/css/app-shell.main.css +88 -3
- package/patterns/app-shell/css/app-shell.sidebar.css +46 -1
- package/patterns/app-shell/css/app-shell.tokens.css +0 -2
package/components/card/card.css
CHANGED
|
@@ -335,6 +335,15 @@
|
|
|
335
335
|
margin-inline-start: 0;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
/* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
|
|
339
|
+
edge, trailing action cluster on the inline-end. margin-inline-end:
|
|
340
|
+
auto fills the gap between the two groups. */
|
|
341
|
+
> footer > [slot="action-leading"] {
|
|
342
|
+
margin-inline-end: auto;
|
|
343
|
+
display: flex;
|
|
344
|
+
gap: var(--card-footer-gap);
|
|
345
|
+
}
|
|
346
|
+
|
|
338
347
|
/* ═══════ Images ═══════ */
|
|
339
348
|
|
|
340
349
|
> img,
|
|
@@ -49,14 +49,20 @@
|
|
|
49
49
|
border-color: var(--chat-input-border-focus);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/* Textarea: no border/bg of its own — container handles it
|
|
53
|
-
|
|
52
|
+
/* Textarea: no border/bg of its own — container handles it. The
|
|
53
|
+
second selector keeps the transparent bg even when the host
|
|
54
|
+
textarea-ui carries [disabled] (streaming / submit lock) —
|
|
55
|
+
otherwise textarea.css's `:scope[disabled] [slot="text"]` rule
|
|
56
|
+
(specificity 0,3,0) paints --a-ui-bg-disabled over the container
|
|
57
|
+
bg. The `:scope` prefix boosts specificity to 0,3,1 so our rule
|
|
58
|
+
wins. */
|
|
59
|
+
textarea-ui [slot="text"],
|
|
60
|
+
:scope textarea-ui[disabled] [slot="text"] {
|
|
54
61
|
border: none;
|
|
55
62
|
background: transparent;
|
|
56
63
|
border-radius: 0;
|
|
57
64
|
box-shadow: none;
|
|
58
65
|
max-height: 8rem;
|
|
59
|
-
/*min-height: var(--a-size-md);*/
|
|
60
66
|
padding: var(--chat-input-textarea-pt) var(--chat-input-textarea-px) 0;
|
|
61
67
|
}
|
|
62
68
|
|
|
@@ -23,7 +23,10 @@ import { AdiaElement } from '../../core/element.js';
|
|
|
23
23
|
* models — JSON array of model options: [{value, label}] or [{label, options: [...]}]
|
|
24
24
|
* model — currently selected model value (reflected, two-way with select)
|
|
25
25
|
* placeholder — textarea placeholder
|
|
26
|
-
* disabled — disable entire input
|
|
26
|
+
* disabled — disable entire input (textarea becomes contenteditable=false)
|
|
27
|
+
* busy — in-flight / streaming state: send button disabled, submit
|
|
28
|
+
* events suppressed, but textarea stays editable so the user
|
|
29
|
+
* can draft a follow-up while the model is still responding.
|
|
27
30
|
*
|
|
28
31
|
* Events:
|
|
29
32
|
* submit — user pressed Enter or clicked send (detail: { text, model })
|
|
@@ -37,6 +40,7 @@ import { AdiaElement } from '../../core/element.js';
|
|
|
37
40
|
class AdiaChatInput extends AdiaElement {
|
|
38
41
|
static properties = {
|
|
39
42
|
disabled: { type: Boolean, default: false, reflect: true },
|
|
43
|
+
busy: { type: Boolean, default: false, reflect: true },
|
|
40
44
|
placeholder: { type: String, default: 'Type a message...', reflect: true },
|
|
41
45
|
model: { type: String, default: '', reflect: true },
|
|
42
46
|
};
|
|
@@ -138,6 +142,9 @@ class AdiaChatInput extends AdiaElement {
|
|
|
138
142
|
this.#textareaEl.disabled = this.disabled;
|
|
139
143
|
this.#textareaEl.placeholder = this.placeholder;
|
|
140
144
|
}
|
|
145
|
+
if (this.#sendEl) {
|
|
146
|
+
this.#sendEl.disabled = this.disabled || this.busy;
|
|
147
|
+
}
|
|
141
148
|
// Sync model value to select (handles late upgrades)
|
|
142
149
|
if (this.#modelEl && this.model && this.#modelEl.value !== this.model) {
|
|
143
150
|
this.#modelEl.value = this.model;
|
|
@@ -154,7 +161,7 @@ class AdiaChatInput extends AdiaElement {
|
|
|
154
161
|
};
|
|
155
162
|
|
|
156
163
|
#onSubmit = () => {
|
|
157
|
-
if (this.disabled) return;
|
|
164
|
+
if (this.disabled || this.busy) return;
|
|
158
165
|
const text = this.value;
|
|
159
166
|
if (!text && !this.#attachments.length) return;
|
|
160
167
|
this.dispatchEvent(new CustomEvent('submit', {
|
|
@@ -97,7 +97,10 @@
|
|
|
97
97
|
"description": "Direct child of <header> — grid row 2, spans the heading + action columns. Also accepts bare <p> / <small> tags."
|
|
98
98
|
},
|
|
99
99
|
"action": {
|
|
100
|
-
"description": "Direct child of <header> — placed in the grid's last column alongside the stamped close button. Flex container for badge + button combinations."
|
|
100
|
+
"description": "Direct child of <header> — placed in the grid's last column alongside the stamped close button. Flex container for badge + button combinations. In <footer>, self-aligns to the trailing (inline-end) edge; pair with `action-leading` for dual-cluster footers."
|
|
101
|
+
},
|
|
102
|
+
"action-leading": {
|
|
103
|
+
"description": "Direct child of <footer> — leading (inline-start) action cluster. Used for dual-cluster footers where a destructive or secondary action sits on the opposite edge from the primary trailing cluster (e.g. Delete ↔ Cancel/Save, Back ↔ Cancel/Next). Replaces the legacy <span data-spacer> hack."
|
|
101
104
|
},
|
|
102
105
|
"backdrop": {
|
|
103
106
|
"description": "Scrim overlay behind the drawer (stamped by the component)."
|
|
@@ -339,4 +339,13 @@
|
|
|
339
339
|
[slot="panel"] > [slot="footer"] > [slot="action"] ~ [slot="action"] {
|
|
340
340
|
margin-inline-start: 0;
|
|
341
341
|
}
|
|
342
|
+
|
|
343
|
+
/* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
|
|
344
|
+
edge, trailing action cluster on the inline-end. The margin-inline-end:
|
|
345
|
+
auto on the leading slot fills the gap between the two groups. */
|
|
346
|
+
[slot="panel"] > [slot="footer"] > [slot="action-leading"] {
|
|
347
|
+
margin-inline-end: auto;
|
|
348
|
+
display: flex;
|
|
349
|
+
gap: var(--drawer-footer-gap);
|
|
350
|
+
}
|
|
342
351
|
}
|
|
@@ -97,6 +97,15 @@ slots:
|
|
|
97
97
|
description: >-
|
|
98
98
|
Direct child of <header> — placed in the grid's last column alongside the
|
|
99
99
|
stamped close button. Flex container for badge + button combinations.
|
|
100
|
+
In <footer>, self-aligns to the trailing (inline-end) edge; pair with
|
|
101
|
+
`action-leading` for dual-cluster footers.
|
|
102
|
+
action-leading:
|
|
103
|
+
description: >-
|
|
104
|
+
Direct child of <footer> — leading (inline-start) action cluster. Used
|
|
105
|
+
for dual-cluster footers where a destructive or secondary action sits on
|
|
106
|
+
the opposite edge from the primary trailing cluster (e.g. Delete ↔
|
|
107
|
+
Cancel/Save, Back ↔ Cancel/Next). Replaces the legacy <span data-spacer>
|
|
108
|
+
hack.
|
|
100
109
|
states:
|
|
101
110
|
- name: idle
|
|
102
111
|
description: Default, ready for interaction.
|
|
@@ -35,6 +35,16 @@
|
|
|
35
35
|
"description": "Component property: resizable.",
|
|
36
36
|
"type": "boolean",
|
|
37
37
|
"default": false
|
|
38
|
+
},
|
|
39
|
+
"side": {
|
|
40
|
+
"description": "Opts a pane into horizontal-sibling chrome: suppresses the default\nfour-sided border and moves the resize grabber to the inner edge\n(right edge for `leading`, left edge for `trailing`). Also flips the\nresize-drag direction so `trailing` panes grow when dragged leftward.\nUnset keeps the pane-intrinsic chrome (full border, right-edge\ngrabber when resizable).\n",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"enum": [
|
|
43
|
+
"",
|
|
44
|
+
"leading",
|
|
45
|
+
"trailing"
|
|
46
|
+
],
|
|
47
|
+
"default": ""
|
|
38
48
|
}
|
|
39
49
|
},
|
|
40
50
|
"required": [
|
package/components/pane/pane.css
CHANGED
|
@@ -54,6 +54,23 @@
|
|
|
54
54
|
overflow: hidden;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/* Side-aware border treatment.
|
|
58
|
+
`leading` — pane sits at the leading edge of a horizontal row, so
|
|
59
|
+
the only visual separator it needs is on its trailing
|
|
60
|
+
edge (right in LTR).
|
|
61
|
+
`trailing` — mirror: only a leading-edge border (left in LTR).
|
|
62
|
+
Default (no side attr) keeps the full 4-sided border. */
|
|
63
|
+
:scope[side="leading"],
|
|
64
|
+
:scope[side="trailing"] {
|
|
65
|
+
border: none;
|
|
66
|
+
}
|
|
67
|
+
:scope[side="leading"] {
|
|
68
|
+
border-inline-end: 1px solid var(--pane-border);
|
|
69
|
+
}
|
|
70
|
+
:scope[side="trailing"] {
|
|
71
|
+
border-inline-start: 1px solid var(--pane-border);
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
/* ── Pane header ── */
|
|
58
75
|
> header {
|
|
59
76
|
display: flex;
|
|
@@ -101,16 +118,23 @@
|
|
|
101
118
|
display: none;
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
/* Section header
|
|
121
|
+
/* Section header — section's padding is the single source of horizontal inset;
|
|
122
|
+
content inside the section must not add its own padding. */
|
|
105
123
|
> section > header {
|
|
106
124
|
font-size: var(--pane-section-header-size);
|
|
107
125
|
color: var(--pane-section-header-fg);
|
|
108
|
-
padding-bottom: var(--pane-gap
|
|
126
|
+
padding-bottom: var(--pane-col-gap);
|
|
109
127
|
text-transform: uppercase;
|
|
110
128
|
letter-spacing: 0.06em;
|
|
111
129
|
font-weight: var(--pane-section-header-weight);
|
|
112
130
|
}
|
|
113
131
|
|
|
132
|
+
/* Defensive: a data-col directly under a section inherits the section's inset;
|
|
133
|
+
adding its own padding would compound and misalign with the section header. */
|
|
134
|
+
> section > [data-col] {
|
|
135
|
+
padding: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
114
138
|
/* Section divider between sections */
|
|
115
139
|
> section + section {
|
|
116
140
|
border-top: 1px solid var(--pane-border);
|
|
@@ -143,11 +167,16 @@
|
|
|
143
167
|
border-bottom: none;
|
|
144
168
|
}
|
|
145
169
|
|
|
146
|
-
/* ── Resize handle ──
|
|
170
|
+
/* ── Resize handle ──
|
|
171
|
+
Default (no side) positions the grabber on the right edge. When
|
|
172
|
+
`side="trailing"` is set, flip the grabber to the left edge so it
|
|
173
|
+
sits on the pane's "inner" side (where it meets the sibling
|
|
174
|
+
content/pane in a horizontal row). `inset-inline-*` lets the rules
|
|
175
|
+
stay LTR/RTL-safe. */
|
|
147
176
|
[slot="resize"] {
|
|
148
177
|
position: absolute;
|
|
149
178
|
top: 0;
|
|
150
|
-
|
|
179
|
+
inset-inline-end: 0;
|
|
151
180
|
bottom: 0;
|
|
152
181
|
width: var(--pane-resize-width);
|
|
153
182
|
cursor: col-resize;
|
|
@@ -156,6 +185,11 @@
|
|
|
156
185
|
z-index: 1;
|
|
157
186
|
}
|
|
158
187
|
|
|
188
|
+
:scope[side="trailing"] > [slot="resize"] {
|
|
189
|
+
inset-inline-end: auto;
|
|
190
|
+
inset-inline-start: 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
159
193
|
[slot="resize"]:hover {
|
|
160
194
|
background: var(--pane-resize-fg-hover);
|
|
161
195
|
}
|
package/components/pane/pane.js
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* resizable — boolean, enables drag-to-resize (width)
|
|
20
20
|
* min-width — minimum width when resizing (default: 200)
|
|
21
21
|
* max-width — maximum width when resizing (default: 600)
|
|
22
|
+
* side — 'leading' | 'trailing' | '' (default). When set, the
|
|
23
|
+
* pane is treated as a horizontal sibling: the default
|
|
24
|
+
* four-sided border is suppressed, only the inner-edge
|
|
25
|
+
* border is drawn, the resize grabber moves to that
|
|
26
|
+
* inner edge, and the resize-drag direction flips so
|
|
27
|
+
* trailing panes grow when dragged leftward.
|
|
22
28
|
*
|
|
23
29
|
* JS API:
|
|
24
30
|
* pane.collapsed = true/false
|
|
@@ -33,6 +39,7 @@ class AdiaPane extends AdiaElement {
|
|
|
33
39
|
resizable: { type: Boolean, default: false, reflect: true },
|
|
34
40
|
minWidth: { type: Number, default: 200, attribute: 'min-width', reflect: true },
|
|
35
41
|
maxWidth: { type: Number, default: 9999, attribute: 'max-width', reflect: true },
|
|
42
|
+
side: { type: String, default: '', reflect: true },
|
|
36
43
|
};
|
|
37
44
|
|
|
38
45
|
static template = () => null;
|
|
@@ -116,7 +123,12 @@ class AdiaPane extends AdiaElement {
|
|
|
116
123
|
|
|
117
124
|
#onResizeMove = (e) => {
|
|
118
125
|
if (!this.#dragging) return;
|
|
119
|
-
|
|
126
|
+
// For a trailing pane the grabber sits on the LEFT edge but the pane
|
|
127
|
+
// is anchored on the RIGHT side of its flex row — dragging leftward
|
|
128
|
+
// should GROW the pane (grabber moves further left = more width),
|
|
129
|
+
// not shrink it. Negate dx in that case.
|
|
130
|
+
const sign = this.side === 'trailing' ? -1 : 1;
|
|
131
|
+
const dx = (e.clientX - this.#startX) * sign;
|
|
120
132
|
const w = Math.max(this.minWidth, Math.min(this.maxWidth, this.#startW + dx));
|
|
121
133
|
this.style.width = `${w}px`;
|
|
122
134
|
};
|
|
@@ -26,6 +26,18 @@ props:
|
|
|
26
26
|
description: "Component property: resizable."
|
|
27
27
|
type: boolean
|
|
28
28
|
default: false
|
|
29
|
+
side:
|
|
30
|
+
description: |
|
|
31
|
+
Opts a pane into horizontal-sibling chrome: suppresses the default
|
|
32
|
+
four-sided border and moves the resize grabber to the inner edge
|
|
33
|
+
(right edge for `leading`, left edge for `trailing`). Also flips the
|
|
34
|
+
resize-drag direction so `trailing` panes grow when dragged leftward.
|
|
35
|
+
Unset keeps the pane-intrinsic chrome (full border, right-edge
|
|
36
|
+
grabber when resizable).
|
|
37
|
+
type: string
|
|
38
|
+
default: ""
|
|
39
|
+
enum: ["", leading, trailing]
|
|
40
|
+
reflect: true
|
|
29
41
|
events: {}
|
|
30
42
|
slots:
|
|
31
43
|
header:
|
package/package.json
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"action": {
|
|
49
49
|
"description": "Trailing control cluster inside <header> or <footer>. The first [slot=\"action\"] child pushes itself (and siblings) to the end of the bar; subsequent [slot=\"action\"] siblings flow with gap."
|
|
50
50
|
},
|
|
51
|
+
"action-leading": {
|
|
52
|
+
"description": "Leading (inline-start) control cluster inside <header> or <footer>. Pairs with [slot=\"action\"] to produce a dual-cluster bar with space-between alignment (e.g. Back ↔ Cancel/Next, Discard ↔ Publish). Replaces the legacy <span data-spacer> hack."
|
|
53
|
+
},
|
|
51
54
|
"heading": {
|
|
52
55
|
"description": "Primary label inside <header> or <footer>. Rendered with --editor-title-weight + the strong foreground token."
|
|
53
56
|
},
|
|
@@ -37,6 +37,12 @@ slots:
|
|
|
37
37
|
Trailing control cluster inside <header> or <footer>. The first
|
|
38
38
|
[slot="action"] child pushes itself (and siblings) to the end of
|
|
39
39
|
the bar; subsequent [slot="action"] siblings flow with gap.
|
|
40
|
+
action-leading:
|
|
41
|
+
description: >-
|
|
42
|
+
Leading (inline-start) control cluster inside <header> or <footer>.
|
|
43
|
+
Pairs with [slot="action"] to produce a dual-cluster bar with
|
|
44
|
+
space-between alignment (e.g. Back ↔ Cancel/Next, Discard ↔
|
|
45
|
+
Publish). Replaces the legacy <span data-spacer> hack.
|
|
40
46
|
|
|
41
47
|
states:
|
|
42
48
|
- name: idle
|
|
@@ -72,6 +72,15 @@ adia-editor-ui > header > [slot="action"] ~ [slot="action"] {
|
|
|
72
72
|
margin-inline-start: 0;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/* Dual-cluster: leading group on inline-start, trailing cluster on inline-end. */
|
|
76
|
+
adia-editor-ui > header > [slot="action-leading"] {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--editor-bar-gap);
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
margin-inline-end: auto;
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
/* ── Body: pane | canvas | pane ── */
|
|
76
85
|
adia-editor-ui > [data-editor-body] {
|
|
77
86
|
display: flex;
|
|
@@ -151,3 +160,12 @@ adia-editor-ui > footer > [slot="action"] {
|
|
|
151
160
|
adia-editor-ui > footer > [slot="action"] ~ [slot="action"] {
|
|
152
161
|
margin-inline-start: 0;
|
|
153
162
|
}
|
|
163
|
+
|
|
164
|
+
/* Dual-cluster: leading group on inline-start, trailing cluster on inline-end. */
|
|
165
|
+
adia-editor-ui > footer > [slot="action-leading"] {
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: var(--editor-bar-gap);
|
|
169
|
+
flex-shrink: 0;
|
|
170
|
+
margin-inline-end: auto;
|
|
171
|
+
}
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
--nav-group-bg-hover: var(--a-bg-muted);
|
|
17
17
|
--nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
|
|
18
18
|
--nav-group-badge-size: var(--a-ui-sm);
|
|
19
|
+
|
|
20
|
+
--nav-item-child-height: var(--a-size-sm);
|
|
21
|
+
--nav-item-child-font: var(--nav-group-font-size);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
:scope {
|
|
@@ -74,8 +74,23 @@
|
|
|
74
74
|
"Command"
|
|
75
75
|
],
|
|
76
76
|
"slots": {
|
|
77
|
+
"description": {
|
|
78
|
+
"description": "Secondary metadata inside any chrome bar. Muted + --a-ui-sm size."
|
|
79
|
+
},
|
|
77
80
|
"default": {
|
|
78
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."
|
|
79
94
|
}
|
|
80
95
|
},
|
|
81
96
|
"states": [
|
|
@@ -39,6 +39,29 @@ slots:
|
|
|
39
39
|
description: >-
|
|
40
40
|
Author-supplied page DOM. Expected structure — aside[data-sidebar] for
|
|
41
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.
|
|
42
65
|
|
|
43
66
|
states:
|
|
44
67
|
- name: idle
|
|
@@ -13,7 +13,19 @@ app-shell-ui > main {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/* ── Main > header (topbar) ──
|
|
16
|
-
Contains: sidebar toggle, breadcrumb, spacer, action buttons
|
|
16
|
+
Contains: sidebar toggle, breadcrumb, spacer, action buttons.
|
|
17
|
+
|
|
18
|
+
Slot contract (shared with > main > footer and [data-sidebar] >
|
|
19
|
+
header/footer) — identical to card-ui / drawer-ui / adia-editor-ui:
|
|
20
|
+
[slot="icon"] leading glyph
|
|
21
|
+
[slot="heading"] primary label; strong weight + strong fg
|
|
22
|
+
[slot="description"] secondary metadata; muted fg + --a-ui-sm
|
|
23
|
+
[slot="action"] trailing cluster; first pushes to end
|
|
24
|
+
The legacy data-sidebar-toggle / breadcrumb-ui / <span data-spacer>
|
|
25
|
+
/ <div data-actions> hooks (see app-shell.helpers.css) remain
|
|
26
|
+
fully supported — slots are additive, not a replacement. Use slots
|
|
27
|
+
for simpler chrome surfaces; keep breadcrumb + data-actions for
|
|
28
|
+
docs-style shells where those conventions carry semantic weight. */
|
|
17
29
|
app-shell-ui > main > header {
|
|
18
30
|
display: flex;
|
|
19
31
|
align-items: center;
|
|
@@ -25,6 +37,39 @@ app-shell-ui > main > header {
|
|
|
25
37
|
flex-shrink: 0;
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
app-shell-ui > main > header > [slot="icon"] {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
color: var(--page-header-fg-muted);
|
|
45
|
+
}
|
|
46
|
+
app-shell-ui > main > header > [slot="heading"] {
|
|
47
|
+
font-weight: var(--a-weight-medium);
|
|
48
|
+
color: var(--a-fg);
|
|
49
|
+
}
|
|
50
|
+
app-shell-ui > main > header > [slot="description"] {
|
|
51
|
+
color: var(--page-header-fg-muted);
|
|
52
|
+
font-size: var(--a-ui-sm);
|
|
53
|
+
}
|
|
54
|
+
app-shell-ui > main > header > [slot="action"] {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: var(--page-actions-gap);
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
margin-inline-start: auto;
|
|
60
|
+
}
|
|
61
|
+
app-shell-ui > main > header > [slot="action"] ~ [slot="action"] {
|
|
62
|
+
margin-inline-start: 0;
|
|
63
|
+
}
|
|
64
|
+
/* Dual-cluster: leading group on inline-start, trailing cluster on inline-end. */
|
|
65
|
+
app-shell-ui > main > header > [slot="action-leading"] {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: var(--page-actions-gap);
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
margin-inline-end: auto;
|
|
71
|
+
}
|
|
72
|
+
|
|
28
73
|
/* ── Main > section (scroll container) ──
|
|
29
74
|
Wraps [data-content-root]. Scrolls vertically, hides scrollbar. */
|
|
30
75
|
app-shell-ui > main > section {
|
|
@@ -39,7 +84,11 @@ app-shell-ui > main > section {
|
|
|
39
84
|
app-shell-ui > main > section::-webkit-scrollbar { display: none; }
|
|
40
85
|
|
|
41
86
|
/* ── Main > footer (status bar) ──
|
|
42
|
-
|
|
87
|
+
Legacy pattern: last-child auto-pushed to trailing edge via
|
|
88
|
+
margin-inline-start: auto (works when authors use a simple
|
|
89
|
+
<span>…</span><span>version</span> shape).
|
|
90
|
+
Slot pattern: same icon / heading / description / action vocabulary
|
|
91
|
+
as > main > header (see comment block above). */
|
|
43
92
|
app-shell-ui > main > footer {
|
|
44
93
|
flex-shrink: 0;
|
|
45
94
|
display: flex;
|
|
@@ -53,6 +102,42 @@ app-shell-ui > main > footer {
|
|
|
53
102
|
color: var(--page-header-fg-muted);
|
|
54
103
|
}
|
|
55
104
|
|
|
56
|
-
|
|
105
|
+
/* Legacy: bare <span>…</span><span>version</span> shapes. Only kicks
|
|
106
|
+
in when NO slot="action" is present, so it doesn't fight the slot
|
|
107
|
+
rule below. */
|
|
108
|
+
app-shell-ui > main > footer:not(:has(> [slot="action"])) > :last-child {
|
|
57
109
|
margin-inline-start: auto;
|
|
58
110
|
}
|
|
111
|
+
|
|
112
|
+
app-shell-ui > main > footer > [slot="icon"] {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
color: var(--page-header-fg-muted);
|
|
117
|
+
}
|
|
118
|
+
app-shell-ui > main > footer > [slot="heading"] {
|
|
119
|
+
font-weight: var(--a-weight-medium);
|
|
120
|
+
color: var(--a-fg);
|
|
121
|
+
}
|
|
122
|
+
app-shell-ui > main > footer > [slot="description"] {
|
|
123
|
+
color: var(--page-header-fg-muted);
|
|
124
|
+
font-size: var(--a-ui-sm);
|
|
125
|
+
}
|
|
126
|
+
app-shell-ui > main > footer > [slot="action"] {
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: var(--page-actions-gap);
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
margin-inline-start: auto;
|
|
132
|
+
}
|
|
133
|
+
app-shell-ui > main > footer > [slot="action"] ~ [slot="action"] {
|
|
134
|
+
margin-inline-start: 0;
|
|
135
|
+
}
|
|
136
|
+
/* Dual-cluster: leading group on inline-start, trailing cluster on inline-end. */
|
|
137
|
+
app-shell-ui > main > footer > [slot="action-leading"] {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: var(--page-actions-gap);
|
|
141
|
+
flex-shrink: 0;
|
|
142
|
+
margin-inline-end: auto;
|
|
143
|
+
}
|
|
@@ -60,7 +60,13 @@
|
|
|
60
60
|
width: var(--page-sidebar-width-trailing);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/* ── Sidebar header / footer ──
|
|
63
|
+
/* ── Sidebar header / footer ──
|
|
64
|
+
Share the same slot contract as > main > header/footer (see
|
|
65
|
+
app-shell.main.css top-of-file comment): icon / heading /
|
|
66
|
+
description / action. Sidebar chromes are typically single-child
|
|
67
|
+
(a workspace-select or user-select) so the slot rules rarely come
|
|
68
|
+
into play — but they stay consistent for authors that compose
|
|
69
|
+
richer sidebar chromes. */
|
|
64
70
|
[data-sidebar] > header,
|
|
65
71
|
[data-sidebar] > footer {
|
|
66
72
|
display: flex;
|
|
@@ -83,6 +89,45 @@
|
|
|
83
89
|
border-top: var(--page-border);
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
[data-sidebar] > header > [slot="icon"],
|
|
93
|
+
[data-sidebar] > footer > [slot="icon"] {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
color: var(--page-header-fg-muted);
|
|
98
|
+
}
|
|
99
|
+
[data-sidebar] > header > [slot="heading"],
|
|
100
|
+
[data-sidebar] > footer > [slot="heading"] {
|
|
101
|
+
font-weight: var(--a-weight-medium);
|
|
102
|
+
color: var(--a-fg);
|
|
103
|
+
}
|
|
104
|
+
[data-sidebar] > header > [slot="description"],
|
|
105
|
+
[data-sidebar] > footer > [slot="description"] {
|
|
106
|
+
color: var(--page-header-fg-muted);
|
|
107
|
+
font-size: var(--a-ui-sm);
|
|
108
|
+
}
|
|
109
|
+
[data-sidebar] > header > [slot="action"],
|
|
110
|
+
[data-sidebar] > footer > [slot="action"] {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: var(--page-actions-gap);
|
|
114
|
+
flex-shrink: 0;
|
|
115
|
+
margin-inline-start: auto;
|
|
116
|
+
}
|
|
117
|
+
[data-sidebar] > header > [slot="action"] ~ [slot="action"],
|
|
118
|
+
[data-sidebar] > footer > [slot="action"] ~ [slot="action"] {
|
|
119
|
+
margin-inline-start: 0;
|
|
120
|
+
}
|
|
121
|
+
/* Dual-cluster: leading group on inline-start, trailing cluster on inline-end. */
|
|
122
|
+
[data-sidebar] > header > [slot="action-leading"],
|
|
123
|
+
[data-sidebar] > footer > [slot="action-leading"] {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: var(--page-actions-gap);
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
margin-inline-end: auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
/* ── Sidebar section (scrollable body) ── */
|
|
87
132
|
[data-sidebar] > section {
|
|
88
133
|
flex: 1;
|
|
@@ -108,8 +108,6 @@
|
|
|
108
108
|
--nav-item-trailing-border: var(--a-border-subtle);
|
|
109
109
|
--nav-item-trailing-radius: var(--a-radius-sm);
|
|
110
110
|
--nav-item-trailing-px: var(--a-space-0-5);
|
|
111
|
-
--nav-item-child-height: var(--a-size-sm);
|
|
112
|
-
--nav-item-child-font: var(--a-ui-sm);
|
|
113
111
|
|
|
114
112
|
/* Global icon size bump */
|
|
115
113
|
--a-icon-size: 18px;
|