@apliteni/apliteni-ui 0.26.0 → 0.30.0
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/README.md +2 -2
- package/package.json +1 -1
- package/react/README.md +111 -2
- package/react/dist/index.css +11 -13
- package/react/dist/index.d.ts +111 -4
- package/react/dist/index.js +811 -167
- package/src/components/back.js +57 -0
- package/src/components/command-palette.js +597 -0
- package/src/components/confirm.js +3 -2
- package/src/components/drawer.js +31 -2
- package/src/components/dropdown.js +192 -17
- package/src/components/feedback.js +2 -2
- package/src/components/index.js +5 -2
- package/src/components/loading.js +3 -2
- package/src/components/nav.js +15 -7
- package/src/components/overlay.js +67 -14
- package/src/components/pagination.js +344 -0
- package/src/components/shell.js +11 -2
- package/src/components/tabs.js +7 -1
- package/src/components/tooltip.js +237 -0
- package/src/components/topbar.js +9 -4
- package/src/index.css +4 -0
- package/src/index.js +4 -0
- package/src/inline.js +8 -0
- package/src/motion.js +43 -2
- package/src/styles/back.css +42 -0
- package/src/styles/badge.css +5 -7
- package/src/styles/base.css +8 -7
- package/src/styles/button.css +14 -0
- package/src/styles/card.css +12 -8
- package/src/styles/code.css +3 -4
- package/src/styles/command-palette.css +321 -0
- package/src/styles/confirm.css +11 -11
- package/src/styles/drawer.css +57 -15
- package/src/styles/dropdown.css +70 -11
- package/src/styles/feedback.css +2 -0
- package/src/styles/footer.css +3 -4
- package/src/styles/input.css +7 -1
- package/src/styles/layout.css +3 -2
- package/src/styles/loading.css +5 -0
- package/src/styles/nav.css +12 -8
- package/src/styles/pagination.css +124 -0
- package/src/styles/success.css +3 -2
- package/src/styles/table.css +4 -5
- package/src/styles/tabs.css +3 -0
- package/src/styles/tooltip.css +65 -0
- package/src/styles/topbar.css +3 -4
- package/src/tokens/tokens.css +14 -8
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Command palette — a text box and a ranked list, over a scrim, near the top of
|
|
3
|
+
* the window. Fully token-driven so it re-themes across accents + light/dark.
|
|
4
|
+
* Shares its scrim, its Escape and its focus trap with the drawer and the
|
|
5
|
+
* confirm (see overlay.js), and paints one layer above the drawer and one below
|
|
6
|
+
* the confirm a row opens.
|
|
7
|
+
*
|
|
8
|
+
* Local tokens so the panel and scrim re-theme cleanly:
|
|
9
|
+
* --cmdk-surface panel background --cmdk-shadow panel elevation
|
|
10
|
+
* --cmdk-scrim backdrop colour --cmdk-w panel width
|
|
11
|
+
*
|
|
12
|
+
* Two densities, one markup: the default packs a row onto one line, and
|
|
13
|
+
* `.ui-cmdk--roomy` gives the description a line of its own.
|
|
14
|
+
*
|
|
15
|
+
* why: docs/specification.md#the-command-palette */
|
|
16
|
+
|
|
17
|
+
.ui-cmdk {
|
|
18
|
+
--cmdk-surface: var(--surface-2);
|
|
19
|
+
--cmdk-shadow: var(--shadow-lg);
|
|
20
|
+
--cmdk-scrim: var(--scrim);
|
|
21
|
+
--cmdk-w: 560px;
|
|
22
|
+
--cmdk-top: 12vh;
|
|
23
|
+
--cmdk-list-h: 332px;
|
|
24
|
+
|
|
25
|
+
position: fixed;
|
|
26
|
+
inset: 0;
|
|
27
|
+
/* One above the drawer, one below the confirm: a palette is summoned
|
|
28
|
+
deliberately and must be seen, so it paints over a drawer that was already
|
|
29
|
+
open; a confirm is a question about what is under it and paints over both. */
|
|
30
|
+
z-index: calc(var(--z-overlay) + 1);
|
|
31
|
+
pointer-events: none; /* container never blocks; scrim/panel opt back in */
|
|
32
|
+
|
|
33
|
+
/* One place owns "is this palette on screen at all", and the scrim and panel
|
|
34
|
+
inherit it. Only the HIDE is transitioned, so the panel is still on screen
|
|
35
|
+
while it fades out; the show is instant (`.is-open` cancels the transition)
|
|
36
|
+
because openCommandPalette() focuses the text box in the same frame it
|
|
37
|
+
opens, and focus() on a hidden element does nothing. `linear` and not
|
|
38
|
+
var(--ease): visibility is discrete, easing a step buys nothing, and a curve
|
|
39
|
+
that leaves [0, 1] would flip it in the middle of the fade. */
|
|
40
|
+
visibility: hidden;
|
|
41
|
+
transition: visibility var(--dur-med) linear;
|
|
42
|
+
}
|
|
43
|
+
.ui-cmdk.is-open { visibility: visible; transition: none; }
|
|
44
|
+
|
|
45
|
+
/* Hit-testable only while open. The root stays visible for --dur-med after
|
|
46
|
+
closeCommandPalette() has stopped trapping, and a row still clickable in that
|
|
47
|
+
window runs the caller's command a second time. */
|
|
48
|
+
.ui-cmdk.is-open .ui-cmdk__scrim,
|
|
49
|
+
.ui-cmdk.is-open .ui-cmdk__panel { pointer-events: auto; }
|
|
50
|
+
|
|
51
|
+
.ui-cmdk__scrim {
|
|
52
|
+
position: absolute;
|
|
53
|
+
inset: 0;
|
|
54
|
+
background: var(--cmdk-scrim);
|
|
55
|
+
opacity: 0;
|
|
56
|
+
transition: opacity var(--dur-med) var(--ease);
|
|
57
|
+
}
|
|
58
|
+
.ui-cmdk.is-open .ui-cmdk__scrim { opacity: 1; }
|
|
59
|
+
|
|
60
|
+
/* Panel — near the top rather than centred: the list grows downward, and a
|
|
61
|
+
centred panel moves the text box every time the number of results changes. */
|
|
62
|
+
.ui-cmdk__panel {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: var(--cmdk-top);
|
|
65
|
+
left: 50%;
|
|
66
|
+
width: min(var(--cmdk-w), calc(100vw - var(--space-8)));
|
|
67
|
+
max-height: min(72vh, calc(100vh - var(--cmdk-top) - var(--space-8)));
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
font-family: var(--font-sans);
|
|
71
|
+
background: var(--cmdk-surface);
|
|
72
|
+
border: 1px solid var(--border);
|
|
73
|
+
border-radius: var(--radius-lg);
|
|
74
|
+
box-shadow: var(--cmdk-shadow);
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
opacity: 0;
|
|
77
|
+
transform: translate(-50%, -8px);
|
|
78
|
+
transition:
|
|
79
|
+
opacity var(--dur-med) var(--ease),
|
|
80
|
+
transform var(--dur-med) var(--ease);
|
|
81
|
+
}
|
|
82
|
+
.ui-cmdk.is-open .ui-cmdk__panel { opacity: 1; transform: translate(-50%, 0); }
|
|
83
|
+
.ui-cmdk__panel:focus-visible { outline: none; box-shadow: var(--cmdk-shadow), var(--ring); }
|
|
84
|
+
|
|
85
|
+
/* -- The text box --------------------------------------------------------- */
|
|
86
|
+
|
|
87
|
+
.ui-cmdk__search {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: var(--space-3);
|
|
91
|
+
padding: 0 var(--space-4);
|
|
92
|
+
border-bottom: 1px solid var(--border);
|
|
93
|
+
}
|
|
94
|
+
.ui-cmdk__search-ic {
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
color: var(--muted);
|
|
97
|
+
flex: none;
|
|
98
|
+
}
|
|
99
|
+
/* 2.2 of a 24 box drawn at 17px = 1.56 CSS px, which is what keeps the mark a
|
|
100
|
+
graphic rather than a smudge. why: docs/specification.md#icons-and-glyphs */
|
|
101
|
+
.ui-cmdk__search-ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; }
|
|
102
|
+
|
|
103
|
+
/* Not .ui-input: this one has no box of its own — the panel is the box — and a
|
|
104
|
+
second border inside a bordered panel reads as a form somebody has to submit. */
|
|
105
|
+
.ui-cmdk__input {
|
|
106
|
+
flex: 1;
|
|
107
|
+
min-width: 0;
|
|
108
|
+
/* Literal px, and a line-height it does not inherit: the target-size gate
|
|
109
|
+
reads declarations rather than a laid-out box, and `var()` inside a padding
|
|
110
|
+
shorthand measures as nothing there.
|
|
111
|
+
why: CONTRIBUTING.md#an-unresolved-var-measures-nothing-and-reports-green */
|
|
112
|
+
padding: 16px 0;
|
|
113
|
+
line-height: 1.5;
|
|
114
|
+
font-family: inherit;
|
|
115
|
+
font-size: var(--text-md);
|
|
116
|
+
font-weight: var(--weight-normal);
|
|
117
|
+
color: var(--text);
|
|
118
|
+
background: none;
|
|
119
|
+
border: 0;
|
|
120
|
+
/* The one control in the kit that removes the indicator and puts no
|
|
121
|
+
box-shadow: var(--ring) back, against base.css. It is the only tab stop in
|
|
122
|
+
the dialog and it holds focus from the frame it opens, so a ring would be
|
|
123
|
+
painted the whole time the palette is up and would mark nothing; the panel
|
|
124
|
+
already carries one, and the caret says where the typing goes. */
|
|
125
|
+
outline: none;
|
|
126
|
+
}
|
|
127
|
+
.ui-cmdk__input::placeholder { color: var(--muted); }
|
|
128
|
+
|
|
129
|
+
/* -- The list ------------------------------------------------------------- */
|
|
130
|
+
|
|
131
|
+
.ui-cmdk__list {
|
|
132
|
+
flex: 1;
|
|
133
|
+
min-height: 0;
|
|
134
|
+
max-height: var(--cmdk-list-h);
|
|
135
|
+
overflow-y: auto;
|
|
136
|
+
padding: var(--space-2);
|
|
137
|
+
}
|
|
138
|
+
.ui-cmdk__group + .ui-cmdk__group { margin-top: var(--space-2); }
|
|
139
|
+
|
|
140
|
+
/* Sentence case at 13px medium — the kit's label role, not a heading and not a
|
|
141
|
+
shout. A group name is the one word the reader scans for. */
|
|
142
|
+
.ui-cmdk__group-head {
|
|
143
|
+
padding: var(--space-2) var(--space-3) var(--space-1);
|
|
144
|
+
font-size: var(--text-sm);
|
|
145
|
+
font-weight: var(--weight-medium);
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Row — [icon] [label + description] [badge] [keys]. A <div role="option">, so
|
|
150
|
+
the reset a button or a link would need is not needed here. */
|
|
151
|
+
/* The four declarations after `display` are the reset a row written as a
|
|
152
|
+
<button> needs: the browser gives one a 2px outset border, `font: 400
|
|
153
|
+
13.3333px Arial`, centred text and a shrink-to-fit width, and a consumer who
|
|
154
|
+
needs the row operable from the keyboard should get the row this sheet
|
|
155
|
+
describes rather than that. `font: inherit` comes before the line-height it
|
|
156
|
+
keeps. why: docs/specification.md#a-dropdown-row-is-a-div-a-link-or-a-button */
|
|
157
|
+
.ui-cmdk__item {
|
|
158
|
+
display: flex;
|
|
159
|
+
border: 0;
|
|
160
|
+
font: inherit;
|
|
161
|
+
text-align: left;
|
|
162
|
+
width: 100%;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: var(--space-3);
|
|
165
|
+
padding: 8px 12px;
|
|
166
|
+
line-height: 1.5;
|
|
167
|
+
border-radius: var(--radius-sm);
|
|
168
|
+
color: var(--text);
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
/* The active row is painted, never faded in: the row moves on every arrow
|
|
171
|
+
press and a transition would leave two rows looking half-chosen. */
|
|
172
|
+
background: none;
|
|
173
|
+
}
|
|
174
|
+
.ui-cmdk__item[hidden] { display: none; } /* motion: still — a row the query no longer answers is gone on the next keystroke */
|
|
175
|
+
.ui-cmdk__item.is-active { background: var(--surface-3); }
|
|
176
|
+
.ui-cmdk__item.is-active .ui-cmdk__label { color: var(--strong); }
|
|
177
|
+
|
|
178
|
+
.ui-cmdk__ic {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
color: var(--dim);
|
|
181
|
+
flex: none;
|
|
182
|
+
}
|
|
183
|
+
/* 2.3 of a 24 box drawn at 16px = 1.53 CSS px. Same arithmetic as the dropdown's
|
|
184
|
+
row icon next door. why: docs/specification.md#icons-and-glyphs */
|
|
185
|
+
.ui-cmdk__ic svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.3; }
|
|
186
|
+
.ui-cmdk__item.is-active .ui-cmdk__ic { color: var(--accent); }
|
|
187
|
+
|
|
188
|
+
.ui-cmdk__main {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: baseline;
|
|
191
|
+
gap: var(--space-2);
|
|
192
|
+
min-width: 0;
|
|
193
|
+
flex: 1;
|
|
194
|
+
}
|
|
195
|
+
.ui-cmdk__label {
|
|
196
|
+
font-size: var(--text-base);
|
|
197
|
+
font-weight: var(--weight-medium);
|
|
198
|
+
color: var(--text);
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
text-overflow: ellipsis;
|
|
202
|
+
}
|
|
203
|
+
.ui-cmdk__desc {
|
|
204
|
+
font-size: var(--text-sm);
|
|
205
|
+
/* --dim and not --muted: a row's note is how two rows called INV-4812 and
|
|
206
|
+
INV-4809 are told apart, which is not something a reader may skip.
|
|
207
|
+
why: docs/specification.md#colour-and-contrast */
|
|
208
|
+
color: var(--dim);
|
|
209
|
+
white-space: nowrap;
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
text-overflow: ellipsis;
|
|
212
|
+
min-width: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ui-cmdk__badge {
|
|
216
|
+
flex: none;
|
|
217
|
+
padding: 2px 8px;
|
|
218
|
+
border-radius: var(--radius-pill);
|
|
219
|
+
border: 1px solid var(--border);
|
|
220
|
+
font-size: var(--text-xs);
|
|
221
|
+
color: var(--dim);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* Destructive rows take the kit's danger ink, never the accent, and stay quiet
|
|
225
|
+
until the reader is on them — the same pair .ui-dropdown__item.is-danger
|
|
226
|
+
writes with :hover. A palette row is reached with an arrow key far more often
|
|
227
|
+
than with a pointer, so `.is-active` carries the signal beside :hover: it is
|
|
228
|
+
the same moment, and it is the row Enter would run.
|
|
229
|
+
why: docs/specification.md#colour-and-contrast */
|
|
230
|
+
.ui-cmdk__item.is-danger .ui-cmdk__label { color: var(--muted); }
|
|
231
|
+
.ui-cmdk__item.is-danger:hover .ui-cmdk__label,
|
|
232
|
+
.ui-cmdk__item.is-danger.is-active .ui-cmdk__label { color: var(--pink); }
|
|
233
|
+
.ui-cmdk__item.is-danger.is-active .ui-cmdk__ic { color: var(--pink); }
|
|
234
|
+
|
|
235
|
+
/* A disabled row is painted, never faded. It takes --disabled-ink-bare, the ink
|
|
236
|
+
for a control with no box of its own: --disabled-surface IS --surface-2,
|
|
237
|
+
which is the panel behind it, so a disabled row paints no box a reader can
|
|
238
|
+
see and its label is read on whatever the panel is.
|
|
239
|
+
why: docs/specification.md#colour-and-contrast */
|
|
240
|
+
.ui-cmdk__item.is-disabled { cursor: default; color: var(--disabled-ink-bare); }
|
|
241
|
+
.ui-cmdk__item.is-disabled .ui-cmdk__label,
|
|
242
|
+
.ui-cmdk__item.is-disabled .ui-cmdk__desc,
|
|
243
|
+
.ui-cmdk__item.is-disabled .ui-cmdk__ic { color: var(--disabled-ink-bare); }
|
|
244
|
+
|
|
245
|
+
/* -- Keys, empty state, legend -------------------------------------------- */
|
|
246
|
+
|
|
247
|
+
.ui-cmdk__keys { display: inline-flex; gap: 4px; flex: none; }
|
|
248
|
+
.ui-cmdk__key {
|
|
249
|
+
min-width: 20px;
|
|
250
|
+
padding: 2px 5px;
|
|
251
|
+
border: 1px solid var(--border);
|
|
252
|
+
border-radius: var(--radius-xs);
|
|
253
|
+
background: var(--surface);
|
|
254
|
+
font-family: var(--font-sans);
|
|
255
|
+
font-size: var(--text-xs);
|
|
256
|
+
line-height: 1.5;
|
|
257
|
+
color: var(--dim);
|
|
258
|
+
text-align: center;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.ui-cmdk__empty {
|
|
262
|
+
margin: 0;
|
|
263
|
+
padding: var(--space-6) var(--space-4) var(--space-8);
|
|
264
|
+
text-align: center;
|
|
265
|
+
font-size: var(--text-base);
|
|
266
|
+
color: var(--dim);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.ui-cmdk__foot {
|
|
270
|
+
display: flex;
|
|
271
|
+
gap: var(--space-4);
|
|
272
|
+
align-items: center;
|
|
273
|
+
padding: var(--space-2) var(--space-4);
|
|
274
|
+
border-top: 1px solid var(--border);
|
|
275
|
+
font-size: var(--text-xs);
|
|
276
|
+
color: var(--muted);
|
|
277
|
+
}
|
|
278
|
+
.ui-cmdk__foot span { display: inline-flex; align-items: center; gap: 5px; }
|
|
279
|
+
|
|
280
|
+
/* -- Roomy ---------------------------------------------------------------- */
|
|
281
|
+
/* The same markup, two lines to a row: the description drops under the label
|
|
282
|
+
and the icon gets a tile of its own. For a palette whose rows need a sentence
|
|
283
|
+
to tell apart — an invoice from an invoice — rather than a list of verbs. */
|
|
284
|
+
|
|
285
|
+
.ui-cmdk--roomy { --cmdk-w: 640px; --cmdk-list-h: 396px; }
|
|
286
|
+
.ui-cmdk--roomy .ui-cmdk__item { padding: 9px 12px; gap: var(--space-3); }
|
|
287
|
+
.ui-cmdk--roomy .ui-cmdk__main { display: block; }
|
|
288
|
+
.ui-cmdk--roomy .ui-cmdk__label { display: block; }
|
|
289
|
+
.ui-cmdk--roomy .ui-cmdk__desc { display: block; margin-top: 1px; }
|
|
290
|
+
.ui-cmdk--roomy .ui-cmdk__ic {
|
|
291
|
+
width: 30px;
|
|
292
|
+
height: 30px;
|
|
293
|
+
align-items: center;
|
|
294
|
+
justify-content: center;
|
|
295
|
+
border-radius: var(--radius-sm);
|
|
296
|
+
background: var(--surface);
|
|
297
|
+
border: 1px solid var(--border);
|
|
298
|
+
}
|
|
299
|
+
.ui-cmdk--roomy .ui-cmdk__item.is-active .ui-cmdk__ic { background: var(--surface-2); }
|
|
300
|
+
|
|
301
|
+
/* -- Narrow --------------------------------------------------------------- */
|
|
302
|
+
/* At the kit's narrowest step the palette is the page: full width, off the top
|
|
303
|
+
edge, and the key legend goes — there is no keyboard to legend.
|
|
304
|
+
why: docs/specification.md#breakpoints */
|
|
305
|
+
@media (max-width: 560px) {
|
|
306
|
+
.ui-cmdk { --cmdk-top: var(--space-4); --cmdk-list-h: 60vh; }
|
|
307
|
+
.ui-cmdk__panel { width: calc(100vw - var(--space-4)); }
|
|
308
|
+
.ui-cmdk__foot { display: none; }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Under reduced motion the net gives every element a 0.01ms transition, so a
|
|
312
|
+
child inherits `visible` one tick after the root: the text box
|
|
313
|
+
openCommandPalette() focuses is still hidden in that frame, and focus falls to
|
|
314
|
+
<body> — the arrows and every letter then reach nothing. So an open palette
|
|
315
|
+
carries no transition inside it at all — for as long as it is open, not only
|
|
316
|
+
in the frame it opens — which is what reduced motion asked for anyway: under
|
|
317
|
+
the net nothing inside had longer than 0.01ms to run.
|
|
318
|
+
why: docs/specification.md#reduced-motion-travels-with-the-stylesheet */
|
|
319
|
+
@media (prefers-reduced-motion: reduce) {
|
|
320
|
+
.ui-cmdk.is-open * { transition: none !important; }
|
|
321
|
+
}
|
package/src/styles/confirm.css
CHANGED
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
|
|
21
21
|
position: fixed;
|
|
22
22
|
inset: 0;
|
|
23
|
-
/*
|
|
24
|
-
|
|
25
|
-
the
|
|
26
|
-
z-index: calc(var(--z-overlay) +
|
|
23
|
+
/* Top of the overlay stack — one above the palette, two above the drawer: a
|
|
24
|
+
confirm asks a question about whichever of them opened it, so it has to be
|
|
25
|
+
readable above both whatever order the three are mounted in. */
|
|
26
|
+
z-index: calc(var(--z-overlay) + 2);
|
|
27
27
|
pointer-events: none; /* container never blocks; scrim/panel opt back in */
|
|
28
28
|
|
|
29
29
|
/* Only the hide is transitioned, so the panel is still on screen while it
|
|
@@ -119,12 +119,12 @@
|
|
|
119
119
|
margin-top: var(--space-2);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
/*
|
|
122
|
+
/* Under reduced motion the net makes every change instant, and gives every
|
|
123
|
+
element a 0.01ms transition: a child inherits `visible` one tick after the
|
|
124
|
+
root, so the answer openConfirm() focuses is still hidden and focus falls to
|
|
125
|
+
<body>. So an open confirm carries no transition inside it at all — for as
|
|
126
|
+
long as it is open, not only in the frame it opens.
|
|
127
|
+
why: docs/specification.md#reduced-motion-travels-with-the-stylesheet */
|
|
123
128
|
@media (prefers-reduced-motion: reduce) {
|
|
124
|
-
.ui-confirm { transition:
|
|
125
|
-
.ui-confirm__panel {
|
|
126
|
-
transform: none;
|
|
127
|
-
transition: opacity var(--dur-fast) linear;
|
|
128
|
-
}
|
|
129
|
-
.ui-confirm__scrim { transition: opacity var(--dur-fast) linear; }
|
|
129
|
+
.ui-confirm.is-open * { transition: none !important; }
|
|
130
130
|
}
|
package/src/styles/drawer.css
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Drawer — edge-anchored overlay panel (Sheet / Slide-over): a panel that
|
|
3
3
|
* slides in from any screen edge over a scrim. Fully token-driven so it
|
|
4
4
|
* re-themes across accents + light/dark. Shares its scrim + focus trap with
|
|
5
|
-
* confirm() (see overlay.js). `prefers-reduced-motion`
|
|
6
|
-
*
|
|
5
|
+
* confirm() (see overlay.js). Under `prefers-reduced-motion` the kit's net
|
|
6
|
+
* (reduced-motion.css) makes the slide and the fade instant.
|
|
7
7
|
*
|
|
8
8
|
* Local tokens (surface/shadow/scrim) so the panel + scrim re-theme cleanly:
|
|
9
9
|
* --drawer-surface panel background --drawer-shadow panel elevation
|
|
@@ -58,10 +58,7 @@
|
|
|
58
58
|
flex-direction: column;
|
|
59
59
|
background: var(--drawer-surface);
|
|
60
60
|
box-shadow: var(--drawer-shadow);
|
|
61
|
-
|
|
62
|
-
transition:
|
|
63
|
-
transform var(--dur-med) var(--ease),
|
|
64
|
-
opacity var(--dur-med) var(--ease);
|
|
61
|
+
transition: transform var(--dur-med) var(--ease);
|
|
65
62
|
}
|
|
66
63
|
.ui-drawer__panel:focus-visible { outline: none; box-shadow: var(--drawer-shadow), var(--ring); }
|
|
67
64
|
.ui-drawer.is-open .ui-drawer__panel { transform: none; }
|
|
@@ -110,6 +107,10 @@
|
|
|
110
107
|
.ui-drawer--top.ui-drawer--lg, .ui-drawer--bottom.ui-drawer--lg { --drawer-h: 72vh; }
|
|
111
108
|
|
|
112
109
|
/* -- Slots ----------------------------------------------------------------- */
|
|
110
|
+
/* The header keeps its rule, and the footer keeps its. A drawer's normal state is a
|
|
111
|
+
long record scrolling, and those two lines are what say where the scrolling body
|
|
112
|
+
ends: the one place inside the panel a line still earns.
|
|
113
|
+
why: docs/specification.md#the-drawer */
|
|
113
114
|
.ui-drawer__header {
|
|
114
115
|
display: flex;
|
|
115
116
|
align-items: center;
|
|
@@ -174,14 +175,55 @@
|
|
|
174
175
|
flex: none;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
/* --
|
|
178
|
+
/* -- Groups and rows ------------------------------------------------------- */
|
|
179
|
+
/* drawerSection(): a heading over label/value rows. The value sits beside its
|
|
180
|
+
label rather than at the far edge, so nothing has to lead the eye across the
|
|
181
|
+
panel, and rows are held apart by space alone. Group from group is the one
|
|
182
|
+
division inside the body worth a line; the body's padding insets it, so it
|
|
183
|
+
parts the groups without cutting the panel in two.
|
|
184
|
+
why: docs/specification.md#the-drawer */
|
|
185
|
+
.ui-drawer__section + .ui-drawer__section {
|
|
186
|
+
margin-top: var(--space-6);
|
|
187
|
+
padding-top: var(--space-6);
|
|
188
|
+
border-top: 1px solid var(--border);
|
|
189
|
+
}
|
|
190
|
+
/* An h3, and it opts out of the display face for the reason .ui-drawer__title does.
|
|
191
|
+
why: docs/specification.md#typefaces */
|
|
192
|
+
.ui-drawer__section-title {
|
|
193
|
+
margin: 0 0 var(--space-2);
|
|
194
|
+
font-family: var(--font-sans);
|
|
195
|
+
font-size: var(--text-sm);
|
|
196
|
+
/* Semibold, and so outside the rank table, which has nothing at this size and
|
|
197
|
+
weight. A group heading set in the label rank would match the row labels
|
|
198
|
+
under it in size and weight both, and differ from them by colour alone. */
|
|
199
|
+
font-weight: var(--weight-semibold);
|
|
200
|
+
color: var(--strong);
|
|
201
|
+
}
|
|
202
|
+
.ui-drawer__rows {
|
|
203
|
+
display: grid;
|
|
204
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
205
|
+
column-gap: var(--space-4);
|
|
206
|
+
margin: 0;
|
|
207
|
+
}
|
|
208
|
+
.ui-drawer__row { display: contents; }
|
|
209
|
+
.ui-drawer__row dt,
|
|
210
|
+
.ui-drawer__row dd {
|
|
211
|
+
margin: 0;
|
|
212
|
+
padding-block: var(--space-1);
|
|
213
|
+
overflow-wrap: anywhere;
|
|
214
|
+
}
|
|
215
|
+
.ui-drawer__row dt { color: var(--muted); }
|
|
216
|
+
.ui-drawer__row dd { color: var(--text); font-variant-numeric: tabular-nums; }
|
|
217
|
+
.ui-drawer__rows + * { margin-top: var(--space-3); }
|
|
218
|
+
|
|
219
|
+
/* Under reduced motion the net gives every element a 0.01ms transition, so a
|
|
220
|
+
child inherits `visible` one tick after the root: the control openDrawer()
|
|
221
|
+
focuses is still hidden in that frame, and focus lands on the panel. So an
|
|
222
|
+
open drawer carries no transition inside it at all — for as long as it is
|
|
223
|
+
open, not only in the frame it opens — which is what reduced motion asked
|
|
224
|
+
for anyway: under the net nothing inside had longer than 0.01ms to run.
|
|
225
|
+
why: docs/specification.md#reduced-motion-travels-with-the-stylesheet */
|
|
178
226
|
@media (prefers-reduced-motion: reduce) {
|
|
179
|
-
.ui-drawer { transition:
|
|
180
|
-
.ui-drawer__panel {
|
|
181
|
-
transform: none !important;
|
|
182
|
-
opacity: 0;
|
|
183
|
-
transition: opacity var(--dur-fast) linear;
|
|
184
|
-
}
|
|
185
|
-
.ui-drawer.is-open .ui-drawer__panel { opacity: 1; }
|
|
186
|
-
.ui-drawer__scrim { transition: opacity var(--dur-fast) linear; }
|
|
227
|
+
.ui-drawer.is-open * { transition: none !important; }
|
|
187
228
|
}
|
|
229
|
+
|
package/src/styles/dropdown.css
CHANGED
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
/* Trailing selected tick (listbox variant) */
|
|
138
138
|
.ui-dropdown__tick { margin-left: auto; flex: none; display: none; color: var(--accent); }
|
|
139
139
|
.ui-dropdown__tick svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.4; }
|
|
140
|
-
.ui-dropdown__item.is-selected .ui-dropdown__tick { display: inline-flex; }
|
|
140
|
+
.ui-dropdown__item.is-selected .ui-dropdown__tick { display: inline-flex; } /* motion: still — a mark inside a row whose highlight already transitions; picking it closes the panel, which fades out over --dur-med */
|
|
141
141
|
.ui-dropdown__item.is-selected .ui-dropdown__label { color: var(--accent); }
|
|
142
142
|
|
|
143
143
|
/* When a row has both a badge and a tick, keep the badge from eating the margin */
|
|
@@ -145,10 +145,9 @@
|
|
|
145
145
|
|
|
146
146
|
/* Trailing status badge */
|
|
147
147
|
.ui-dropdown__badge {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
font-weight: 700;
|
|
148
|
+
/* rank: chip */
|
|
149
|
+
font-size: var(--text-xs);
|
|
150
|
+
font-weight: var(--weight-semibold);
|
|
152
151
|
padding: 2px 7px;
|
|
153
152
|
border-radius: var(--radius-pill);
|
|
154
153
|
margin-left: auto;
|
|
@@ -166,13 +165,13 @@
|
|
|
166
165
|
.ui-dropdown__item.is-danger .ui-dropdown__label { color: var(--muted); }
|
|
167
166
|
.ui-dropdown__item.is-danger:hover .ui-dropdown__label { color: var(--pink); }
|
|
168
167
|
|
|
169
|
-
/* Grouped sections
|
|
170
|
-
|
|
168
|
+
/* Grouped sections. `~` and :not([hidden]), because `+` counts a group a search
|
|
169
|
+
query has hidden and drew the divider above the first group left showing. */
|
|
170
|
+
.ui-dropdown__section:not([hidden]) ~ .ui-dropdown__section:not([hidden]) { border-top: 1px solid var(--border); margin-top: 5px; padding-top: 5px; }
|
|
171
171
|
.ui-dropdown__group {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
font-weight: 700;
|
|
172
|
+
/* rank: label */
|
|
173
|
+
font-size: var(--text-sm);
|
|
174
|
+
font-weight: var(--weight-medium);
|
|
176
175
|
color: var(--muted);
|
|
177
176
|
padding: 7px 12px 4px;
|
|
178
177
|
}
|
|
@@ -187,3 +186,63 @@
|
|
|
187
186
|
border-bottom: 1px solid var(--border);
|
|
188
187
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
189
188
|
}
|
|
189
|
+
|
|
190
|
+
/* Search — `search: true` pins a field above the rows and filters them as the
|
|
191
|
+
reader types. The rows move into a list of their own, so the field stays put
|
|
192
|
+
while the list scrolls. Every rule below keys on a class only the search
|
|
193
|
+
variant emits, so the plain dropdown renders as it did.
|
|
194
|
+
why: docs/specification.md#a-dropdown-with-a-search-field */
|
|
195
|
+
.ui-dropdown__panel--search { min-width: 280px; }
|
|
196
|
+
/* Open, the panel is visible at once instead of at the first step of the
|
|
197
|
+
`visibility` transition, because a browser will not focus a field in a box
|
|
198
|
+
that is still `hidden` and opening puts focus in the field. Closing still fades. */
|
|
199
|
+
.ui-dropdown.open .ui-dropdown__panel--search,
|
|
200
|
+
.ui-dropdown__panel--search.is-open { transition-property: opacity, transform; }
|
|
201
|
+
|
|
202
|
+
.ui-dropdown__search { position: relative; display: flex; align-items: center; margin-bottom: 6px; }
|
|
203
|
+
.ui-dropdown__search-ic {
|
|
204
|
+
position: absolute;
|
|
205
|
+
left: 11px;
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
color: var(--muted);
|
|
208
|
+
pointer-events: none;
|
|
209
|
+
}
|
|
210
|
+
/* 16px at 2.4 paints 1.6px, over the 1.5px floor stories/glyph-stroke.test.js holds. */
|
|
211
|
+
.ui-dropdown__search-ic svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.4; }
|
|
212
|
+
.ui-dropdown__search-input {
|
|
213
|
+
width: 100%;
|
|
214
|
+
font: inherit;
|
|
215
|
+
font-size: 12.5px;
|
|
216
|
+
color: var(--strong);
|
|
217
|
+
background: var(--surface);
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: var(--radius-sm);
|
|
220
|
+
padding: 8px 12px 8px 34px;
|
|
221
|
+
/* The height Chromium paints (38.25px), stated so the target-size gate, which
|
|
222
|
+
cannot see an <input>'s line of text, reads the box a reader hits. */
|
|
223
|
+
min-height: 38px;
|
|
224
|
+
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
|
|
225
|
+
}
|
|
226
|
+
/* iOS Safari zooms the page into a focused field under 16px, and opening the
|
|
227
|
+
panel focuses this one. A real 16px, not a scaled one: the zoom reads the
|
|
228
|
+
computed size, and a transform would shrink the ring and border with it. */
|
|
229
|
+
@media (pointer: coarse) { .ui-dropdown__search-input { font-size: 16px; } }
|
|
230
|
+
.ui-dropdown__search-input::placeholder { color: var(--muted); }
|
|
231
|
+
.ui-dropdown__search-input:hover { border-color: var(--border-strong); }
|
|
232
|
+
.ui-dropdown__search-input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
|
|
233
|
+
|
|
234
|
+
.ui-dropdown__list { max-height: 300px; overflow-y: auto; }
|
|
235
|
+
/* A row is `display: flex`, which outranks the UA's [hidden], so a row the
|
|
236
|
+
query hides is put away here. */
|
|
237
|
+
.ui-dropdown__list [hidden] { display: none; } /* motion: still — a row the query no longer matches is gone on the next keystroke */
|
|
238
|
+
|
|
239
|
+
/* The row Enter would pick. Focus stays in the field, which carries the ring,
|
|
240
|
+
so this row takes the hover fill and a bar rather than a second ring. */
|
|
241
|
+
.ui-dropdown__item.is-active { background: var(--surface); box-shadow: inset 2px 0 0 var(--accent); }
|
|
242
|
+
|
|
243
|
+
/* No match — a nudge and no action, per Guidelines / Microcopy. Empty while
|
|
244
|
+
anything matches, so it takes no room. */
|
|
245
|
+
.ui-dropdown__none { display: flex; flex-direction: column; gap: 3px; padding: 14px 12px 10px; text-align: center; }
|
|
246
|
+
.ui-dropdown__none:empty { display: none; }
|
|
247
|
+
.ui-dropdown__none-title { font-size: 12.5px; font-weight: 600; color: var(--strong); }
|
|
248
|
+
.ui-dropdown__none-hint { font-size: 11px; color: var(--muted); line-height: 1.35; }
|
package/src/styles/feedback.css
CHANGED
|
@@ -127,6 +127,8 @@
|
|
|
127
127
|
/* Error banner */
|
|
128
128
|
.ui-fbc__err { margin: 14px 20px 0; background: color-mix(in srgb, var(--pink) 14%, transparent); color: var(--pink); font-size: 13px; padding: 11px 15px; border-radius: 11px; display: none; }
|
|
129
129
|
.ui-fbc__err.show { display: block; }
|
|
130
|
+
/* A failed send's line fades in; wireFeedback() adds the class as it shows it. */
|
|
131
|
+
.ui-fbc__err.is-entering { animation: m-fade var(--dur-fast) var(--ease-out) both; }
|
|
130
132
|
|
|
131
133
|
@media (prefers-reduced-motion: reduce) {
|
|
132
134
|
.ui-fbpill, .ui-fbpill::before, .ui-fbcomposer, .ui-fbscrim, .ui-fbck, .ui-fbck-m, .ui-fbck-s { animation: none !important; transition: none !important; }
|
package/src/styles/footer.css
CHANGED
|
@@ -71,10 +71,9 @@
|
|
|
71
71
|
gap: 30px 56px;
|
|
72
72
|
}
|
|
73
73
|
.ui-footer__col-title {
|
|
74
|
-
|
|
75
|
-
font-
|
|
76
|
-
|
|
77
|
-
text-transform: uppercase;
|
|
74
|
+
/* rank: label */
|
|
75
|
+
font-size: var(--text-sm);
|
|
76
|
+
font-weight: var(--weight-medium);
|
|
78
77
|
color: var(--footer-text);
|
|
79
78
|
margin: 0 0 14px;
|
|
80
79
|
}
|
package/src/styles/input.css
CHANGED
|
@@ -49,7 +49,13 @@
|
|
|
49
49
|
|
|
50
50
|
/* The value a reader typed is still the value; it goes quiet, it does not fade
|
|
51
51
|
into the field under it the way an opacity would take both down together. #220 */
|
|
52
|
+
/* `.ui-select` joins these two in #273. It had no disabled rule at all, and it
|
|
53
|
+
sets `background: var(--surface-2)` as an author declaration — which outranks
|
|
54
|
+
the UA's own grey-out — so a disabled select rendered pixel-identical to a live
|
|
55
|
+
one. A pager marked busy showed every button and the jump input visibly off
|
|
56
|
+
beside a size control that still looked available. */
|
|
52
57
|
.ui-input:disabled,
|
|
58
|
+
.ui-select:disabled,
|
|
53
59
|
.ui-textarea:disabled {
|
|
54
60
|
background: var(--disabled-surface);
|
|
55
61
|
border-color: var(--disabled-border);
|
|
@@ -134,7 +140,7 @@
|
|
|
134
140
|
width: 5px; height: 10px;
|
|
135
141
|
border: solid var(--accent-contrast);
|
|
136
142
|
border-width: 0 2px 2px 0;
|
|
137
|
-
transform: rotate(45deg);
|
|
143
|
+
transform: rotate(45deg); /* motion: still — the turn is the tick's shape, not travel; it lands with the box's fill, which transitions */
|
|
138
144
|
}
|
|
139
145
|
.ui-check input[type="radio"]:checked::after {
|
|
140
146
|
content: "";
|
package/src/styles/layout.css
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
smallest of three weak signals doing the most work, so it grows with the row. */
|
|
79
79
|
.ui-app__rail .ui-nav__ic svg { opacity: 0.62; }
|
|
80
80
|
.ui-app__rail .ui-nav__item.is-active .ui-nav__ic svg,
|
|
81
|
-
.ui-app__rail .ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: currentColor; }
|
|
81
|
+
.ui-app__rail .ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: currentColor; } /* motion: still — the glyph brightens with its row, whose own highlight already transitions */
|
|
82
82
|
.ui-app__rail .ui-nav__item.is-active::before { width: 3px; height: 62%; }
|
|
83
83
|
|
|
84
84
|
/* Sign out is a navigation row and rests like one. The danger intent is the
|
|
@@ -133,7 +133,8 @@
|
|
|
133
133
|
.ui-app__main .ui-nav--crumbs { margin-bottom: var(--space-5); }
|
|
134
134
|
.ui-app__main h1 {
|
|
135
135
|
color: var(--strong);
|
|
136
|
-
|
|
136
|
+
/* rank: page-title */
|
|
137
|
+
font-size: var(--text-2xl);
|
|
137
138
|
font-weight: var(--weight-bold);
|
|
138
139
|
line-height: 1.1;
|
|
139
140
|
letter-spacing: var(--tracking-tight);
|
package/src/styles/loading.css
CHANGED
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
.ui-busy {
|
|
32
32
|
display: block;
|
|
33
33
|
}
|
|
34
|
+
/* What setBusy() puts in the body fades in, without travel. The region the page
|
|
35
|
+
loads with is not animated: only setBusy() adds the class. */
|
|
36
|
+
.ui-busy__body.is-entering {
|
|
37
|
+
animation: m-fade var(--dur-med) var(--ease-out) both;
|
|
38
|
+
}
|
|
34
39
|
|
|
35
40
|
/* -- Skeleton -------------------------------------------------------------- */
|
|
36
41
|
.ui-skel {
|