@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1327 -0
  3. package/bin/deck.mjs +219 -0
  4. package/build.mjs +338 -0
  5. package/dist/api-buckets.json +10486 -0
  6. package/dist/api.json +57896 -0
  7. package/dist/brand/deck-apple-touch-icon.png +0 -0
  8. package/dist/brand/deck-logo-dark.svg +1 -0
  9. package/dist/brand/deck-logo-light.svg +1 -0
  10. package/dist/brand/deck-logo-stacked.svg +1 -0
  11. package/dist/brand/deck-logo.svg +1 -0
  12. package/dist/brand/deck-mark.svg +7 -0
  13. package/dist/brand/deck-og.png +0 -0
  14. package/dist/collisions.json +223 -0
  15. package/dist/deck-adapters.js +482 -0
  16. package/dist/deck-adapters.min.js +23 -0
  17. package/dist/deck-extras.js +823 -0
  18. package/dist/deck-extras.min.js +5 -0
  19. package/dist/deck-icons.svg +184 -0
  20. package/dist/deck.bundle.js +2638 -0
  21. package/dist/deck.bundle.min.js +86 -0
  22. package/dist/deck.css +7904 -0
  23. package/dist/deck.esm.js +2644 -0
  24. package/dist/deck.js +1331 -0
  25. package/dist/deck.min.css +32 -0
  26. package/dist/deck.min.js +60 -0
  27. package/dist/layers/anchor.css +192 -0
  28. package/dist/layers/buttons.css +210 -0
  29. package/dist/layers/charts.css +337 -0
  30. package/dist/layers/combobox.css +248 -0
  31. package/dist/layers/components.css +868 -0
  32. package/dist/layers/container.css +199 -0
  33. package/dist/layers/datagrid.css +335 -0
  34. package/dist/layers/datepicker.css +305 -0
  35. package/dist/layers/forms.css +401 -0
  36. package/dist/layers/gradients.css +302 -0
  37. package/dist/layers/inputs.css +522 -0
  38. package/dist/layers/layers.css +87 -0
  39. package/dist/layers/layout.css +235 -0
  40. package/dist/layers/logical.css +226 -0
  41. package/dist/layers/media.css +400 -0
  42. package/dist/layers/mobile.css +245 -0
  43. package/dist/layers/motion.css +379 -0
  44. package/dist/layers/nav.css +771 -0
  45. package/dist/layers/perf.css +234 -0
  46. package/dist/layers/print.css +198 -0
  47. package/dist/layers/reset.css +103 -0
  48. package/dist/layers/space3d.css +280 -0
  49. package/dist/layers/toasts.css +201 -0
  50. package/dist/layers/tokens.css +229 -0
  51. package/dist/layers/type.css +114 -0
  52. package/dist/layers/utilities.css +241 -0
  53. package/dist/sizes.json +52 -0
  54. package/dist/usage.json +6340 -0
  55. package/package.json +94 -0
  56. package/src/00-layers.css +87 -0
  57. package/src/01-tokens.css +229 -0
  58. package/src/02-reset.css +103 -0
  59. package/src/03-type.css +114 -0
  60. package/src/04-layout.css +235 -0
  61. package/src/05-buttons.css +210 -0
  62. package/src/06-forms.css +401 -0
  63. package/src/07-components.css +868 -0
  64. package/src/08-mobile.css +245 -0
  65. package/src/09-utilities.css +241 -0
  66. package/src/10-datepicker.css +305 -0
  67. package/src/11-combobox.css +248 -0
  68. package/src/12-datagrid.css +335 -0
  69. package/src/13-toasts.css +201 -0
  70. package/src/14-charts.css +337 -0
  71. package/src/16-motion.css +379 -0
  72. package/src/18-container.css +199 -0
  73. package/src/19-logical.css +226 -0
  74. package/src/20-gradients.css +302 -0
  75. package/src/21-space3d.css +280 -0
  76. package/src/22-nav.css +771 -0
  77. package/src/23-inputs.css +522 -0
  78. package/src/24-media.css +400 -0
  79. package/src/25-anchor.css +192 -0
  80. package/src/26-perf.css +234 -0
  81. package/src/99-print.css +198 -0
  82. package/src/brand/deck-apple-touch-icon.png +0 -0
  83. package/src/brand/deck-logo-dark.svg +1 -0
  84. package/src/brand/deck-logo-light.svg +1 -0
  85. package/src/brand/deck-logo-stacked.svg +1 -0
  86. package/src/brand/deck-logo.svg +1 -0
  87. package/src/brand/deck-mark.svg +7 -0
  88. package/src/brand/deck-og.png +0 -0
  89. package/src/brand/sources.json +7 -0
  90. package/src/deck-icons.svg +184 -0
  91. package/src/js/deck-adapters.js +482 -0
  92. package/src/js/deck-extras.js +823 -0
  93. package/src/js/deck.js +1331 -0
@@ -0,0 +1,401 @@
1
+ /* ==========================================================================
2
+ Deck — 06 Forms
3
+ Inputs are 16px on touch devices so iOS never zooms on focus.
4
+ ========================================================================== */
5
+
6
+ @layer deck.components {
7
+
8
+ /* One form control and everything attached to it: label, input, help
9
+ text, error. The vertical gap between them comes from here, so a field
10
+ never needs margins. */
11
+ .field {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--space-2);
15
+ min-inline-size: 0;
16
+ }
17
+
18
+ /* The label for a control. A flex row, so a "required" or "optional"
19
+ marker sits beside the text without extra markup. */
20
+ .label {
21
+ font-size: var(--text-sm);
22
+ font-weight: 580;
23
+ color: var(--text);
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--space-2);
27
+ }
28
+ /* The marker for an optional field. Where most fields are required this
29
+ reads better than an asterisk on nearly every label. */
30
+ .label .optional { color: var(--text-faint); font-weight: 400; }
31
+ /* The marker beside a required field's label. Visual only — a screen
32
+ reader announces the required attribute, not the asterisk. */
33
+ .label .required { color: var(--bad-500); }
34
+
35
+ /* A hint below a control. Needs aria-describedby on the control to reach
36
+ a screen reader — sitting next to it is not an association. */
37
+ .help { font-size: var(--text-sm); color: var(--text-muted); }
38
+ /* A validation message below a control. Lightened for dark mode with
39
+ light-dark(), and aligned to flex-start so an icon stays on the first
40
+ line of a two-line message. */
41
+ .error {
42
+ font-size: var(--text-sm);
43
+ color: light-dark(var(--bad-700), oklch(76% .13 var(--hue-bad)));
44
+ display: flex;
45
+ gap: var(--space-2);
46
+ align-items: flex-start;
47
+ }
48
+
49
+ .input, .textarea, .select {
50
+ inline-size: 100%;
51
+ min-block-size: var(--control-h);
52
+ padding: var(--space-2) var(--space-3);
53
+ background: var(--surface);
54
+ color: var(--text);
55
+ border: 1px solid var(--line-strong);
56
+ border-radius: var(--r-sm);
57
+ box-shadow: var(--shadow-1);
58
+ font-size: var(--text-base);
59
+ line-height: 1.45;
60
+ transition: border-color var(--dur-1) var(--ease-out),
61
+ box-shadow var(--dur-1) var(--ease-out),
62
+ background-color var(--dur-1) var(--ease-out);
63
+ }
64
+
65
+ @media (pointer: coarse) {
66
+ .input, .textarea, .select { font-size: 16px; }
67
+ }
68
+
69
+ .input::placeholder, .textarea::placeholder { color: var(--text-faint); }
70
+
71
+ .input:hover, .textarea:hover, .select:hover { border-color: var(--ink-400); }
72
+
73
+ .input:focus, .textarea:focus, .select:focus {
74
+ outline: none;
75
+ border-color: var(--focus);
76
+ box-shadow: var(--ring);
77
+ }
78
+
79
+ .input[disabled], .textarea[disabled], .select[disabled] {
80
+ background: var(--bg-sunken);
81
+ color: var(--text-faint);
82
+ cursor: not-allowed;
83
+ box-shadow: none;
84
+ }
85
+ .input[readonly] { background: var(--surface-2); }
86
+
87
+ /* Grows with its content instead of scrolling in a 4-row box */
88
+ .textarea {
89
+ min-block-size: calc(var(--control-h) * 2);
90
+ resize: vertical;
91
+ field-sizing: content;
92
+ max-block-size: 22rem;
93
+ }
94
+
95
+ .select {
96
+ appearance: none;
97
+ padding-inline-end: var(--space-10);
98
+ background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
99
+ linear-gradient(135deg, currentColor 50%, transparent 50%);
100
+ background-position: calc(100% - 19px) calc(50% + 1px), calc(100% - 14px) calc(50% + 1px);
101
+ background-size: 5px 5px, 5px 5px;
102
+ background-repeat: no-repeat;
103
+ cursor: pointer;
104
+ }
105
+
106
+ /* Validation only after the user has actually interacted */
107
+ /* Marks a field invalid from your own validation, producing the same
108
+ border and ring as :user-invalid without needing the browser to agree.
109
+ One of the few states an author writes by hand. */
110
+ .input:user-invalid, .textarea:user-invalid, .select:user-invalid,
111
+ .field.is-invalid :is(.input, .textarea, .select) {
112
+ border-color: var(--bad-500);
113
+ }
114
+ .input:user-invalid:focus, .field.is-invalid :is(.input, .textarea, .select):focus {
115
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--bad-500) 26%, transparent);
116
+ }
117
+
118
+ /* --- Input group: prefix / suffix / attached button --------------------- */
119
+ /* A prefix, suffix or attached button joined to a control. The group
120
+ takes over the border, radius and shadow, and the input inside strips
121
+ its own, so the seam does not double up. :focus-within puts the ring
122
+ around the whole assembly. */
123
+ .input-group {
124
+ display: flex;
125
+ align-items: stretch;
126
+ inline-size: 100%;
127
+ border: 1px solid var(--line-strong);
128
+ border-radius: var(--r-sm);
129
+ background: var(--surface);
130
+ box-shadow: var(--shadow-1);
131
+ overflow: hidden;
132
+ }
133
+ .input-group:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
134
+ .input-group > .input {
135
+ border: 0;
136
+ border-radius: 0;
137
+ box-shadow: none;
138
+ background: transparent;
139
+ }
140
+ .input-group > .input:focus { box-shadow: none; }
141
+ /* A prefix or suffix attached to a control inside .input-group. Its
142
+ divider is border-inline-end normally and border-inline-start on
143
+ :last-child, so a leading and a trailing addon each get their rule on
144
+ the correct side in both writing directions. */
145
+ .input-group > .addon {
146
+ display: inline-flex;
147
+ align-items: center;
148
+ gap: var(--space-2);
149
+ padding-inline: var(--space-3);
150
+ background: var(--surface-2);
151
+ color: var(--text-muted);
152
+ font-size: var(--text-sm);
153
+ white-space: nowrap;
154
+ border-inline-end: 1px solid var(--line);
155
+ }
156
+ .input-group > .addon:last-child { border-inline-start: 1px solid var(--line); border-inline-end: 0; }
157
+ .input-group > .btn { border-radius: 0; border-block: 0; border-inline-end: 0; box-shadow: none; }
158
+
159
+ /* --- Search field ------------------------------------------------------- */
160
+ /* A control with an icon inside its starting edge. The icon is
161
+ pointer-events: none, so clicking it focuses the field underneath
162
+ rather than doing nothing. */
163
+ .search { position: relative; display: flex; align-items: center; }
164
+ .search > .icon {
165
+ position: absolute;
166
+ inset-inline-start: var(--space-3);
167
+ color: var(--text-faint);
168
+ pointer-events: none;
169
+ }
170
+ .search > .input { padding-inline-start: var(--space-10); border-radius: var(--r-full); }
171
+
172
+ /* --- Checkbox and radio ------------------------------------------------- */
173
+ /* A checkbox or radio and its text, laid out as a row that clears the
174
+ 44px touch target. Wrap the text in .check-text to add a second line. */
175
+ .check {
176
+ display: flex;
177
+ align-items: flex-start;
178
+ gap: var(--space-3);
179
+ min-block-size: var(--tap);
180
+ padding-block: var(--space-2);
181
+ cursor: pointer;
182
+ line-height: 1.4;
183
+ }
184
+ .check input[type="checkbox"], .check input[type="radio"] {
185
+ appearance: none;
186
+ flex: 0 0 auto;
187
+ inline-size: 20px;
188
+ block-size: 20px;
189
+ margin-block-start: 1px;
190
+ border: 1.5px solid var(--line-strong);
191
+ background: var(--surface);
192
+ border-radius: var(--r-xs);
193
+ display: grid;
194
+ place-content: center;
195
+ cursor: pointer;
196
+ transition: background-color var(--dur-1) var(--ease-out),
197
+ border-color var(--dur-1) var(--ease-out);
198
+ }
199
+ .check input[type="radio"] { border-radius: var(--r-full); }
200
+ .check input:hover { border-color: var(--brand-400); }
201
+ .check input:checked { background: var(--brand-600); border-color: var(--brand-600); }
202
+ .check input[type="checkbox"]::before {
203
+ content: "";
204
+ inline-size: 11px;
205
+ block-size: 11px;
206
+ scale: 0;
207
+ transition: scale var(--dur-2) var(--ease-spring);
208
+ box-shadow: inset 1em 1em var(--text-on-brand);
209
+ clip-path: polygon(14% 44%, 0 65%, 41% 100%, 100% 16%, 84% 0, 39% 68%);
210
+ }
211
+ .check input[type="radio"]::before {
212
+ content: "";
213
+ inline-size: 9px;
214
+ block-size: 9px;
215
+ border-radius: var(--r-full);
216
+ background: var(--text-on-brand);
217
+ scale: 0;
218
+ transition: scale var(--dur-2) var(--ease-spring);
219
+ }
220
+ .check input:checked::before { scale: 1; }
221
+ .check input:indeterminate { background: var(--brand-600); border-color: var(--brand-600); }
222
+ .check input:indeterminate::before {
223
+ scale: 1;
224
+ clip-path: polygon(10% 42%, 90% 42%, 90% 58%, 10% 58%);
225
+ }
226
+ .check input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
227
+ /* The text beside a checkbox or radio. A column, so a .check-note can sit
228
+ under the label as a second line. */
229
+ /* The text beside a checkbox, as a column, so a .check-note can sit
230
+ beneath the label as a second line. */
231
+ .check .check-text { display: flex; flex-direction: column; gap: 2px; }
232
+ /* A muted second line inside .check-text. It is inside the label, so it
233
+ becomes part of the accessible name — move it out and use
234
+ aria-describedby if that name gets too long. */
235
+ .check .check-note { font-size: var(--text-sm); color: var(--text-muted); }
236
+
237
+ /* Card-style choice, good for pricing and plan pickers */
238
+ .check-card {
239
+ border: 1px solid var(--line);
240
+ border-radius: var(--r-md);
241
+ padding: var(--space-4);
242
+ background: var(--surface);
243
+ transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
244
+ }
245
+ .check-card:has(input:checked) {
246
+ border-color: var(--brand-500);
247
+ background: var(--brand-soft);
248
+ }
249
+
250
+ /* --- Switch ------------------------------------------------------------- */
251
+ /* An on/off control that takes effect immediately, drawn over a real
252
+ checkbox. If the reader has to press Save afterwards, use .check
253
+ instead. Add role="switch" when "on"/"off" reads better than
254
+ "checked"/"unchecked". */
255
+ .switch { display: flex; align-items: center; gap: var(--space-3); min-block-size: var(--tap); cursor: pointer; }
256
+ .switch input {
257
+ appearance: none;
258
+ flex: 0 0 auto;
259
+ inline-size: 46px;
260
+ block-size: 28px;
261
+ border-radius: var(--r-full);
262
+ background: var(--ink-300);
263
+ position: relative;
264
+ cursor: pointer;
265
+ transition: background-color var(--dur-2) var(--ease-out);
266
+ }
267
+ .switch input::after {
268
+ content: "";
269
+ position: absolute;
270
+ inset-block-start: 3px;
271
+ inset-inline-start: 3px;
272
+ inline-size: 22px;
273
+ block-size: 22px;
274
+ border-radius: var(--r-full);
275
+ background: var(--ink-0);
276
+ box-shadow: var(--shadow-2);
277
+ transition: translate var(--dur-2) var(--ease-spring);
278
+ }
279
+ .switch input:checked { background: var(--brand-600); }
280
+ .switch input:checked::after { translate: 18px 0; }
281
+ .switch input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
282
+
283
+ /* --- Range -------------------------------------------------------------- */
284
+ /* A slider. The track and thumb are vendor pseudo-elements with
285
+ different names and different box models per engine, so every
286
+ declaration is written twice — WebKit needs a negative block-start
287
+ margin to centre the thumb and Firefox does not. */
288
+ .range {
289
+ inline-size: 100%;
290
+ appearance: none;
291
+ background: transparent;
292
+ block-size: var(--tap);
293
+ }
294
+ .range::-webkit-slider-runnable-track {
295
+ block-size: 6px;
296
+ border-radius: var(--r-full);
297
+ background: var(--ink-200);
298
+ }
299
+ .range::-moz-range-track {
300
+ block-size: 6px;
301
+ border-radius: var(--r-full);
302
+ background: var(--ink-200);
303
+ }
304
+ .range::-webkit-slider-thumb {
305
+ appearance: none;
306
+ margin-block-start: -8px;
307
+ inline-size: 22px;
308
+ block-size: 22px;
309
+ border-radius: var(--r-full);
310
+ background: var(--brand-600);
311
+ border: 3px solid var(--ink-0);
312
+ box-shadow: var(--shadow-2);
313
+ cursor: grab;
314
+ }
315
+ .range::-moz-range-thumb {
316
+ inline-size: 22px;
317
+ block-size: 22px;
318
+ border-radius: var(--r-full);
319
+ background: var(--brand-600);
320
+ border: 3px solid var(--ink-0);
321
+ box-shadow: var(--shadow-2);
322
+ cursor: grab;
323
+ }
324
+ .range:active::-webkit-slider-thumb { cursor: grabbing; scale: 1.08; }
325
+
326
+ /* --- File --------------------------------------------------------------- */
327
+ /* A dashed drop zone wrapping a real file input. The input is 1px and
328
+ transparent rather than display: none, so it keeps its place in the
329
+ tab order, and :focus-within is what makes that focus visible. Deck
330
+ styles the zone; dropping needs a handler of your own. */
331
+ .file {
332
+ display: grid;
333
+ place-items: center;
334
+ gap: var(--space-2);
335
+ padding: var(--space-6);
336
+ border: 1.5px dashed var(--line-strong);
337
+ border-radius: var(--r-md);
338
+ background: var(--surface-2);
339
+ text-align: center;
340
+ color: var(--text-muted);
341
+ cursor: pointer;
342
+ transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
343
+ }
344
+ .file:hover, .file:focus-within { border-color: var(--brand-500); background: var(--brand-soft); }
345
+ .file input[type="file"] { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; }
346
+
347
+ /* --- Fieldset ----------------------------------------------------------- */
348
+ /* A bordered group of controls that answer one question. Its legend is
349
+ the group's accessible name, which is what a radio group needs and
350
+ what a .label above one does not provide. */
351
+ .fieldset {
352
+ border: 1px solid var(--line);
353
+ border-radius: var(--r-md);
354
+ padding: var(--space-5);
355
+ display: flex;
356
+ flex-direction: column;
357
+ gap: var(--space-4);
358
+ min-inline-size: 0;
359
+ }
360
+ .fieldset > legend {
361
+ padding-inline: var(--space-2);
362
+ font-size: var(--text-sm);
363
+ font-weight: 600;
364
+ color: var(--text-muted);
365
+ }
366
+
367
+ /* Two fields side by side on anything wider than a phone */
368
+ .field-row {
369
+ display: grid;
370
+ gap: var(--space-4);
371
+ grid-template-columns: 1fr;
372
+ }
373
+ @media (min-width: 40rem) {
374
+ .field-row { grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); }
375
+ }
376
+
377
+ /* Sticky save bar for long forms on mobile */
378
+ .form-actions {
379
+ display: flex;
380
+ gap: var(--space-3);
381
+ flex-wrap: wrap;
382
+ }
383
+ /* Pins the action row to the bottom of the viewport with a blur behind
384
+ it and a safe-area inset for the home indicator. Add matching bottom
385
+ padding to the form, or the last field sits underneath it. */
386
+ .form-actions-sticky {
387
+ position: sticky;
388
+ inset-block-end: 0;
389
+ z-index: var(--z-sticky);
390
+ background: color-mix(in oklab, var(--bg) 88%, transparent);
391
+ backdrop-filter: blur(12px);
392
+ -webkit-backdrop-filter: blur(12px);
393
+ border-block-start: 1px solid var(--line);
394
+ padding-block: var(--space-3);
395
+ padding-block-end: calc(var(--space-3) + env(safe-area-inset-bottom));
396
+ }
397
+ .form-actions-sticky > .btn { flex: 1 1 auto; }
398
+ @media (min-width: 48rem) {
399
+ .form-actions-sticky > .btn { flex: 0 0 auto; }
400
+ }
401
+ }