@echodial/deck 0.1.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/LICENSE +21 -0
- package/README.md +1327 -0
- package/bin/deck.mjs +219 -0
- package/build.mjs +338 -0
- package/dist/api-buckets.json +10486 -0
- package/dist/api.json +57896 -0
- package/dist/brand/deck-apple-touch-icon.png +0 -0
- package/dist/brand/deck-logo-dark.svg +1 -0
- package/dist/brand/deck-logo-light.svg +1 -0
- package/dist/brand/deck-logo-stacked.svg +1 -0
- package/dist/brand/deck-logo.svg +1 -0
- package/dist/brand/deck-mark.svg +7 -0
- package/dist/brand/deck-og.png +0 -0
- package/dist/collisions.json +223 -0
- package/dist/deck-adapters.js +482 -0
- package/dist/deck-adapters.min.js +23 -0
- package/dist/deck-extras.js +823 -0
- package/dist/deck-extras.min.js +5 -0
- package/dist/deck-icons.svg +184 -0
- package/dist/deck.bundle.js +2638 -0
- package/dist/deck.bundle.min.js +86 -0
- package/dist/deck.css +7904 -0
- package/dist/deck.esm.js +2644 -0
- package/dist/deck.js +1331 -0
- package/dist/deck.min.css +32 -0
- package/dist/deck.min.js +60 -0
- package/dist/layers/anchor.css +192 -0
- package/dist/layers/buttons.css +210 -0
- package/dist/layers/charts.css +337 -0
- package/dist/layers/combobox.css +248 -0
- package/dist/layers/components.css +868 -0
- package/dist/layers/container.css +199 -0
- package/dist/layers/datagrid.css +335 -0
- package/dist/layers/datepicker.css +305 -0
- package/dist/layers/forms.css +401 -0
- package/dist/layers/gradients.css +302 -0
- package/dist/layers/inputs.css +522 -0
- package/dist/layers/layers.css +87 -0
- package/dist/layers/layout.css +235 -0
- package/dist/layers/logical.css +226 -0
- package/dist/layers/media.css +400 -0
- package/dist/layers/mobile.css +245 -0
- package/dist/layers/motion.css +379 -0
- package/dist/layers/nav.css +771 -0
- package/dist/layers/perf.css +234 -0
- package/dist/layers/print.css +198 -0
- package/dist/layers/reset.css +103 -0
- package/dist/layers/space3d.css +280 -0
- package/dist/layers/toasts.css +201 -0
- package/dist/layers/tokens.css +229 -0
- package/dist/layers/type.css +114 -0
- package/dist/layers/utilities.css +241 -0
- package/dist/sizes.json +52 -0
- package/dist/usage.json +6340 -0
- package/package.json +94 -0
- package/src/00-layers.css +87 -0
- package/src/01-tokens.css +229 -0
- package/src/02-reset.css +103 -0
- package/src/03-type.css +114 -0
- package/src/04-layout.css +235 -0
- package/src/05-buttons.css +210 -0
- package/src/06-forms.css +401 -0
- package/src/07-components.css +868 -0
- package/src/08-mobile.css +245 -0
- package/src/09-utilities.css +241 -0
- package/src/10-datepicker.css +305 -0
- package/src/11-combobox.css +248 -0
- package/src/12-datagrid.css +335 -0
- package/src/13-toasts.css +201 -0
- package/src/14-charts.css +337 -0
- package/src/16-motion.css +379 -0
- package/src/18-container.css +199 -0
- package/src/19-logical.css +226 -0
- package/src/20-gradients.css +302 -0
- package/src/21-space3d.css +280 -0
- package/src/22-nav.css +771 -0
- package/src/23-inputs.css +522 -0
- package/src/24-media.css +400 -0
- package/src/25-anchor.css +192 -0
- package/src/26-perf.css +234 -0
- package/src/99-print.css +198 -0
- package/src/brand/deck-apple-touch-icon.png +0 -0
- package/src/brand/deck-logo-dark.svg +1 -0
- package/src/brand/deck-logo-light.svg +1 -0
- package/src/brand/deck-logo-stacked.svg +1 -0
- package/src/brand/deck-logo.svg +1 -0
- package/src/brand/deck-mark.svg +7 -0
- package/src/brand/deck-og.png +0 -0
- package/src/brand/sources.json +7 -0
- package/src/deck-icons.svg +184 -0
- package/src/js/deck-adapters.js +482 -0
- package/src/js/deck-extras.js +823 -0
- package/src/js/deck.js +1331 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 10 Date picker
|
|
3
|
+
Popover calendar on desktop, bottom sheet on a phone. Single date, range,
|
|
4
|
+
and month/year jump. Behaviour lives in deck.js; this file is the skin.
|
|
5
|
+
========================================================================== */
|
|
6
|
+
|
|
7
|
+
@layer deck.components {
|
|
8
|
+
|
|
9
|
+
/* --- Trigger field ------------------------------------------------------ */
|
|
10
|
+
/* The trigger for a date picker: an input with a calendar icon inside its
|
|
11
|
+
starting edge and a clear button at the end. The input is readonly and
|
|
12
|
+
cursor: pointer, because the value comes from the calendar — which also
|
|
13
|
+
means a reader who knows the date cannot type it. */
|
|
14
|
+
.datefield { position: relative; display: flex; align-items: center; }
|
|
15
|
+
.datefield > .icon {
|
|
16
|
+
position: absolute;
|
|
17
|
+
inset-inline-start: var(--space-3);
|
|
18
|
+
color: var(--text-faint);
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
}
|
|
21
|
+
.datefield > .input { padding-inline-start: var(--space-10); cursor: pointer; }
|
|
22
|
+
/* Clears the field. Hidden until .has-value is set by deck.js. */
|
|
23
|
+
.datefield > .datefield-clear {
|
|
24
|
+
position: absolute;
|
|
25
|
+
inset-inline-end: var(--space-2);
|
|
26
|
+
inline-size: 28px;
|
|
27
|
+
block-size: 28px;
|
|
28
|
+
display: grid;
|
|
29
|
+
place-items: center;
|
|
30
|
+
border-radius: var(--r-full);
|
|
31
|
+
color: var(--text-faint);
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
}
|
|
34
|
+
.datefield > .datefield-clear:hover { background: var(--surface-hover); color: var(--text); }
|
|
35
|
+
.datefield:not(.has-value) .datefield-clear { display: none; }
|
|
36
|
+
|
|
37
|
+
/* --- Panel -------------------------------------------------------------- */
|
|
38
|
+
/* The calendar panel: a native popover, anchored to its field by
|
|
39
|
+
25-anchor.css with the same fallback chain as a menu. Where there is no
|
|
40
|
+
room below it opens upward, and position-try-order: most-block-size picks
|
|
41
|
+
the direction with the most room rather than the first that fits — which
|
|
42
|
+
matters more for a calendar than for a menu. */
|
|
43
|
+
.datepicker {
|
|
44
|
+
position: absolute;
|
|
45
|
+
margin: 0;
|
|
46
|
+
inset: auto;
|
|
47
|
+
padding: 0;
|
|
48
|
+
inline-size: max-content;
|
|
49
|
+
max-inline-size: calc(100vw - var(--space-6));
|
|
50
|
+
background: var(--surface);
|
|
51
|
+
border: 1px solid var(--line);
|
|
52
|
+
border-radius: var(--r-md);
|
|
53
|
+
box-shadow: var(--shadow-4);
|
|
54
|
+
color: var(--text);
|
|
55
|
+
opacity: 0;
|
|
56
|
+
translate: 0 -6px;
|
|
57
|
+
transition: opacity var(--dur-2) var(--ease-out),
|
|
58
|
+
translate var(--dur-2) var(--ease-out),
|
|
59
|
+
overlay var(--dur-2) allow-discrete,
|
|
60
|
+
display var(--dur-2) allow-discrete;
|
|
61
|
+
}
|
|
62
|
+
.datepicker:popover-open { opacity: 1; translate: 0; }
|
|
63
|
+
@starting-style { .datepicker:popover-open { opacity: 0; translate: 0 -6px; } }
|
|
64
|
+
.datepicker::backdrop { background: transparent; }
|
|
65
|
+
|
|
66
|
+
.datepicker-layout { display: flex; flex-direction: column; }
|
|
67
|
+
@media (min-width: 40rem) {
|
|
68
|
+
.datepicker-layout:has(.datepicker-presets) { flex-direction: row; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* --- Preset rail --------------------------------------------------------- */
|
|
72
|
+
/* The shortcut rail — "Last 7 days", "This month". A horizontal scroller
|
|
73
|
+
below 40rem and a column beside the calendar above it. */
|
|
74
|
+
.datepicker-presets {
|
|
75
|
+
display: flex;
|
|
76
|
+
gap: var(--space-1);
|
|
77
|
+
padding: var(--space-2);
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
scrollbar-width: none;
|
|
80
|
+
border-block-end: 1px solid var(--line);
|
|
81
|
+
}
|
|
82
|
+
.datepicker-presets::-webkit-scrollbar { display: none; }
|
|
83
|
+
@media (min-width: 40rem) {
|
|
84
|
+
.datepicker-presets {
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
inline-size: 10.5rem;
|
|
87
|
+
overflow-x: visible;
|
|
88
|
+
border-block-end: 0;
|
|
89
|
+
border-inline-end: 1px solid var(--line);
|
|
90
|
+
background: var(--surface-2);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
.datepicker-preset {
|
|
94
|
+
padding: var(--space-2) var(--space-3);
|
|
95
|
+
min-block-size: 36px;
|
|
96
|
+
border-radius: var(--r-xs);
|
|
97
|
+
font-size: var(--text-sm);
|
|
98
|
+
font-weight: 520;
|
|
99
|
+
color: var(--text-muted);
|
|
100
|
+
text-align: start;
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
|
|
104
|
+
}
|
|
105
|
+
.datepicker-preset:hover { background: var(--surface-hover); color: var(--text); }
|
|
106
|
+
.datepicker-preset[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-soft-text); font-weight: 600; }
|
|
107
|
+
|
|
108
|
+
/* --- Calendar ----------------------------------------------------------- */
|
|
109
|
+
.datepicker-main { padding: var(--space-3); }
|
|
110
|
+
|
|
111
|
+
.datepicker-head {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: var(--space-1);
|
|
115
|
+
padding-block-end: var(--space-2);
|
|
116
|
+
}
|
|
117
|
+
.datepicker-title {
|
|
118
|
+
flex: 1 1 auto;
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
gap: var(--space-2);
|
|
123
|
+
min-block-size: 36px;
|
|
124
|
+
padding-inline: var(--space-3);
|
|
125
|
+
border-radius: var(--r-sm);
|
|
126
|
+
font-weight: 620;
|
|
127
|
+
font-size: var(--text-sm);
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
letter-spacing: -.01em;
|
|
130
|
+
}
|
|
131
|
+
.datepicker-title:hover { background: var(--surface-hover); }
|
|
132
|
+
.datepicker-nav {
|
|
133
|
+
inline-size: 36px;
|
|
134
|
+
block-size: 36px;
|
|
135
|
+
display: grid;
|
|
136
|
+
place-items: center;
|
|
137
|
+
border-radius: var(--r-sm);
|
|
138
|
+
color: var(--text-muted);
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
}
|
|
141
|
+
.datepicker-nav:hover { background: var(--surface-hover); color: var(--text); }
|
|
142
|
+
.datepicker-nav[disabled] { opacity: .35; pointer-events: none; }
|
|
143
|
+
|
|
144
|
+
.datepicker-months { display: flex; gap: var(--space-5); }
|
|
145
|
+
.datepicker-month { display: flex; flex-direction: column; }
|
|
146
|
+
|
|
147
|
+
.datepicker-grid {
|
|
148
|
+
display: grid;
|
|
149
|
+
grid-template-columns: repeat(7, 1fr);
|
|
150
|
+
gap: 1px;
|
|
151
|
+
}
|
|
152
|
+
.datepicker-dow {
|
|
153
|
+
text-align: center;
|
|
154
|
+
font-size: var(--text-2xs);
|
|
155
|
+
font-weight: 620;
|
|
156
|
+
color: var(--text-faint);
|
|
157
|
+
padding-block: var(--space-2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* One day cell. 38px, raised to 42px on a coarse pointer so it approaches
|
|
161
|
+
the touch target. Eight is- states compose on it, and the range band is
|
|
162
|
+
built from is-in-range plus logical corner radii on the two ends. */
|
|
163
|
+
.datepicker-day {
|
|
164
|
+
position: relative;
|
|
165
|
+
inline-size: 38px;
|
|
166
|
+
block-size: 38px;
|
|
167
|
+
display: grid;
|
|
168
|
+
place-items: center;
|
|
169
|
+
border-radius: var(--r-sm);
|
|
170
|
+
font-size: var(--text-sm);
|
|
171
|
+
font-variant-numeric: tabular-nums;
|
|
172
|
+
color: var(--text);
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
-webkit-tap-highlight-color: transparent;
|
|
175
|
+
transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
|
|
176
|
+
}
|
|
177
|
+
@media (pointer: coarse) { .datepicker-day { inline-size: 42px; block-size: 42px; } }
|
|
178
|
+
|
|
179
|
+
.datepicker-day:hover { background: var(--surface-hover); }
|
|
180
|
+
.datepicker-day.is-outside { color: var(--text-faint); opacity: .55; }
|
|
181
|
+
.datepicker-day[disabled], .datepicker-day.is-blocked {
|
|
182
|
+
color: var(--text-faint);
|
|
183
|
+
opacity: .4;
|
|
184
|
+
pointer-events: none;
|
|
185
|
+
text-decoration: line-through;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Today gets a marker, not a fill, so it never competes with the selection */
|
|
189
|
+
.datepicker-day.is-today::after {
|
|
190
|
+
content: "";
|
|
191
|
+
position: absolute;
|
|
192
|
+
inset-block-end: 5px;
|
|
193
|
+
inline-size: 4px;
|
|
194
|
+
block-size: 4px;
|
|
195
|
+
border-radius: var(--r-full);
|
|
196
|
+
background: var(--brand);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.datepicker-day.is-selected {
|
|
200
|
+
background: var(--brand-600);
|
|
201
|
+
color: var(--text-on-brand);
|
|
202
|
+
font-weight: 620;
|
|
203
|
+
}
|
|
204
|
+
.datepicker-day.is-selected::after { background: var(--text-on-brand); }
|
|
205
|
+
|
|
206
|
+
/* Range: the middle days get a continuous band, the ends get the caps */
|
|
207
|
+
.datepicker-day.is-in-range {
|
|
208
|
+
background: var(--brand-soft);
|
|
209
|
+
color: var(--brand-soft-text);
|
|
210
|
+
border-radius: 0;
|
|
211
|
+
}
|
|
212
|
+
.datepicker-day.is-range-start {
|
|
213
|
+
border-start-end-radius: 0;
|
|
214
|
+
border-end-end-radius: 0;
|
|
215
|
+
}
|
|
216
|
+
.datepicker-day.is-range-end {
|
|
217
|
+
border-start-start-radius: 0;
|
|
218
|
+
border-end-start-radius: 0;
|
|
219
|
+
}
|
|
220
|
+
.datepicker-day.is-range-start.is-range-end { border-radius: var(--r-sm); }
|
|
221
|
+
.datepicker-grid:has(.is-in-range) .datepicker-day.is-in-range:first-child {
|
|
222
|
+
border-start-start-radius: var(--r-sm); border-end-start-radius: var(--r-sm);
|
|
223
|
+
}
|
|
224
|
+
.datepicker-grid:has(.is-in-range) .datepicker-day.is-in-range:nth-child(7n) {
|
|
225
|
+
border-start-end-radius: var(--r-sm); border-end-end-radius: var(--r-sm);
|
|
226
|
+
}
|
|
227
|
+
.datepicker-day.is-preview { background: color-mix(in oklab, var(--brand-soft) 60%, transparent); }
|
|
228
|
+
.datepicker-day:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; z-index: 1; }
|
|
229
|
+
|
|
230
|
+
/* --- Month and year jump grid ------------------------------------------- */
|
|
231
|
+
/* Opens the month and year grid. A picker without one makes a reader press
|
|
232
|
+
the next-month arrow twenty-four times to reach a date two years out. */
|
|
233
|
+
.datepicker-jump {
|
|
234
|
+
display: grid;
|
|
235
|
+
grid-template-columns: repeat(3, 1fr);
|
|
236
|
+
gap: var(--space-1);
|
|
237
|
+
padding-block: var(--space-2);
|
|
238
|
+
}
|
|
239
|
+
.datepicker-jump > button {
|
|
240
|
+
min-block-size: 40px;
|
|
241
|
+
padding-inline: var(--space-2);
|
|
242
|
+
border-radius: var(--r-sm);
|
|
243
|
+
font-size: var(--text-sm);
|
|
244
|
+
font-variant-numeric: tabular-nums;
|
|
245
|
+
color: var(--text-muted);
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
}
|
|
248
|
+
.datepicker-jump > button:hover { background: var(--surface-hover); color: var(--text); }
|
|
249
|
+
.datepicker-jump > button[aria-selected="true"] {
|
|
250
|
+
background: var(--brand-600);
|
|
251
|
+
color: var(--text-on-brand);
|
|
252
|
+
font-weight: 620;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* --- Time row and footer ------------------------------------------------- */
|
|
256
|
+
.datepicker-time {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
gap: var(--space-2);
|
|
260
|
+
padding: var(--space-3);
|
|
261
|
+
border-block-start: 1px solid var(--line);
|
|
262
|
+
}
|
|
263
|
+
.datepicker-time .input { inline-size: auto; min-inline-size: 7.5rem; }
|
|
264
|
+
|
|
265
|
+
.datepicker-foot {
|
|
266
|
+
display: flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
gap: var(--space-2);
|
|
269
|
+
padding: var(--space-3);
|
|
270
|
+
border-block-start: 1px solid var(--line);
|
|
271
|
+
background: var(--surface-2);
|
|
272
|
+
}
|
|
273
|
+
.datepicker-readout {
|
|
274
|
+
flex: 1 1 auto;
|
|
275
|
+
font-size: var(--text-sm);
|
|
276
|
+
color: var(--text-muted);
|
|
277
|
+
font-variant-numeric: tabular-nums;
|
|
278
|
+
min-inline-size: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* --- Phone: the picker becomes a sheet ---------------------------------- */
|
|
282
|
+
@media (max-width: 30rem) {
|
|
283
|
+
.datepicker {
|
|
284
|
+
position: fixed;
|
|
285
|
+
inset: auto 0 0 0;
|
|
286
|
+
inline-size: 100%;
|
|
287
|
+
max-inline-size: 100%;
|
|
288
|
+
border-radius: var(--r-lg) var(--r-lg) 0 0;
|
|
289
|
+
border-inline: 0;
|
|
290
|
+
border-block-end: 0;
|
|
291
|
+
translate: 0 100%;
|
|
292
|
+
padding-block-end: env(safe-area-inset-bottom);
|
|
293
|
+
transition: translate var(--dur-3) var(--ease-out),
|
|
294
|
+
overlay var(--dur-3) allow-discrete,
|
|
295
|
+
display var(--dur-3) allow-discrete;
|
|
296
|
+
opacity: 1;
|
|
297
|
+
}
|
|
298
|
+
.datepicker:popover-open { translate: 0; }
|
|
299
|
+
@starting-style { .datepicker:popover-open { translate: 0 100%; } }
|
|
300
|
+
.datepicker::backdrop { background: oklch(18% .02 var(--hue-neutral) / .45); }
|
|
301
|
+
.datepicker-months { flex-direction: column; }
|
|
302
|
+
.datepicker-day { inline-size: 100%; }
|
|
303
|
+
.datepicker-main { padding-inline: var(--space-4); }
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 11 Combobox / autocomplete
|
|
3
|
+
Type to filter, arrow keys to move, enter to pick. Single and multi select.
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
@layer deck.components {
|
|
7
|
+
|
|
8
|
+
/* A filtering select. It wraps a real <select> that stays in the DOM and
|
|
9
|
+
stays in sync, so an ordinary form post works with no server-side
|
|
10
|
+
handling, server-side rendering is just marking options selected, and
|
|
11
|
+
without JavaScript the reader still gets a working control. */
|
|
12
|
+
.combo { position: relative; display: flex; flex-direction: column; }
|
|
13
|
+
|
|
14
|
+
/* --- Control ------------------------------------------------------------ */
|
|
15
|
+
/* The box, styled to match .input so a combobox in a form does not look
|
|
16
|
+
like a different species. Focus is handled twice — :has(.combo-input:focus)
|
|
17
|
+
and :focus-within — so a token's remove button lights the box too. */
|
|
18
|
+
.combo-control {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: var(--space-2);
|
|
22
|
+
inline-size: 100%;
|
|
23
|
+
min-block-size: var(--control-h);
|
|
24
|
+
padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
|
|
25
|
+
background: var(--surface);
|
|
26
|
+
border: 1px solid var(--line-strong);
|
|
27
|
+
border-radius: var(--r-sm);
|
|
28
|
+
box-shadow: var(--shadow-1);
|
|
29
|
+
cursor: text;
|
|
30
|
+
transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
|
|
31
|
+
}
|
|
32
|
+
.combo-control:hover { border-color: var(--ink-400); }
|
|
33
|
+
.combo:has(.combo-input:focus) .combo-control,
|
|
34
|
+
.combo-control:focus-within {
|
|
35
|
+
border-color: var(--focus);
|
|
36
|
+
box-shadow: var(--ring);
|
|
37
|
+
}
|
|
38
|
+
.combo.is-invalid .combo-control { border-color: var(--bad-500); }
|
|
39
|
+
.combo.is-disabled .combo-control {
|
|
40
|
+
background: var(--bg-sunken);
|
|
41
|
+
color: var(--text-faint);
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* The text field. deck.js gives it role="combobox" and keeps aria-expanded
|
|
46
|
+
in step; arrow keys move .is-active rather than focus, so the reader can
|
|
47
|
+
keep typing while the highlight moves. */
|
|
48
|
+
.combo-input {
|
|
49
|
+
flex: 1 1 6rem;
|
|
50
|
+
min-inline-size: 4rem;
|
|
51
|
+
border: 0;
|
|
52
|
+
outline: none;
|
|
53
|
+
background: none;
|
|
54
|
+
padding: var(--space-2) 0;
|
|
55
|
+
font-size: var(--text-base);
|
|
56
|
+
color: inherit;
|
|
57
|
+
}
|
|
58
|
+
@media (pointer: coarse) { .combo-input { font-size: 16px; } }
|
|
59
|
+
.combo-input::placeholder { color: var(--text-faint); }
|
|
60
|
+
|
|
61
|
+
.combo-toggle {
|
|
62
|
+
inline-size: 30px;
|
|
63
|
+
block-size: 30px;
|
|
64
|
+
display: grid;
|
|
65
|
+
place-items: center;
|
|
66
|
+
border-radius: var(--r-xs);
|
|
67
|
+
color: var(--text-faint);
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
flex: 0 0 auto;
|
|
70
|
+
}
|
|
71
|
+
.combo-toggle:hover { background: var(--surface-hover); color: var(--text); }
|
|
72
|
+
.combo-toggle .icon { transition: rotate var(--dur-2) var(--ease-out); }
|
|
73
|
+
.combo.is-open .combo-toggle .icon { rotate: 180deg; }
|
|
74
|
+
.combo-clear { color: var(--text-faint); }
|
|
75
|
+
|
|
76
|
+
/* --- Selected tokens (multi select) -------------------------------------- */
|
|
77
|
+
.combo-tokens { display: contents; }
|
|
78
|
+
.combo-token {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: var(--space-1);
|
|
82
|
+
min-block-size: 26px;
|
|
83
|
+
padding-inline: var(--space-1) var(--space-2);
|
|
84
|
+
padding-inline-start: var(--space-2);
|
|
85
|
+
border-radius: var(--r-xs);
|
|
86
|
+
background: var(--brand-soft);
|
|
87
|
+
color: var(--brand-soft-text);
|
|
88
|
+
font-size: var(--text-sm);
|
|
89
|
+
font-weight: 540;
|
|
90
|
+
max-inline-size: 14rem;
|
|
91
|
+
flex: 0 0 auto;
|
|
92
|
+
}
|
|
93
|
+
.combo-token > span {
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
text-overflow: ellipsis;
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
}
|
|
98
|
+
.combo-token > button {
|
|
99
|
+
display: grid;
|
|
100
|
+
place-items: center;
|
|
101
|
+
inline-size: 18px;
|
|
102
|
+
block-size: 18px;
|
|
103
|
+
border-radius: var(--r-full);
|
|
104
|
+
opacity: .65;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
flex: 0 0 auto;
|
|
107
|
+
}
|
|
108
|
+
.combo-token > button:hover { opacity: 1; background: color-mix(in oklab, currentColor 16%, transparent); }
|
|
109
|
+
.combo-count {
|
|
110
|
+
font-size: var(--text-xs);
|
|
111
|
+
font-weight: 620;
|
|
112
|
+
color: var(--text-muted);
|
|
113
|
+
flex: 0 0 auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* --- Listbox ------------------------------------------------------------- */
|
|
117
|
+
/* The option list. A positioned sibling by default, which a clipping
|
|
118
|
+
ancestor can cut off; 25-anchor.css moves it to the top layer where
|
|
119
|
+
anchor positioning exists, which is the fix for the combobox-inside-a-modal
|
|
120
|
+
bug. */
|
|
121
|
+
.combo-list {
|
|
122
|
+
position: absolute;
|
|
123
|
+
inset-block-start: calc(100% + 4px);
|
|
124
|
+
inset-inline: 0;
|
|
125
|
+
z-index: var(--z-modal);
|
|
126
|
+
margin: 0;
|
|
127
|
+
padding: var(--space-1);
|
|
128
|
+
max-block-size: min(20rem, 50dvh);
|
|
129
|
+
overflow-y: auto;
|
|
130
|
+
overscroll-behavior: contain;
|
|
131
|
+
background: var(--surface);
|
|
132
|
+
border: 1px solid var(--line);
|
|
133
|
+
border-radius: var(--r-md);
|
|
134
|
+
box-shadow: var(--shadow-4);
|
|
135
|
+
display: none;
|
|
136
|
+
}
|
|
137
|
+
.combo.is-open .combo-list { display: block; animation: dk-combo-in var(--dur-2) var(--ease-out); }
|
|
138
|
+
@keyframes dk-combo-in { from { opacity: 0; translate: 0 -4px; } }
|
|
139
|
+
|
|
140
|
+
/* Open upward when there is no room below */
|
|
141
|
+
.combo-list.is-above { inset-block: auto calc(100% + 4px); }
|
|
142
|
+
|
|
143
|
+
.combo-option {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: var(--space-3);
|
|
147
|
+
inline-size: 100%;
|
|
148
|
+
min-block-size: 40px;
|
|
149
|
+
padding: var(--space-2) var(--space-3);
|
|
150
|
+
border-radius: var(--r-xs);
|
|
151
|
+
font-size: var(--text-sm);
|
|
152
|
+
color: var(--text);
|
|
153
|
+
text-align: start;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
scroll-margin-block: var(--space-1);
|
|
156
|
+
}
|
|
157
|
+
/* The text column of an option. min-inline-size: 0 so a long value cannot
|
|
158
|
+
push the tick off the end. */
|
|
159
|
+
.combo-option .combo-option-main { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
160
|
+
.combo-option .combo-option-sub { font-size: var(--text-xs); color: var(--text-muted); }
|
|
161
|
+
.combo-option .combo-option-trail { color: var(--text-faint); font-size: var(--text-xs); flex: 0 0 auto; }
|
|
162
|
+
|
|
163
|
+
/* Keyboard highlight and mouse hover use the same state, set by deck.js */
|
|
164
|
+
.combo-option.is-active { background: var(--surface-hover); }
|
|
165
|
+
.combo-option[aria-selected="true"] {
|
|
166
|
+
background: var(--brand-soft);
|
|
167
|
+
color: var(--brand-soft-text);
|
|
168
|
+
font-weight: 560;
|
|
169
|
+
}
|
|
170
|
+
.combo-option[aria-selected="true"].is-active { background: color-mix(in oklab, var(--brand-soft) 78%, var(--brand-300)); }
|
|
171
|
+
.combo-option[aria-disabled="true"] { opacity: .45; pointer-events: none; }
|
|
172
|
+
/* The tick on a selected option, revealed by [aria-selected="true"]. */
|
|
173
|
+
.combo-option .combo-check { margin-inline-start: auto; opacity: 0; flex: 0 0 auto; }
|
|
174
|
+
.combo-option[aria-selected="true"] .combo-check { opacity: 1; }
|
|
175
|
+
|
|
176
|
+
/* The matched substring, wrapped by deck.js */
|
|
177
|
+
.combo-option mark {
|
|
178
|
+
background: none;
|
|
179
|
+
color: inherit;
|
|
180
|
+
font-weight: 700;
|
|
181
|
+
padding: 0;
|
|
182
|
+
text-decoration: underline;
|
|
183
|
+
text-decoration-color: color-mix(in oklab, var(--brand) 55%, transparent);
|
|
184
|
+
text-underline-offset: .16em;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* A sticky heading above a group of options. */
|
|
188
|
+
.combo-group {
|
|
189
|
+
padding: var(--space-3) var(--space-3) var(--space-1);
|
|
190
|
+
font-size: var(--text-2xs);
|
|
191
|
+
font-weight: 700;
|
|
192
|
+
color: var(--text-faint);
|
|
193
|
+
position: sticky;
|
|
194
|
+
inset-block-start: calc(var(--space-1) * -1);
|
|
195
|
+
background: var(--surface);
|
|
196
|
+
}
|
|
197
|
+
.combo-sep { block-size: 1px; background: var(--line); margin: var(--space-1) 0; }
|
|
198
|
+
|
|
199
|
+
/* The no-matches row. Name what was searched for — "No repositories match
|
|
200
|
+
'deckk'" tells a reader who mistyped what they actually typed. */
|
|
201
|
+
.combo-empty, .combo-loading {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
gap: var(--space-2);
|
|
206
|
+
padding: var(--space-6) var(--space-4);
|
|
207
|
+
font-size: var(--text-sm);
|
|
208
|
+
color: var(--text-muted);
|
|
209
|
+
text-align: center;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Sticky "create new" row for tag-style inputs */
|
|
213
|
+
.combo-create {
|
|
214
|
+
position: sticky;
|
|
215
|
+
inset-block-end: calc(var(--space-1) * -1);
|
|
216
|
+
background: var(--surface);
|
|
217
|
+
border-block-start: 1px solid var(--line);
|
|
218
|
+
margin: var(--space-1) calc(var(--space-1) * -1) calc(var(--space-1) * -1);
|
|
219
|
+
padding: var(--space-1);
|
|
220
|
+
}
|
|
221
|
+
.combo-create .combo-option { color: var(--brand); font-weight: 560; }
|
|
222
|
+
|
|
223
|
+
.combo-hint {
|
|
224
|
+
display: none;
|
|
225
|
+
gap: var(--space-3);
|
|
226
|
+
padding: var(--space-2) var(--space-3);
|
|
227
|
+
border-block-start: 1px solid var(--line);
|
|
228
|
+
font-size: var(--text-2xs);
|
|
229
|
+
color: var(--text-faint);
|
|
230
|
+
}
|
|
231
|
+
@media (pointer: fine) { .combo.is-open .combo-hint { display: flex; } }
|
|
232
|
+
|
|
233
|
+
/* --- Phone: the list takes over the screen ------------------------------- */
|
|
234
|
+
@media (max-width: 30rem) {
|
|
235
|
+
.combo.is-open .combo-list {
|
|
236
|
+
position: fixed;
|
|
237
|
+
inset: auto 0 0 0;
|
|
238
|
+
max-block-size: 70dvh;
|
|
239
|
+
border-radius: var(--r-lg) var(--r-lg) 0 0;
|
|
240
|
+
border-inline: 0;
|
|
241
|
+
border-block-end: 0;
|
|
242
|
+
padding-block-end: calc(var(--space-2) + env(safe-area-inset-bottom));
|
|
243
|
+
animation: dk-combo-up var(--dur-3) var(--ease-out);
|
|
244
|
+
}
|
|
245
|
+
.combo-option { min-block-size: var(--tap); }
|
|
246
|
+
@keyframes dk-combo-up { from { translate: 0 100%; } }
|
|
247
|
+
}
|
|
248
|
+
}
|