@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,522 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 23 Inputs, extended
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
@layer deck.components {
|
|
6
|
+
|
|
7
|
+
/* --- Floating label ------------------------------------------------------
|
|
8
|
+
No JavaScript. :placeholder-shown tells us whether the field is empty, so
|
|
9
|
+
the label lifts on focus or on content and never gets out of sync.
|
|
10
|
+
The input needs placeholder=" " for the selector to work.
|
|
11
|
+
---------------------------------------------------------------------- */
|
|
12
|
+
/* Two things about the markup that this file cannot enforce and that fail
|
|
13
|
+
silently when they are wrong:
|
|
14
|
+
|
|
15
|
+
1. The label must come AFTER the control. Every lift selector below is
|
|
16
|
+
`~ label`, and the general sibling combinator only looks forward.
|
|
17
|
+
Label first and it simply never moves.
|
|
18
|
+
2. The control needs placeholder=" " (one space). Without a placeholder
|
|
19
|
+
attribute :placeholder-shown never matches, so the label sits lifted
|
|
20
|
+
over an empty field.
|
|
21
|
+
|
|
22
|
+
FINDINGS 61: .float.is-invalid only colours the label. .field.is-invalid
|
|
23
|
+
in 06-forms.css also reddens the control's border; there is no equivalent
|
|
24
|
+
rule here, so the same class name does two different amounts of work
|
|
25
|
+
depending on which wrapper it is on. :user-invalid is unaffected -- it
|
|
26
|
+
matches the control directly through the 06-forms.css rule. */
|
|
27
|
+
.float {
|
|
28
|
+
position: relative;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
}
|
|
32
|
+
.float > :is(.input, .textarea, .select) {
|
|
33
|
+
padding-block: var(--space-5) var(--space-2);
|
|
34
|
+
min-block-size: var(--control-h-lg);
|
|
35
|
+
}
|
|
36
|
+
.float > .textarea { padding-block-start: var(--space-6); }
|
|
37
|
+
.float > label {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset-block-start: 0;
|
|
40
|
+
inset-inline-start: var(--space-3);
|
|
41
|
+
padding-inline: 2px;
|
|
42
|
+
block-size: var(--control-h-lg);
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
color: var(--text-faint);
|
|
46
|
+
font-size: var(--text-base);
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
transform-origin: inline-start center;
|
|
49
|
+
transition: translate var(--dur-2) var(--ease-out),
|
|
50
|
+
scale var(--dur-2) var(--ease-out),
|
|
51
|
+
color var(--dur-2) var(--ease-out);
|
|
52
|
+
}
|
|
53
|
+
.float:focus-within > label,
|
|
54
|
+
.float > .input:not(:placeholder-shown) ~ label,
|
|
55
|
+
.float > .textarea:not(:placeholder-shown) ~ label,
|
|
56
|
+
.float > .select ~ label {
|
|
57
|
+
translate: 0 calc(var(--control-h-lg) * -.28);
|
|
58
|
+
scale: .78;
|
|
59
|
+
color: var(--text-muted);
|
|
60
|
+
}
|
|
61
|
+
.float:focus-within > label { color: var(--brand); }
|
|
62
|
+
.float.is-invalid > label,
|
|
63
|
+
.float > :user-invalid ~ label { color: var(--bad-700); }
|
|
64
|
+
|
|
65
|
+
/* Outlined variant, where the label sits in a notch in the border */
|
|
66
|
+
.float-outline > label {
|
|
67
|
+
background: var(--surface);
|
|
68
|
+
block-size: auto;
|
|
69
|
+
inset-block-start: 50%;
|
|
70
|
+
translate: 0 -50%;
|
|
71
|
+
padding-inline: var(--space-1);
|
|
72
|
+
}
|
|
73
|
+
.float-outline:focus-within > label,
|
|
74
|
+
.float-outline > .input:not(:placeholder-shown) ~ label,
|
|
75
|
+
.float-outline > .textarea:not(:placeholder-shown) ~ label,
|
|
76
|
+
.float-outline > .select ~ label {
|
|
77
|
+
translate: 0 calc(var(--control-h-lg) * -.5 - 50%);
|
|
78
|
+
scale: .8;
|
|
79
|
+
}
|
|
80
|
+
.float-outline > :is(.input, .textarea, .select) { padding-block: var(--space-3); }
|
|
81
|
+
|
|
82
|
+
/* --- Number input --------------------------------------------------------
|
|
83
|
+
Native spinners are tiny and inconsistent across browsers. These are real
|
|
84
|
+
buttons that clear the touch target.
|
|
85
|
+
---------------------------------------------------------------------- */
|
|
86
|
+
.number {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: stretch;
|
|
89
|
+
inline-size: 100%;
|
|
90
|
+
max-inline-size: var(--number-width, 11rem);
|
|
91
|
+
border: 1px solid var(--line-strong);
|
|
92
|
+
border-radius: var(--r-sm);
|
|
93
|
+
background: var(--surface);
|
|
94
|
+
box-shadow: var(--shadow-1);
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
}
|
|
97
|
+
.number:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
|
|
98
|
+
.number > input {
|
|
99
|
+
flex: 1 1 auto;
|
|
100
|
+
min-inline-size: 0;
|
|
101
|
+
border: 0;
|
|
102
|
+
outline: none;
|
|
103
|
+
background: none;
|
|
104
|
+
text-align: center;
|
|
105
|
+
font-variant-numeric: tabular-nums;
|
|
106
|
+
font-size: var(--text-base);
|
|
107
|
+
color: inherit;
|
|
108
|
+
padding-inline: var(--space-1);
|
|
109
|
+
}
|
|
110
|
+
@media (pointer: coarse) { .number > input { font-size: 16px; } }
|
|
111
|
+
.number > input::-webkit-outer-spin-button,
|
|
112
|
+
.number > input::-webkit-inner-spin-button { appearance: none; margin: 0; }
|
|
113
|
+
.number > input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
|
|
114
|
+
.number > button {
|
|
115
|
+
inline-size: var(--control-h);
|
|
116
|
+
min-block-size: var(--control-h);
|
|
117
|
+
display: grid;
|
|
118
|
+
place-items: center;
|
|
119
|
+
background: var(--surface-2);
|
|
120
|
+
color: var(--text-muted);
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
flex: 0 0 auto;
|
|
123
|
+
user-select: none;
|
|
124
|
+
transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
|
|
125
|
+
}
|
|
126
|
+
.number > button:hover { background: var(--surface-hover); color: var(--text); }
|
|
127
|
+
.number > button:active { background: var(--brand-soft); color: var(--brand-soft-text); }
|
|
128
|
+
.number > button[disabled] { opacity: .4; pointer-events: none; }
|
|
129
|
+
.number > button:first-child { border-inline-end: 1px solid var(--line); }
|
|
130
|
+
.number > button:last-child { border-inline-start: 1px solid var(--line); }
|
|
131
|
+
.number-unit {
|
|
132
|
+
display: grid;
|
|
133
|
+
place-items: center;
|
|
134
|
+
padding-inline: var(--space-3);
|
|
135
|
+
background: var(--surface-2);
|
|
136
|
+
border-inline-start: 1px solid var(--line);
|
|
137
|
+
font-size: var(--text-sm);
|
|
138
|
+
color: var(--text-muted);
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
}
|
|
141
|
+
.number-sm { --number-width: 8.5rem; }
|
|
142
|
+
.number-sm > button { inline-size: var(--control-h-sm); min-block-size: var(--control-h-sm); }
|
|
143
|
+
|
|
144
|
+
/* --- Phone input ---------------------------------------------------------
|
|
145
|
+
Country select welded to the number. The number stays LTR even in an RTL
|
|
146
|
+
document, because a phone number always reads left to right.
|
|
147
|
+
---------------------------------------------------------------------- */
|
|
148
|
+
.phone {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: stretch;
|
|
151
|
+
inline-size: 100%;
|
|
152
|
+
border: 1px solid var(--line-strong);
|
|
153
|
+
border-radius: var(--r-sm);
|
|
154
|
+
background: var(--surface);
|
|
155
|
+
box-shadow: var(--shadow-1);
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
158
|
+
.phone:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
|
|
159
|
+
.phone > .phone-country {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
gap: var(--space-2);
|
|
163
|
+
padding-inline: var(--space-3);
|
|
164
|
+
background: var(--surface-2);
|
|
165
|
+
border-inline-end: 1px solid var(--line);
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
font-size: var(--text-sm);
|
|
168
|
+
color: var(--text-muted);
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
flex: 0 0 auto;
|
|
171
|
+
}
|
|
172
|
+
.phone > .phone-country:hover { background: var(--surface-hover); }
|
|
173
|
+
.phone > .phone-country select {
|
|
174
|
+
appearance: none;
|
|
175
|
+
border: 0;
|
|
176
|
+
background: none;
|
|
177
|
+
outline: none;
|
|
178
|
+
font: inherit;
|
|
179
|
+
color: inherit;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
max-inline-size: 4.5rem;
|
|
182
|
+
}
|
|
183
|
+
.phone-flag { font-size: 1.1em; line-height: 1; }
|
|
184
|
+
.phone-code { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 560; }
|
|
185
|
+
.phone > input {
|
|
186
|
+
flex: 1 1 auto;
|
|
187
|
+
min-inline-size: 0;
|
|
188
|
+
border: 0;
|
|
189
|
+
outline: none;
|
|
190
|
+
background: none;
|
|
191
|
+
padding: var(--space-2) var(--space-3);
|
|
192
|
+
font-size: var(--text-base);
|
|
193
|
+
font-variant-numeric: tabular-nums;
|
|
194
|
+
letter-spacing: .02em;
|
|
195
|
+
color: inherit;
|
|
196
|
+
direction: ltr;
|
|
197
|
+
unicode-bidi: isolate;
|
|
198
|
+
text-align: start;
|
|
199
|
+
}
|
|
200
|
+
@media (pointer: coarse) { .phone > input { font-size: 16px; } }
|
|
201
|
+
.phone.is-valid { border-color: color-mix(in oklab, var(--good-500) 55%, transparent); }
|
|
202
|
+
.phone-status { display: grid; place-items: center; padding-inline: var(--space-3); flex: 0 0 auto; }
|
|
203
|
+
|
|
204
|
+
/* --- Rating --------------------------------------------------------------
|
|
205
|
+
Radio inputs underneath, so it posts a real value and works with the
|
|
206
|
+
keyboard. Reversed flex order makes the CSS-only hover fill work.
|
|
207
|
+
---------------------------------------------------------------------- */
|
|
208
|
+
/* The inputs are written HIGHEST value first -- 5, 4, 3, 2, 1 -- and
|
|
209
|
+
row-reverse draws them 1 to 5. That is not a quirk, it is the whole
|
|
210
|
+
mechanism: to light a star and everything below it, the selector has to
|
|
211
|
+
reach forward from the chosen one, and CSS has no previous-sibling
|
|
212
|
+
combinator. Reversing the row turns "everything below" into "everything
|
|
213
|
+
after".
|
|
214
|
+
|
|
215
|
+
Get the order wrong and it fails recognisably: the stars fill from the
|
|
216
|
+
wrong end and the posted value is the mirror of the one clicked.
|
|
217
|
+
|
|
218
|
+
:focus-visible + label uses the ADJACENT sibling, so each label must come
|
|
219
|
+
immediately after its own input. */
|
|
220
|
+
.rating {
|
|
221
|
+
display: inline-flex;
|
|
222
|
+
flex-direction: row-reverse;
|
|
223
|
+
justify-content: flex-end;
|
|
224
|
+
gap: 2px;
|
|
225
|
+
}
|
|
226
|
+
.rating > input { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; }
|
|
227
|
+
.rating > label {
|
|
228
|
+
inline-size: var(--star, 26px);
|
|
229
|
+
block-size: var(--star, 26px);
|
|
230
|
+
display: grid;
|
|
231
|
+
place-items: center;
|
|
232
|
+
color: var(--ink-300);
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
transition: color var(--dur-1) var(--ease-out), scale var(--dur-1) var(--ease-spring);
|
|
235
|
+
}
|
|
236
|
+
.rating > label .icon { inline-size: 100%; block-size: 100%; fill: currentColor; stroke: none; }
|
|
237
|
+
.rating > label:hover { scale: 1.14; }
|
|
238
|
+
/* the checked star and everything visually before it (after it in the DOM) */
|
|
239
|
+
.rating > input:checked ~ label,
|
|
240
|
+
.rating > label:hover,
|
|
241
|
+
.rating > label:hover ~ label {
|
|
242
|
+
color: var(--accent-500);
|
|
243
|
+
}
|
|
244
|
+
.rating > input:focus-visible + label { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-xs); }
|
|
245
|
+
.rating-sm { --star: 18px; }
|
|
246
|
+
.rating-lg { --star: 36px; }
|
|
247
|
+
|
|
248
|
+
/* Read-only display: a fill percentage rather than whole stars */
|
|
249
|
+
.rating-static {
|
|
250
|
+
display: inline-flex;
|
|
251
|
+
gap: 2px;
|
|
252
|
+
color: var(--ink-300);
|
|
253
|
+
position: relative;
|
|
254
|
+
}
|
|
255
|
+
.rating-static > .rating-fill {
|
|
256
|
+
position: absolute;
|
|
257
|
+
inset-block: 0;
|
|
258
|
+
inset-inline-start: 0;
|
|
259
|
+
inline-size: calc(var(--value) * 1%);
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
display: flex;
|
|
262
|
+
gap: 2px;
|
|
263
|
+
color: var(--accent-500);
|
|
264
|
+
white-space: nowrap;
|
|
265
|
+
}
|
|
266
|
+
.rating-static .icon { fill: currentColor; stroke: none; inline-size: var(--star, 18px); block-size: var(--star, 18px); flex: 0 0 auto; }
|
|
267
|
+
|
|
268
|
+
/* --- Range selector (two handles) ---------------------------------------
|
|
269
|
+
Two stacked native range inputs. Real inputs mean real keyboard support
|
|
270
|
+
and a real form post; the track between them is drawn from --lo and --hi.
|
|
271
|
+
---------------------------------------------------------------------- */
|
|
272
|
+
.range-pair {
|
|
273
|
+
position: relative;
|
|
274
|
+
display: grid;
|
|
275
|
+
block-size: var(--tap);
|
|
276
|
+
align-items: center;
|
|
277
|
+
}
|
|
278
|
+
.range-pair::before {
|
|
279
|
+
content: "";
|
|
280
|
+
grid-area: 1 / 1;
|
|
281
|
+
block-size: 6px;
|
|
282
|
+
border-radius: var(--r-full);
|
|
283
|
+
background: var(--ink-200);
|
|
284
|
+
}
|
|
285
|
+
.range-pair::after {
|
|
286
|
+
content: "";
|
|
287
|
+
grid-area: 1 / 1;
|
|
288
|
+
block-size: 6px;
|
|
289
|
+
border-radius: var(--r-full);
|
|
290
|
+
background: var(--brand-500);
|
|
291
|
+
margin-inline-start: calc(var(--lo, 0) * 1%);
|
|
292
|
+
inline-size: calc((var(--hi, 100) - var(--lo, 0)) * 1%);
|
|
293
|
+
}
|
|
294
|
+
.range-pair > input[type="range"] {
|
|
295
|
+
grid-area: 1 / 1;
|
|
296
|
+
inline-size: 100%;
|
|
297
|
+
background: none;
|
|
298
|
+
appearance: none;
|
|
299
|
+
block-size: var(--tap);
|
|
300
|
+
pointer-events: none;
|
|
301
|
+
margin: 0;
|
|
302
|
+
}
|
|
303
|
+
.range-pair > input[type="range"]::-webkit-slider-runnable-track { background: transparent; block-size: 6px; }
|
|
304
|
+
.range-pair > input[type="range"]::-moz-range-track { background: transparent; block-size: 6px; }
|
|
305
|
+
.range-pair > input[type="range"]::-webkit-slider-thumb {
|
|
306
|
+
appearance: none;
|
|
307
|
+
pointer-events: auto;
|
|
308
|
+
margin-block-start: -8px;
|
|
309
|
+
inline-size: 22px;
|
|
310
|
+
block-size: 22px;
|
|
311
|
+
border-radius: var(--r-full);
|
|
312
|
+
background: var(--brand-600);
|
|
313
|
+
border: 3px solid var(--ink-0);
|
|
314
|
+
box-shadow: var(--shadow-2);
|
|
315
|
+
cursor: grab;
|
|
316
|
+
}
|
|
317
|
+
.range-pair > input[type="range"]::-moz-range-thumb {
|
|
318
|
+
pointer-events: auto;
|
|
319
|
+
inline-size: 22px;
|
|
320
|
+
block-size: 22px;
|
|
321
|
+
border-radius: var(--r-full);
|
|
322
|
+
background: var(--brand-600);
|
|
323
|
+
border: 3px solid var(--ink-0);
|
|
324
|
+
box-shadow: var(--shadow-2);
|
|
325
|
+
cursor: grab;
|
|
326
|
+
}
|
|
327
|
+
.range-pair > input[type="range"]:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
328
|
+
/* The current value of a slider. Put it on an <output>, which is an
|
|
329
|
+
implicit live region, so the value is announced as the slider moves. */
|
|
330
|
+
.range-readout {
|
|
331
|
+
display: flex;
|
|
332
|
+
justify-content: space-between;
|
|
333
|
+
font-size: var(--text-sm);
|
|
334
|
+
font-variant-numeric: tabular-nums;
|
|
335
|
+
color: var(--text-muted);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Ticks under a single or paired range */
|
|
339
|
+
.range-ticks {
|
|
340
|
+
display: flex;
|
|
341
|
+
justify-content: space-between;
|
|
342
|
+
font-size: var(--text-2xs);
|
|
343
|
+
color: var(--text-faint);
|
|
344
|
+
font-variant-numeric: tabular-nums;
|
|
345
|
+
padding-inline: 11px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* --- Copy to clipboard ----------------------------------------------------
|
|
349
|
+
The behaviour hangs off [data-deck-copy], not off .copy-btn. The class is
|
|
350
|
+
the look and nothing else, so a button with the class and no attribute
|
|
351
|
+
looks exactly right and does nothing at all -- the single most common way
|
|
352
|
+
to get this component wrong.
|
|
353
|
+
|
|
354
|
+
Whether a successful copy raises a toast depends on what is inside the
|
|
355
|
+
button: a .copy-done element swaps in place and no toast fires; without
|
|
356
|
+
one, deck-extras.js toasts instead. .copy-inline never toasts.
|
|
357
|
+
---------------------------------------------------------------------- */
|
|
358
|
+
.copy {
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: stretch;
|
|
361
|
+
inline-size: 100%;
|
|
362
|
+
border: 1px solid var(--line);
|
|
363
|
+
border-radius: var(--r-sm);
|
|
364
|
+
background: var(--surface-2);
|
|
365
|
+
overflow: hidden;
|
|
366
|
+
}
|
|
367
|
+
.copy > code, .copy > .copy-value {
|
|
368
|
+
flex: 1 1 auto;
|
|
369
|
+
min-inline-size: 0;
|
|
370
|
+
padding: var(--space-2) var(--space-3);
|
|
371
|
+
background: none;
|
|
372
|
+
border: 0;
|
|
373
|
+
border-radius: 0;
|
|
374
|
+
font-family: var(--font-mono);
|
|
375
|
+
font-size: var(--text-sm);
|
|
376
|
+
color: var(--text);
|
|
377
|
+
overflow-x: auto;
|
|
378
|
+
white-space: nowrap;
|
|
379
|
+
scrollbar-width: none;
|
|
380
|
+
direction: ltr;
|
|
381
|
+
unicode-bidi: isolate;
|
|
382
|
+
display: flex;
|
|
383
|
+
align-items: center;
|
|
384
|
+
}
|
|
385
|
+
.copy > :is(code, .copy-value)::-webkit-scrollbar { display: none; }
|
|
386
|
+
.copy-btn {
|
|
387
|
+
flex: 0 0 auto;
|
|
388
|
+
display: inline-flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: var(--space-2);
|
|
391
|
+
padding-inline: var(--space-3);
|
|
392
|
+
min-inline-size: var(--tap);
|
|
393
|
+
background: var(--surface);
|
|
394
|
+
border-inline-start: 1px solid var(--line);
|
|
395
|
+
color: var(--text-muted);
|
|
396
|
+
font-size: var(--text-sm);
|
|
397
|
+
font-weight: 560;
|
|
398
|
+
cursor: pointer;
|
|
399
|
+
transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
|
|
400
|
+
}
|
|
401
|
+
.copy-btn:hover { background: var(--surface-hover); color: var(--text); }
|
|
402
|
+
.copy-btn.is-copied { color: var(--good-700); background: var(--good-100); }
|
|
403
|
+
.copy-btn .copy-done { display: none; }
|
|
404
|
+
.copy-btn.is-copied .copy-done { display: inline-flex; }
|
|
405
|
+
.copy-btn.is-copied .copy-idle { display: none; }
|
|
406
|
+
|
|
407
|
+
/* Inline icon-only variant, for an order number or an API key in a table */
|
|
408
|
+
.copy-inline {
|
|
409
|
+
display: inline-flex;
|
|
410
|
+
align-items: center;
|
|
411
|
+
gap: var(--space-1);
|
|
412
|
+
color: var(--text-faint);
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
border-radius: var(--r-xs);
|
|
415
|
+
padding: 2px;
|
|
416
|
+
}
|
|
417
|
+
.copy-inline:hover { color: var(--brand); background: var(--surface-hover); }
|
|
418
|
+
.copy-inline.is-copied { color: var(--good-700); }
|
|
419
|
+
|
|
420
|
+
/* --- WYSIWYG editor -------------------------------------------------------
|
|
421
|
+
A toolbar over a contenteditable region. Deck styles the chrome; deck.js
|
|
422
|
+
wires the commands. The content area inherits .prose, so what you type
|
|
423
|
+
looks the way it will look when it is published.
|
|
424
|
+
---------------------------------------------------------------------- */
|
|
425
|
+
.editor {
|
|
426
|
+
display: flex;
|
|
427
|
+
flex-direction: column;
|
|
428
|
+
border: 1px solid var(--line-strong);
|
|
429
|
+
border-radius: var(--r-sm);
|
|
430
|
+
background: var(--surface);
|
|
431
|
+
box-shadow: var(--shadow-1);
|
|
432
|
+
overflow: clip;
|
|
433
|
+
}
|
|
434
|
+
.editor:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
|
|
435
|
+
|
|
436
|
+
/* The tool row. Pressed state is [aria-pressed="true"], which deck-extras.js
|
|
437
|
+
rewrites from queryCommandState after every command and selection change,
|
|
438
|
+
so the toolbar reflects the caret rather than the last button pressed. */
|
|
439
|
+
.editor-toolbar {
|
|
440
|
+
display: flex;
|
|
441
|
+
align-items: center;
|
|
442
|
+
gap: 2px;
|
|
443
|
+
flex-wrap: wrap;
|
|
444
|
+
padding: var(--space-1);
|
|
445
|
+
background: var(--surface-2);
|
|
446
|
+
border-block-end: 1px solid var(--line);
|
|
447
|
+
position: sticky;
|
|
448
|
+
inset-block-start: 0;
|
|
449
|
+
z-index: 1;
|
|
450
|
+
}
|
|
451
|
+
.editor-tool {
|
|
452
|
+
inline-size: 32px;
|
|
453
|
+
block-size: 32px;
|
|
454
|
+
display: grid;
|
|
455
|
+
place-items: center;
|
|
456
|
+
border-radius: var(--r-xs);
|
|
457
|
+
color: var(--text-muted);
|
|
458
|
+
cursor: pointer;
|
|
459
|
+
font-size: var(--text-sm);
|
|
460
|
+
font-weight: 620;
|
|
461
|
+
transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
|
|
462
|
+
}
|
|
463
|
+
.editor-tool:hover { background: var(--surface-hover); color: var(--text); }
|
|
464
|
+
.editor-tool[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-soft-text); }
|
|
465
|
+
.editor-tool-wide { inline-size: auto; padding-inline: var(--space-2); }
|
|
466
|
+
.editor-sep { inline-size: 1px; block-size: 20px; background: var(--line); margin-inline: var(--space-1); }
|
|
467
|
+
.editor-select {
|
|
468
|
+
block-size: 32px;
|
|
469
|
+
padding-inline: var(--space-2);
|
|
470
|
+
border: 0;
|
|
471
|
+
border-radius: var(--r-xs);
|
|
472
|
+
background: none;
|
|
473
|
+
font-size: var(--text-sm);
|
|
474
|
+
color: var(--text-muted);
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
}
|
|
477
|
+
.editor-select:hover { background: var(--surface-hover); }
|
|
478
|
+
|
|
479
|
+
/* The editable region. Its placeholder comes from data-placeholder through
|
|
480
|
+
:empty::before, so it needs no element and no script to hide it. */
|
|
481
|
+
.editor-content {
|
|
482
|
+
min-block-size: var(--editor-height, 12rem);
|
|
483
|
+
max-block-size: var(--editor-max, 30rem);
|
|
484
|
+
overflow-y: auto;
|
|
485
|
+
padding: var(--space-4);
|
|
486
|
+
outline: none;
|
|
487
|
+
line-height: var(--leading-normal);
|
|
488
|
+
}
|
|
489
|
+
.editor-content:empty::before {
|
|
490
|
+
content: attr(data-placeholder);
|
|
491
|
+
color: var(--text-faint);
|
|
492
|
+
pointer-events: none;
|
|
493
|
+
}
|
|
494
|
+
.editor-content > * + * { margin-block-start: var(--space-3); }
|
|
495
|
+
.editor-content :is(h1, h2, h3) { margin-block-start: var(--space-5); }
|
|
496
|
+
.editor-content :is(ul, ol) { padding-inline-start: var(--space-6); }
|
|
497
|
+
.editor-content ul { list-style: disc; }
|
|
498
|
+
.editor-content ol { list-style: decimal; }
|
|
499
|
+
.editor-content blockquote {
|
|
500
|
+
border-inline-start: 3px solid var(--brand-300);
|
|
501
|
+
padding-inline-start: var(--space-4);
|
|
502
|
+
color: var(--text-muted);
|
|
503
|
+
}
|
|
504
|
+
.editor-content a { color: var(--brand); }
|
|
505
|
+
.editor-content img { max-inline-size: 100%; border-radius: var(--r-sm); }
|
|
506
|
+
|
|
507
|
+
.editor-footer {
|
|
508
|
+
display: flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
gap: var(--space-3);
|
|
511
|
+
padding: var(--space-2) var(--space-3);
|
|
512
|
+
border-block-start: 1px solid var(--line);
|
|
513
|
+
background: var(--surface-2);
|
|
514
|
+
font-size: var(--text-xs);
|
|
515
|
+
color: var(--text-faint);
|
|
516
|
+
}
|
|
517
|
+
/* The character count. .is-over turns it red past data-limit — a soft
|
|
518
|
+
limit, since silently refusing keystrokes reads as a broken keyboard. The
|
|
519
|
+
server has to enforce it, and role="status" makes it audible. */
|
|
520
|
+
.editor-count { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
|
|
521
|
+
.editor-count.is-over { color: var(--bad-700); font-weight: 620; }
|
|
522
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 00 Layers
|
|
3
|
+
The whole cascade contract in one place, declared before any rule exists.
|
|
4
|
+
Order is decided here, not by file order, not by specificity, and never by
|
|
5
|
+
!important.
|
|
6
|
+
|
|
7
|
+
Later layers win. Anything you write OUTSIDE a layer beats every layer, so
|
|
8
|
+
a plain rule in a page template always wins with no escalation.
|
|
9
|
+
|
|
10
|
+
deck.* the framework
|
|
11
|
+
app.* your slots, in the order you almost certainly want them
|
|
12
|
+
|
|
13
|
+
In a Keel view you can write:
|
|
14
|
+
|
|
15
|
+
@layer app.pages {
|
|
16
|
+
.page-header { padding-block: var(--space-8); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
and it beats every deck rule without a single !important, while still being
|
|
20
|
+
overridable by app.overrides and by unlayered CSS.
|
|
21
|
+
========================================================================== */
|
|
22
|
+
|
|
23
|
+
@layer
|
|
24
|
+
deck.reset, /* normalize and element defaults */
|
|
25
|
+
deck.tokens, /* custom properties only */
|
|
26
|
+
deck.type, /* headings, prose, links */
|
|
27
|
+
deck.layout, /* containers, stack, grid, containers */
|
|
28
|
+
deck.components, /* buttons, forms, cards, grid, charts */
|
|
29
|
+
deck.mobile, /* sheets, tab bar, icons, emoji */
|
|
30
|
+
deck.motion, /* transitions, entrances, view trans. */
|
|
31
|
+
deck.effects, /* gradients, 3D, surface treatments */
|
|
32
|
+
deck.utilities, /* single-purpose classes */
|
|
33
|
+
deck.rtl, /* direction fixes, must beat utilities */
|
|
34
|
+
deck.print, /* paper */
|
|
35
|
+
|
|
36
|
+
app.base, /* your resets and element styles */
|
|
37
|
+
app.components, /* your components and deck overrides */
|
|
38
|
+
app.pages, /* per-page and per-view tweaks */
|
|
39
|
+
app.overrides; /* the escape hatch, still not !important */
|
|
40
|
+
|
|
41
|
+
/* --------------------------------------------------------------------------
|
|
42
|
+
Registered custom properties.
|
|
43
|
+
@property is not affected by layers. Registering a property gives it a type,
|
|
44
|
+
which is what lets the browser interpolate it — an angle or a color in a
|
|
45
|
+
custom property can now animate, which plain custom properties cannot do.
|
|
46
|
+
-------------------------------------------------------------------------- */
|
|
47
|
+
|
|
48
|
+
@property --g-angle {
|
|
49
|
+
syntax: "<angle>";
|
|
50
|
+
initial-value: 180deg;
|
|
51
|
+
inherits: false;
|
|
52
|
+
}
|
|
53
|
+
@property --g-from {
|
|
54
|
+
syntax: "<color>";
|
|
55
|
+
initial-value: transparent;
|
|
56
|
+
inherits: false;
|
|
57
|
+
}
|
|
58
|
+
@property --g-to {
|
|
59
|
+
syntax: "<color>";
|
|
60
|
+
initial-value: transparent;
|
|
61
|
+
inherits: false;
|
|
62
|
+
}
|
|
63
|
+
@property --g-stop {
|
|
64
|
+
syntax: "<percentage>";
|
|
65
|
+
initial-value: 50%;
|
|
66
|
+
inherits: false;
|
|
67
|
+
}
|
|
68
|
+
@property --sheen {
|
|
69
|
+
syntax: "<percentage>";
|
|
70
|
+
initial-value: -40%;
|
|
71
|
+
inherits: false;
|
|
72
|
+
}
|
|
73
|
+
@property --tilt-x {
|
|
74
|
+
syntax: "<angle>";
|
|
75
|
+
initial-value: 0deg;
|
|
76
|
+
inherits: false;
|
|
77
|
+
}
|
|
78
|
+
@property --tilt-y {
|
|
79
|
+
syntax: "<angle>";
|
|
80
|
+
initial-value: 0deg;
|
|
81
|
+
inherits: false;
|
|
82
|
+
}
|
|
83
|
+
@property --depth {
|
|
84
|
+
syntax: "<length>";
|
|
85
|
+
initial-value: 0px;
|
|
86
|
+
inherits: false;
|
|
87
|
+
}
|