@adia-ai/web-components 0.0.22 → 0.0.24

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.
@@ -4,355 +4,362 @@
4
4
  Layout aligns with card-ui: header uses a 3-column grid
5
5
  (icon | heading+description | action+close), footer uses a
6
6
  flex row with [slot="description"] + [slot="action"] semantics.
7
+
8
+ Safari 17.x bug (Flavor C — top-layer): every panel rule is a
9
+ `:scope[attr] > [slot="dialog"][dyn-attr] > [slot="panel"]` chain
10
+ that fails to match in Safari's top-layer (the panel renders
11
+ invisible). Plus Safari throttles requestAnimationFrame while a
12
+ top-layer dialog is open. Workaround: dissolve @scope for everything
13
+ except the token block (kept on `:where(drawer-ui)` so component-API
14
+ tokens still resolve via inheritance). Plain `drawer-ui[…]` selectors
15
+ match correctly in the top layer.
7
16
  ═══════════════════════════════════════════════════════════════ */
8
17
 
9
- @scope (drawer-ui) {
10
- :where(:scope) {
11
- /* ── Geometry ── */
12
- --drawer-width: 24rem;
13
- --drawer-height: 24rem;
14
- --drawer-inset: var(--a-space-4);
15
- --drawer-header-pad: var(--drawer-inset);
16
- --drawer-footer-pad: var(--drawer-inset);
17
- --drawer-header-gap: var(--a-space-2);
18
- --drawer-footer-gap: var(--a-space-2);
19
-
20
- /* ── Motion ── */
21
- --drawer-duration: var(--a-duration);
22
- --drawer-easing: var(--a-easing-out);
23
-
24
- /* ── Colors ── */
25
- --drawer-bg: var(--a-canvas-1);
26
- --drawer-border: var(--a-border-subtle);
27
- --drawer-divider: var(--a-border-subtle);
28
- --drawer-shadow: var(--a-shadow-lg);
29
- --drawer-backdrop: var(--a-scrim-dialog);
30
- --drawer-fg: var(--a-fg);
31
- --drawer-heading-fg: var(--a-fg-strong);
32
- --drawer-description-fg: var(--a-fg-subtle);
33
-
34
- /* ── Typography ── */
35
- --drawer-font-family: var(--a-font-family);
36
- --drawer-heading-size: var(--a-ui-lg);
37
- --drawer-heading-weight: var(--a-weight-semibold);
38
- --drawer-description-size: var(--a-caption-size);
39
- }
40
-
41
- :scope {
42
- box-sizing: border-box;
43
- display: contents;
44
- }
45
-
46
- /* ── Size variants ── */
47
- :scope[size="sm"] { --drawer-width: min(18rem, 90vw); }
48
- :scope[size="md"] { --drawer-width: min(24rem, 90vw); }
49
- :scope[size="lg"] { --drawer-width: min(36rem, 90vw); }
50
-
51
- /* ═══════ Dialog shell ═══════ */
52
-
53
- > [slot="dialog"]:not([open]) { display: none; }
54
-
55
- > [slot="dialog"][open] {
56
- box-sizing: border-box;
57
- position: fixed;
58
- inset: 0;
59
- width: 100%;
60
- height: 100%;
61
- max-width: none;
62
- max-height: none;
63
- margin: 0;
64
- padding: 0;
65
- border: none;
66
- background: transparent;
67
- overflow: clip;
68
- }
69
-
70
- > [slot="dialog"]::backdrop {
71
- background: var(--drawer-backdrop);
72
- opacity: 0;
73
- transition: opacity var(--drawer-duration) var(--drawer-easing);
74
- }
75
- > [slot="dialog"][data-open]::backdrop { opacity: 1; }
76
- > [slot="dialog"][data-closing]::backdrop { opacity: 0; }
77
-
78
- /* ═══════ Panel ═══════ */
79
-
80
- > [slot="dialog"] > [slot="panel"] {
81
- box-sizing: border-box;
82
- position: absolute;
83
- display: flex;
84
- flex-direction: column;
85
- background: var(--drawer-bg);
86
- color: var(--drawer-fg);
87
- font-family: var(--drawer-font-family);
88
- box-shadow: var(--drawer-shadow);
89
- overflow-y: auto;
90
- opacity: 0;
91
- }
92
-
93
- /* ── Side: right (default) ── */
94
- :scope:not([side]) > [slot="dialog"] > [slot="panel"],
95
- :scope[side="right"] > [slot="dialog"] > [slot="panel"] {
96
- top: 0; right: 0; bottom: 0;
97
- width: var(--drawer-width);
98
- border-left: 1px solid var(--drawer-border);
99
- transform: translateX(100%);
100
- }
101
- :scope:not([side]) > [slot="dialog"][data-open] > [slot="panel"],
102
- :scope[side="right"] > [slot="dialog"][data-open] > [slot="panel"] {
103
- transition: transform var(--drawer-duration) var(--drawer-easing),
104
- opacity var(--drawer-duration) var(--drawer-easing);
105
- transform: translateX(0);
106
- opacity: 1;
107
- }
108
- :scope:not([side]) > [slot="dialog"][data-closing] > [slot="panel"],
109
- :scope[side="right"] > [slot="dialog"][data-closing] > [slot="panel"] {
110
- transition: transform var(--drawer-duration) var(--drawer-easing),
111
- opacity var(--drawer-duration) var(--drawer-easing);
112
- transform: translateX(100%);
113
- opacity: 0;
114
- }
115
-
116
- /* ── Side: left ── */
117
- :scope[side="left"] > [slot="dialog"] > [slot="panel"] {
118
- top: 0; left: 0; bottom: 0;
119
- width: var(--drawer-width);
120
- border-right: 1px solid var(--drawer-border);
121
- transform: translateX(-100%);
122
- }
123
- :scope[side="left"] > [slot="dialog"][data-open] > [slot="panel"] {
124
- transition: transform var(--drawer-duration) var(--drawer-easing),
125
- opacity var(--drawer-duration) var(--drawer-easing);
126
- transform: translateX(0); opacity: 1;
127
- }
128
- :scope[side="left"] > [slot="dialog"][data-closing] > [slot="panel"] {
129
- transition: transform var(--drawer-duration) var(--drawer-easing),
130
- opacity var(--drawer-duration) var(--drawer-easing);
131
- transform: translateX(-100%); opacity: 0;
132
- }
133
-
134
- /* ── Side: bottom ── */
135
- :scope[side="bottom"] > [slot="dialog"] > [slot="panel"] {
136
- bottom: 0; left: 0; right: 0;
137
- height: var(--drawer-height);
138
- border-top: 1px solid var(--drawer-border);
139
- transform: translateY(100%);
140
- }
141
- :scope[side="bottom"] > [slot="dialog"][data-open] > [slot="panel"] {
142
- transition: transform var(--drawer-duration) var(--drawer-easing),
143
- opacity var(--drawer-duration) var(--drawer-easing);
144
- transform: translateY(0); opacity: 1;
145
- }
146
- :scope[side="bottom"] > [slot="dialog"][data-closing] > [slot="panel"] {
147
- transition: transform var(--drawer-duration) var(--drawer-easing),
148
- opacity var(--drawer-duration) var(--drawer-easing);
149
- transform: translateY(100%); opacity: 0;
150
- }
151
-
152
- /* ── Side: top ── */
153
- :scope[side="top"] > [slot="dialog"] > [slot="panel"] {
154
- top: 0; left: 0; right: 0;
155
- height: var(--drawer-height);
156
- border-bottom: 1px solid var(--drawer-border);
157
- transform: translateY(-100%);
158
- }
159
- :scope[side="top"] > [slot="dialog"][data-open] > [slot="panel"] {
160
- transition: transform var(--drawer-duration) var(--drawer-easing),
161
- opacity var(--drawer-duration) var(--drawer-easing);
162
- transform: translateY(0); opacity: 1;
163
- }
164
- :scope[side="top"] > [slot="dialog"][data-closing] > [slot="panel"] {
165
- transition: transform var(--drawer-duration) var(--drawer-easing),
166
- opacity var(--drawer-duration) var(--drawer-easing);
167
- transform: translateY(-100%); opacity: 0;
168
- }
169
-
170
- /* ═══════ Header card-ui-aligned grid ═══════
171
- Block by default (fallback for empty/free-form content).
172
- Grid activates when any slotted child is present. Column template
173
- adapts to the presence of [slot="icon"] and [slot="action"|"close"]. */
174
-
175
- [slot="panel"] > [slot="header"] {
176
- display: block;
177
- padding: var(--drawer-header-pad);
178
- border-bottom: 1px solid var(--drawer-divider);
179
- color: var(--drawer-heading-fg);
180
- flex-shrink: 0;
181
- /* Sticky so that with multiple <section> siblings scrolling below,
182
- the header stays pinned to the top of the panel. */
183
- position: sticky;
184
- top: 0;
185
- background: var(--drawer-bg);
186
- z-index: 1;
187
- }
188
-
189
- [slot="panel"] > [slot="header"]:has(> [slot]) {
190
- display: grid;
191
- gap: var(--drawer-header-gap);
192
- align-items: center;
193
- }
194
-
195
- [slot="panel"] > [slot="header"][padding] {
196
- background: var(--a-canvas-0-scrim);
197
- }
198
-
199
- [slot="panel"] > [slot="header"][center] {
200
- text-align: center;
201
- justify-items: center;
202
- }
203
-
204
- /* Column templates mirror card-ui. `:has(>)` restricts to direct
205
- children so that nested slot="icon" (e.g. inside an <avatar-ui>) doesn't
206
- accidentally activate the icon column. Treat [slot="close"] as an
207
- action-column occupant. */
208
- [slot="panel"] > [slot="header"]:has(> [slot="icon"]):has(> :is([slot="action"], [slot="close"])) {
209
- grid-template-columns: max-content 1fr max-content;
210
- }
211
- [slot="panel"] > [slot="header"]:has(> [slot="icon"]):not(:has(> :is([slot="action"], [slot="close"]))) {
212
- grid-template-columns: max-content 1fr;
213
- }
214
- [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])):has(> :is([slot="action"], [slot="close"])) {
215
- grid-template-columns: 1fr max-content;
216
- }
217
- [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])):not(:has(> :is([slot="action"], [slot="close"]))) {
218
- grid-template-columns: 1fr;
219
- }
220
-
221
- /* Icon first column */
222
- [slot="panel"] > [slot="header"] > [slot="icon"] {
223
- grid-column: 1;
224
- grid-row: 1;
225
- align-self: center;
226
- display: flex;
227
- align-items: center;
228
- justify-content: center;
229
- }
230
- [slot="panel"] > [slot="header"]:has(> :is([slot="description"], p, small)) > [slot="icon"] {
231
- grid-row: 1 / span 2;
232
- align-self: start;
233
- }
234
-
235
- /* Heading row 1 */
236
- [slot="panel"] > [slot="header"] > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
237
- [slot="panel"] > [slot="header"] > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
238
- grid-row: 1;
239
- line-height: 1.3;
240
- margin: 0;
241
- font-size: var(--drawer-heading-size);
242
- font-weight: var(--drawer-heading-weight);
243
- color: var(--drawer-heading-fg);
244
- }
245
- [slot="panel"] > [slot="header"] > [slot="heading"] {
246
- display: flex;
247
- align-items: center;
248
- gap: var(--drawer-header-gap);
249
- }
250
- [slot="panel"] > [slot="header"]:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) {
251
- grid-column: 2;
252
- }
253
- [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) {
254
- grid-column: 1;
255
- }
256
-
257
- /* Description row 2 */
258
- [slot="panel"] > [slot="header"] > :is([slot="description"], p, small) {
259
- grid-row: 2;
260
- grid-column: 1 / -1;
261
- line-height: 1.4;
262
- margin: 0;
263
- font-size: var(--drawer-description-size);
264
- color: var(--drawer-description-fg);
265
- }
266
- [slot="panel"] > [slot="header"]:has(> [slot="icon"]) > :is([slot="description"], p, small) {
267
- grid-column: 2 / -1;
268
- }
269
-
270
- /* Action + close row 1, last column */
271
- [slot="panel"] > [slot="header"] > :is([slot="action"], [slot="close"]) {
272
- justify-self: end;
273
- align-self: center;
274
- grid-row: 1;
275
- grid-column: -2 / -1;
276
- display: flex;
277
- align-items: center;
278
- gap: var(--drawer-header-gap);
279
- }
280
-
281
- /* ═══════ Body (section) ═══════
282
- Padded by default. Multiple <section> siblings are allowed they stack
283
- inside the panel's own scroll region (see panel's overflow-y + sticky
284
- header/footer above). [bleed] removes padding; [padding] adds a canvas
285
- scrim background (card-ui parity). */
286
-
287
- [slot="panel"] > [slot="body"] {
288
- padding: var(--drawer-inset);
289
- /* Last body takes any remaining vertical slack so the footer hugs the
290
- bottom even when content is shorter than the viewport. */
291
- flex: 0 0 auto;
292
- }
293
- [slot="panel"] > [slot="body"]:last-of-type { flex: 1 0 auto; }
294
- [slot="panel"] > [slot="body"] + [slot="body"] { padding-top: 0; }
295
- [slot="panel"] > [slot="body"][bleed] { padding: 0; }
296
- [slot="panel"] > [slot="body"][padding] { background: var(--a-canvas-0-scrim); }
297
-
298
- /* ═══════ Footer ═══════
299
- Block default — single non-slotted child (e.g. <grid-ui>, <col-ui>, raw
300
- paragraph) stretches full-width naturally. Activate flex-row only when a
301
- direct slotted child is present, or there are 2+ children — that's the
302
- action-row case. Mirrors card-ui footer semantics. */
303
-
304
- [slot="panel"] > [slot="footer"] {
305
- display: block;
306
- padding: var(--drawer-footer-pad);
307
- border-top: 1px solid var(--drawer-divider);
308
- flex-shrink: 0;
309
- /* Sticky to the bottom so action buttons stay visible during scroll. */
310
- position: sticky;
311
- bottom: 0;
312
- background: var(--drawer-bg);
313
- z-index: 1;
314
- }
315
-
316
- [slot="panel"] > [slot="footer"]:has(> [slot]),
317
- [slot="panel"] > [slot="footer"]:has(> :nth-child(2)) {
318
- display: flex;
319
- flex-wrap: wrap;
320
- align-items: center;
321
- gap: var(--drawer-footer-gap);
322
- }
323
-
324
- [slot="panel"] > [slot="footer"][justify="end"] {
325
- justify-content: flex-end;
326
- }
327
-
328
- [slot="panel"] > [slot="footer"]:has(> :is([slot="description"], p, small)):has(> [slot="action"]) {
329
- justify-content: space-between;
330
- }
331
-
332
- [slot="panel"] > [slot="footer"] :is([slot="description"], p, small) {
333
- font-size: var(--drawer-description-size);
334
- color: var(--drawer-description-fg);
335
- flex: 1;
336
- min-width: 0;
337
- margin: 0;
338
- }
339
-
340
- [slot="panel"] > [slot="footer"] [slot="action"] {
341
- margin-inline-start: auto;
342
- display: flex;
343
- gap: var(--drawer-footer-gap);
344
- }
345
-
346
- [slot="panel"] > [slot="footer"] > [slot="action"] ~ [slot="action"] {
347
- margin-inline-start: 0;
348
- }
349
-
350
- /* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
351
- edge, trailing action cluster on the inline-end. The margin-inline-end:
352
- auto on the leading slot fills the gap between the two groups. */
353
- [slot="panel"] > [slot="footer"] > [slot="action-leading"] {
354
- margin-inline-end: auto;
355
- display: flex;
356
- gap: var(--drawer-footer-gap);
357
- }
18
+ :where(drawer-ui) {
19
+ /* ── Geometry ── */
20
+ --drawer-width: 24rem;
21
+ --drawer-height: 24rem;
22
+ --drawer-inset: var(--a-space-4);
23
+ --drawer-header-pad: var(--drawer-inset);
24
+ --drawer-footer-pad: var(--drawer-inset);
25
+ --drawer-header-gap: var(--a-space-2);
26
+ --drawer-footer-gap: var(--a-space-2);
27
+
28
+ /* ── Motion ── */
29
+ --drawer-duration: var(--a-duration);
30
+ --drawer-easing: var(--a-easing-out);
31
+
32
+ /* ── Colors ── */
33
+ --drawer-bg: var(--a-canvas-1);
34
+ --drawer-border: var(--a-border-subtle);
35
+ --drawer-divider: var(--a-border-subtle);
36
+ --drawer-shadow: var(--a-shadow-lg);
37
+ --drawer-backdrop: var(--a-scrim-dialog);
38
+ --drawer-fg: var(--a-fg);
39
+ --drawer-heading-fg: var(--a-fg-strong);
40
+ --drawer-description-fg: var(--a-fg-subtle);
41
+
42
+ /* ── Typography ── */
43
+ --drawer-font-family: var(--a-font-family);
44
+ --drawer-heading-size: var(--a-ui-lg);
45
+ --drawer-heading-weight: var(--a-weight-semibold);
46
+ --drawer-description-size: var(--a-caption-size);
47
+ }
48
+
49
+ drawer-ui {
50
+ box-sizing: border-box;
51
+ display: contents;
52
+ }
53
+
54
+ /* ── Size variants ── */
55
+ drawer-ui[size="sm"] { --drawer-width: min(18rem, 90vw); }
56
+ drawer-ui[size="md"] { --drawer-width: min(24rem, 90vw); }
57
+ drawer-ui[size="lg"] { --drawer-width: min(36rem, 90vw); }
58
+
59
+ /* ═══════ Dialog shell ═══════ */
60
+
61
+ drawer-ui > [slot="dialog"]:not([open]) { display: none; }
62
+
63
+ drawer-ui > [slot="dialog"][open] {
64
+ box-sizing: border-box;
65
+ position: fixed;
66
+ inset: 0;
67
+ width: 100%;
68
+ height: 100%;
69
+ max-width: none;
70
+ max-height: none;
71
+ margin: 0;
72
+ padding: 0;
73
+ border: none;
74
+ background: transparent;
75
+ overflow: clip;
76
+ }
77
+
78
+ drawer-ui > [slot="dialog"]::backdrop {
79
+ background: var(--drawer-backdrop);
80
+ opacity: 0;
81
+ transition: opacity var(--drawer-duration) var(--drawer-easing);
82
+ }
83
+ drawer-ui > [slot="dialog"][data-open]::backdrop { opacity: 1; }
84
+ drawer-ui > [slot="dialog"][data-closing]::backdrop { opacity: 0; }
85
+
86
+ /* ═══════ Panel ═══════ */
87
+
88
+ drawer-ui > [slot="dialog"] > [slot="panel"] {
89
+ box-sizing: border-box;
90
+ position: absolute;
91
+ display: flex;
92
+ flex-direction: column;
93
+ background: var(--drawer-bg);
94
+ color: var(--drawer-fg);
95
+ font-family: var(--drawer-font-family);
96
+ box-shadow: var(--drawer-shadow);
97
+ overflow-y: auto;
98
+ opacity: 0;
99
+ }
100
+
101
+ /* ── Side: right (default) ── */
102
+ drawer-ui:not([side]) > [slot="dialog"] > [slot="panel"],
103
+ drawer-ui[side="right"] > [slot="dialog"] > [slot="panel"] {
104
+ top: 0; right: 0; bottom: 0;
105
+ width: var(--drawer-width);
106
+ border-left: 1px solid var(--drawer-border);
107
+ transform: translateX(100%);
108
+ }
109
+ drawer-ui:not([side]) > [slot="dialog"][data-open] > [slot="panel"],
110
+ drawer-ui[side="right"] > [slot="dialog"][data-open] > [slot="panel"] {
111
+ transition: transform var(--drawer-duration) var(--drawer-easing),
112
+ opacity var(--drawer-duration) var(--drawer-easing);
113
+ transform: translateX(0);
114
+ opacity: 1;
115
+ }
116
+ drawer-ui:not([side]) > [slot="dialog"][data-closing] > [slot="panel"],
117
+ drawer-ui[side="right"] > [slot="dialog"][data-closing] > [slot="panel"] {
118
+ transition: transform var(--drawer-duration) var(--drawer-easing),
119
+ opacity var(--drawer-duration) var(--drawer-easing);
120
+ transform: translateX(100%);
121
+ opacity: 0;
122
+ }
123
+
124
+ /* ── Side: left ── */
125
+ drawer-ui[side="left"] > [slot="dialog"] > [slot="panel"] {
126
+ top: 0; left: 0; bottom: 0;
127
+ width: var(--drawer-width);
128
+ border-right: 1px solid var(--drawer-border);
129
+ transform: translateX(-100%);
130
+ }
131
+ drawer-ui[side="left"] > [slot="dialog"][data-open] > [slot="panel"] {
132
+ transition: transform var(--drawer-duration) var(--drawer-easing),
133
+ opacity var(--drawer-duration) var(--drawer-easing);
134
+ transform: translateX(0); opacity: 1;
135
+ }
136
+ drawer-ui[side="left"] > [slot="dialog"][data-closing] > [slot="panel"] {
137
+ transition: transform var(--drawer-duration) var(--drawer-easing),
138
+ opacity var(--drawer-duration) var(--drawer-easing);
139
+ transform: translateX(-100%); opacity: 0;
140
+ }
141
+
142
+ /* ── Side: bottom ── */
143
+ drawer-ui[side="bottom"] > [slot="dialog"] > [slot="panel"] {
144
+ bottom: 0; left: 0; right: 0;
145
+ height: var(--drawer-height);
146
+ border-top: 1px solid var(--drawer-border);
147
+ transform: translateY(100%);
148
+ }
149
+ drawer-ui[side="bottom"] > [slot="dialog"][data-open] > [slot="panel"] {
150
+ transition: transform var(--drawer-duration) var(--drawer-easing),
151
+ opacity var(--drawer-duration) var(--drawer-easing);
152
+ transform: translateY(0); opacity: 1;
153
+ }
154
+ drawer-ui[side="bottom"] > [slot="dialog"][data-closing] > [slot="panel"] {
155
+ transition: transform var(--drawer-duration) var(--drawer-easing),
156
+ opacity var(--drawer-duration) var(--drawer-easing);
157
+ transform: translateY(100%); opacity: 0;
158
+ }
159
+
160
+ /* ── Side: top ── */
161
+ drawer-ui[side="top"] > [slot="dialog"] > [slot="panel"] {
162
+ top: 0; left: 0; right: 0;
163
+ height: var(--drawer-height);
164
+ border-bottom: 1px solid var(--drawer-border);
165
+ transform: translateY(-100%);
166
+ }
167
+ drawer-ui[side="top"] > [slot="dialog"][data-open] > [slot="panel"] {
168
+ transition: transform var(--drawer-duration) var(--drawer-easing),
169
+ opacity var(--drawer-duration) var(--drawer-easing);
170
+ transform: translateY(0); opacity: 1;
171
+ }
172
+ drawer-ui[side="top"] > [slot="dialog"][data-closing] > [slot="panel"] {
173
+ transition: transform var(--drawer-duration) var(--drawer-easing),
174
+ opacity var(--drawer-duration) var(--drawer-easing);
175
+ transform: translateY(-100%); opacity: 0;
176
+ }
177
+
178
+ /* ═══════ Header — card-ui-aligned grid ═══════
179
+ Block by default (fallback for empty/free-form content).
180
+ Grid activates when any slotted child is present. Column template
181
+ adapts to the presence of [slot="icon"] and [slot="action"|"close"]. */
182
+
183
+ drawer-ui [slot="panel"] > [slot="header"] {
184
+ display: block;
185
+ padding: var(--drawer-header-pad);
186
+ border-bottom: 1px solid var(--drawer-divider);
187
+ color: var(--drawer-heading-fg);
188
+ flex-shrink: 0;
189
+ /* Sticky so that with multiple <section> siblings scrolling below,
190
+ the header stays pinned to the top of the panel. */
191
+ position: sticky;
192
+ top: 0;
193
+ background: var(--drawer-bg);
194
+ z-index: 1;
195
+ }
196
+
197
+ drawer-ui [slot="panel"] > [slot="header"]:has(> [slot]) {
198
+ display: grid;
199
+ gap: var(--drawer-header-gap);
200
+ align-items: center;
201
+ }
202
+
203
+ drawer-ui [slot="panel"] > [slot="header"][padding] {
204
+ background: var(--a-canvas-0-scrim);
205
+ }
206
+
207
+ drawer-ui [slot="panel"] > [slot="header"][center] {
208
+ text-align: center;
209
+ justify-items: center;
210
+ }
211
+
212
+ /* Column templates — mirror card-ui. `:has(> …)` restricts to direct
213
+ children so that nested slot="icon" (e.g. inside an <avatar-ui>) doesn't
214
+ accidentally activate the icon column. Treat [slot="close"] as an
215
+ action-column occupant. */
216
+ drawer-ui [slot="panel"] > [slot="header"]:has(> [slot="icon"]):has(> :is([slot="action"], [slot="close"])) {
217
+ grid-template-columns: max-content 1fr max-content;
218
+ }
219
+ drawer-ui [slot="panel"] > [slot="header"]:has(> [slot="icon"]):not(:has(> :is([slot="action"], [slot="close"]))) {
220
+ grid-template-columns: max-content 1fr;
221
+ }
222
+ drawer-ui [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])):has(> :is([slot="action"], [slot="close"])) {
223
+ grid-template-columns: 1fr max-content;
224
+ }
225
+ drawer-ui [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])):not(:has(> :is([slot="action"], [slot="close"]))) {
226
+ grid-template-columns: 1fr;
227
+ }
228
+
229
+ /* Icon — first column */
230
+ drawer-ui [slot="panel"] > [slot="header"] > [slot="icon"] {
231
+ grid-column: 1;
232
+ grid-row: 1;
233
+ align-self: center;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ }
238
+ drawer-ui [slot="panel"] > [slot="header"]:has(> :is([slot="description"], p, small)) > [slot="icon"] {
239
+ grid-row: 1 / span 2;
240
+ align-self: start;
241
+ }
242
+
243
+ /* Heading — row 1 */
244
+ drawer-ui [slot="panel"] > [slot="header"] > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
245
+ drawer-ui [slot="panel"] > [slot="header"] > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
246
+ grid-row: 1;
247
+ line-height: 1.3;
248
+ margin: 0;
249
+ font-size: var(--drawer-heading-size);
250
+ font-weight: var(--drawer-heading-weight);
251
+ color: var(--drawer-heading-fg);
252
+ }
253
+ drawer-ui [slot="panel"] > [slot="header"] > [slot="heading"] {
254
+ display: flex;
255
+ align-items: center;
256
+ gap: var(--drawer-header-gap);
257
+ }
258
+ drawer-ui [slot="panel"] > [slot="header"]:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) {
259
+ grid-column: 2;
260
+ }
261
+ drawer-ui [slot="panel"] > [slot="header"]:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) {
262
+ grid-column: 1;
263
+ }
264
+
265
+ /* Description — row 2 */
266
+ drawer-ui [slot="panel"] > [slot="header"] > :is([slot="description"], p, small) {
267
+ grid-row: 2;
268
+ grid-column: 1 / -1;
269
+ line-height: 1.4;
270
+ margin: 0;
271
+ font-size: var(--drawer-description-size);
272
+ color: var(--drawer-description-fg);
273
+ }
274
+ drawer-ui [slot="panel"] > [slot="header"]:has(> [slot="icon"]) > :is([slot="description"], p, small) {
275
+ grid-column: 2 / -1;
276
+ }
277
+
278
+ /* Action + close — row 1, last column */
279
+ drawer-ui [slot="panel"] > [slot="header"] > :is([slot="action"], [slot="close"]) {
280
+ justify-self: end;
281
+ align-self: center;
282
+ grid-row: 1;
283
+ grid-column: -2 / -1;
284
+ display: flex;
285
+ align-items: center;
286
+ gap: var(--drawer-header-gap);
287
+ }
288
+
289
+ /* ═══════ Body (section) ═══════
290
+ Padded by default. Multiple <section> siblings are allowed — they stack
291
+ inside the panel's own scroll region (see panel's overflow-y + sticky
292
+ header/footer above). [bleed] removes padding; [padding] adds a canvas
293
+ scrim background (card-ui parity). */
294
+
295
+ drawer-ui [slot="panel"] > [slot="body"] {
296
+ padding: var(--drawer-inset);
297
+ /* Last body takes any remaining vertical slack so the footer hugs the
298
+ bottom even when content is shorter than the viewport. */
299
+ flex: 0 0 auto;
300
+ }
301
+ drawer-ui [slot="panel"] > [slot="body"]:last-of-type { flex: 1 0 auto; }
302
+ drawer-ui [slot="panel"] > [slot="body"] + [slot="body"] { padding-top: 0; }
303
+ drawer-ui [slot="panel"] > [slot="body"][bleed] { padding: 0; }
304
+ drawer-ui [slot="panel"] > [slot="body"][padding] { background: var(--a-canvas-0-scrim); }
305
+
306
+ /* ═══════ Footer ═══════
307
+ Block default single non-slotted child (e.g. <grid-ui>, <col-ui>, raw
308
+ paragraph) stretches full-width naturally. Activate flex-row only when a
309
+ direct slotted child is present, or there are 2+ children — that's the
310
+ action-row case. Mirrors card-ui footer semantics. */
311
+
312
+ drawer-ui [slot="panel"] > [slot="footer"] {
313
+ display: block;
314
+ padding: var(--drawer-footer-pad);
315
+ border-top: 1px solid var(--drawer-divider);
316
+ flex-shrink: 0;
317
+ /* Sticky to the bottom so action buttons stay visible during scroll. */
318
+ position: sticky;
319
+ bottom: 0;
320
+ background: var(--drawer-bg);
321
+ z-index: 1;
322
+ }
323
+
324
+ drawer-ui [slot="panel"] > [slot="footer"]:has(> [slot]),
325
+ drawer-ui [slot="panel"] > [slot="footer"]:has(> :nth-child(2)) {
326
+ display: flex;
327
+ flex-wrap: wrap;
328
+ align-items: center;
329
+ gap: var(--drawer-footer-gap);
330
+ }
331
+
332
+ drawer-ui [slot="panel"] > [slot="footer"][justify="end"] {
333
+ justify-content: flex-end;
334
+ }
335
+
336
+ drawer-ui [slot="panel"] > [slot="footer"]:has(> :is([slot="description"], p, small)):has(> [slot="action"]) {
337
+ justify-content: space-between;
338
+ }
339
+
340
+ drawer-ui [slot="panel"] > [slot="footer"] :is([slot="description"], p, small) {
341
+ font-size: var(--drawer-description-size);
342
+ color: var(--drawer-description-fg);
343
+ flex: 1;
344
+ min-width: 0;
345
+ margin: 0;
346
+ }
347
+
348
+ drawer-ui [slot="panel"] > [slot="footer"] [slot="action"] {
349
+ margin-inline-start: auto;
350
+ display: flex;
351
+ gap: var(--drawer-footer-gap);
352
+ }
353
+
354
+ drawer-ui [slot="panel"] > [slot="footer"] > [slot="action"] ~ [slot="action"] {
355
+ margin-inline-start: 0;
356
+ }
357
+
358
+ /* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
359
+ edge, trailing action cluster on the inline-end. The margin-inline-end:
360
+ auto on the leading slot fills the gap between the two groups. */
361
+ drawer-ui [slot="panel"] > [slot="footer"] > [slot="action-leading"] {
362
+ margin-inline-end: auto;
363
+ display: flex;
364
+ gap: var(--drawer-footer-gap);
358
365
  }