@devslab/dds-css 0.3.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 +74 -0
- package/README.ko.md +25 -0
- package/README.md +26 -0
- package/dist/components/avatar.css +47 -0
- package/dist/components/badge.css +47 -0
- package/dist/components/base.css +31 -0
- package/dist/components/button.css +114 -0
- package/dist/components/card.css +38 -0
- package/dist/components/checkbox-radio.css +91 -0
- package/dist/components/chip.css +51 -0
- package/dist/components/dialog.css +58 -0
- package/dist/components/divider.css +18 -0
- package/dist/components/emptystate.css +47 -0
- package/dist/components/iconbutton.css +71 -0
- package/dist/components/listrow.css +81 -0
- package/dist/components/select.css +66 -0
- package/dist/components/skeleton.css +32 -0
- package/dist/components/spinner.css +34 -0
- package/dist/components/switch.css +78 -0
- package/dist/components/tabs.css +59 -0
- package/dist/components/textarea.css +42 -0
- package/dist/components/textfield.css +63 -0
- package/dist/components/toast.css +72 -0
- package/dist/components/tooltip.css +43 -0
- package/dist/dds.css +1206 -0
- package/package.json +28 -0
package/dist/dds.css
ADDED
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
/* @devslab/dds-css — generated bundle, do not edit. Source: src/*.css
|
|
2
|
+
* Requires @devslab/dds-tokens/tokens.css to be loaded first. */
|
|
3
|
+
|
|
4
|
+
/* dds-css shared plumbing. Components reference ONLY semantic tokens
|
|
5
|
+
* (var(--dds-*)) — tokens.css must be loaded first. */
|
|
6
|
+
[class^="dds-"],
|
|
7
|
+
[class*=" dds-"] {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
[dir="rtl"] .dds-icon--directional {
|
|
12
|
+
transform: scaleX(-1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-reduced-motion: reduce) {
|
|
16
|
+
:root [class^="dds-"],
|
|
17
|
+
:root [class*=" dds-"] {
|
|
18
|
+
animation-duration: 0.01ms;
|
|
19
|
+
animation-iteration-count: 1;
|
|
20
|
+
scroll-behavior: auto;
|
|
21
|
+
transition-duration: 0.01ms;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (forced-colors: active) {
|
|
26
|
+
[class^="dds-"],
|
|
27
|
+
[class*=" dds-"] {
|
|
28
|
+
forced-color-adjust: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
:is([class^="dds-"], [class*=" dds-"]):focus-visible {
|
|
32
|
+
outline-color: Highlight;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Button — spec §4.2: default → hover → pressed → focus-visible → disabled → loading.
|
|
37
|
+
* Variants: primary / secondary / ghost / danger. Sizes: sm / md(default) / lg.
|
|
38
|
+
* Control heights come from the spacing scale (32/40/48); type from the typo
|
|
39
|
+
* scale (label for sm/md, body-1 size for lg). Loading = aria-busy="true" with
|
|
40
|
+
* a .dds-spinner child. */
|
|
41
|
+
|
|
42
|
+
.dds-btn {
|
|
43
|
+
appearance: none;
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
gap: var(--dds-space-8);
|
|
48
|
+
min-block-size: var(--dds-space-40);
|
|
49
|
+
padding-block: var(--dds-space-8);
|
|
50
|
+
padding-inline: var(--dds-space-16);
|
|
51
|
+
border: 1px solid transparent;
|
|
52
|
+
border-radius: var(--dds-radius-md);
|
|
53
|
+
background: none;
|
|
54
|
+
font-family: var(--dds-font-family-sans);
|
|
55
|
+
font-size: var(--dds-typo-label-font-size);
|
|
56
|
+
line-height: var(--dds-typo-label-line-height);
|
|
57
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
58
|
+
color: var(--dds-color-text-primary);
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
61
|
+
-webkit-tap-highlight-color: transparent;
|
|
62
|
+
white-space: normal;
|
|
63
|
+
overflow-wrap: anywhere;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.dds-btn:focus-visible {
|
|
67
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
68
|
+
outline-offset: var(--dds-space-2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
72
|
+
.dds-btn[disabled],
|
|
73
|
+
.dds-btn[aria-busy="true"] {
|
|
74
|
+
opacity: 0.45;
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
}
|
|
77
|
+
.dds-btn[aria-busy="true"] {
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* variants ---------------------------------------------------------------- */
|
|
82
|
+
|
|
83
|
+
.dds-btn--primary {
|
|
84
|
+
background: var(--dds-color-bg-brand);
|
|
85
|
+
color: var(--dds-color-text-on-brand); /* zinc.950, not white — spec §3.1 on-brand note */
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
88
|
+
.dds-btn--primary:not([disabled]):not([aria-busy="true"]):hover {
|
|
89
|
+
background: var(--dds-color-bg-brand-hover);
|
|
90
|
+
}
|
|
91
|
+
.dds-btn--primary:not([disabled]):active {
|
|
92
|
+
background: var(--dds-color-bg-brand-hover);
|
|
93
|
+
box-shadow: inset 0 0 0 999px var(--dds-color-overlay-pressed-tint);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dds-btn--secondary {
|
|
97
|
+
border-color: var(--dds-color-border-strong);
|
|
98
|
+
}
|
|
99
|
+
.dds-btn--secondary:not([disabled]):not([aria-busy="true"]):hover {
|
|
100
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
101
|
+
}
|
|
102
|
+
.dds-btn--secondary:not([disabled]):active {
|
|
103
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dds-btn--ghost {
|
|
107
|
+
color: var(--dds-color-text-brand);
|
|
108
|
+
}
|
|
109
|
+
.dds-btn--ghost:not([disabled]):not([aria-busy="true"]):hover {
|
|
110
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
111
|
+
}
|
|
112
|
+
.dds-btn--ghost:not([disabled]):active {
|
|
113
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.dds-btn--danger {
|
|
117
|
+
background: var(--dds-color-status-danger);
|
|
118
|
+
color: var(--dds-color-text-on-status);
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
}
|
|
121
|
+
.dds-btn--danger:not([disabled]):not([aria-busy="true"]):hover {
|
|
122
|
+
box-shadow: inset 0 0 0 999px var(--dds-color-overlay-hover-tint);
|
|
123
|
+
}
|
|
124
|
+
.dds-btn--danger:not([disabled]):active {
|
|
125
|
+
box-shadow: inset 0 0 0 999px var(--dds-color-overlay-pressed-tint);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* sizes ------------------------------------------------------------------- */
|
|
129
|
+
|
|
130
|
+
.dds-btn--sm {
|
|
131
|
+
min-block-size: var(--dds-space-32);
|
|
132
|
+
padding-block: var(--dds-space-4);
|
|
133
|
+
padding-inline: var(--dds-space-12);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.dds-btn--lg {
|
|
137
|
+
min-block-size: var(--dds-space-48);
|
|
138
|
+
padding-block: var(--dds-space-12);
|
|
139
|
+
padding-inline: var(--dds-space-20);
|
|
140
|
+
font-size: var(--dds-typo-body-1-font-size);
|
|
141
|
+
line-height: var(--dds-typo-body-1-line-height);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@media (pointer: coarse) {
|
|
145
|
+
.dds-btn,
|
|
146
|
+
.dds-btn--sm {
|
|
147
|
+
min-block-size: 44px;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* IconButton — a Button whose only content is an icon (spec §4.3 v1 inventory).
|
|
152
|
+
* Same state set and sizes as .dds-btn (32/40/48 from the spacing scale) so an
|
|
153
|
+
* icon button never lands a few px off the text button next to it — the row
|
|
154
|
+
* misalignment this house rejects. The icon inherits currentColor.
|
|
155
|
+
* Markup MUST carry an accessible name: aria-label (spec §6). */
|
|
156
|
+
|
|
157
|
+
.dds-iconbtn {
|
|
158
|
+
appearance: none;
|
|
159
|
+
display: inline-flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
flex: none;
|
|
163
|
+
inline-size: var(--dds-space-40);
|
|
164
|
+
block-size: var(--dds-space-40);
|
|
165
|
+
border: 1px solid transparent;
|
|
166
|
+
border-radius: var(--dds-radius-md);
|
|
167
|
+
background: none;
|
|
168
|
+
color: var(--dds-color-text-primary);
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
171
|
+
-webkit-tap-highlight-color: transparent;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.dds-iconbtn:not([disabled]):hover {
|
|
175
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
176
|
+
}
|
|
177
|
+
.dds-iconbtn:not([disabled]):active {
|
|
178
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
179
|
+
}
|
|
180
|
+
.dds-iconbtn:focus-visible {
|
|
181
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
182
|
+
outline-offset: var(--dds-space-2);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
186
|
+
.dds-iconbtn[disabled] {
|
|
187
|
+
opacity: 0.45;
|
|
188
|
+
cursor: not-allowed;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* variants ---------------------------------------------------------------- */
|
|
192
|
+
|
|
193
|
+
.dds-iconbtn--secondary {
|
|
194
|
+
border-color: var(--dds-color-border-strong);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.dds-iconbtn--danger {
|
|
198
|
+
color: var(--dds-color-status-danger);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* sizes ------------------------------------------------------------------- */
|
|
202
|
+
|
|
203
|
+
/* --sm is 32×32: still above the 24×24 web floor, but below the 44×44 mobile
|
|
204
|
+
* target — on touch surfaces keep the default size (spec §6). */
|
|
205
|
+
.dds-iconbtn--sm {
|
|
206
|
+
inline-size: var(--dds-space-32);
|
|
207
|
+
block-size: var(--dds-space-32);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.dds-iconbtn--lg {
|
|
211
|
+
inline-size: var(--dds-space-48);
|
|
212
|
+
block-size: var(--dds-space-48);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@media (pointer: coarse) {
|
|
216
|
+
.dds-iconbtn,
|
|
217
|
+
.dds-iconbtn--sm {
|
|
218
|
+
min-inline-size: 44px;
|
|
219
|
+
min-block-size: 44px;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* TextField — label + input + help, error via .dds-field--error OR
|
|
224
|
+
* aria-invalid="true" on the input (markup should set aria-invalid +
|
|
225
|
+
* aria-describedby; error is never conveyed by border color alone — the
|
|
226
|
+
* help line carries the message, spec §6). */
|
|
227
|
+
|
|
228
|
+
.dds-field {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
gap: var(--dds-space-4);
|
|
232
|
+
font-family: var(--dds-font-family-sans);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.dds-field__label {
|
|
236
|
+
font-size: var(--dds-typo-label-font-size);
|
|
237
|
+
line-height: var(--dds-typo-label-line-height);
|
|
238
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
239
|
+
color: var(--dds-color-text-primary);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.dds-input {
|
|
243
|
+
font-family: var(--dds-font-family-sans);
|
|
244
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
245
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
246
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
247
|
+
height: var(--dds-space-40);
|
|
248
|
+
padding: 0 var(--dds-space-12);
|
|
249
|
+
background: var(--dds-color-bg-default);
|
|
250
|
+
color: var(--dds-color-text-primary);
|
|
251
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
252
|
+
border-radius: var(--dds-radius-md);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.dds-input::placeholder {
|
|
256
|
+
color: var(--dds-color-text-muted);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.dds-input:focus-visible {
|
|
260
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
261
|
+
outline-offset: 0;
|
|
262
|
+
border-color: transparent;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
266
|
+
.dds-input[disabled] {
|
|
267
|
+
opacity: 0.45;
|
|
268
|
+
cursor: not-allowed;
|
|
269
|
+
background: var(--dds-color-bg-subtle);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.dds-field__help {
|
|
273
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
274
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
275
|
+
font-weight: var(--dds-typo-caption-font-weight);
|
|
276
|
+
color: var(--dds-color-text-muted);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.dds-field--error .dds-input,
|
|
280
|
+
.dds-input[aria-invalid="true"] {
|
|
281
|
+
border-color: var(--dds-color-status-danger);
|
|
282
|
+
}
|
|
283
|
+
.dds-field--error .dds-field__help {
|
|
284
|
+
color: var(--dds-color-status-danger);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Textarea — the multi-line member of the TextField family: it lives in the
|
|
288
|
+
* same .dds-field wrapper and shares label/help/error behavior with .dds-input.
|
|
289
|
+
* Row rule from the house UI discipline: a textarea gets its own row, never a
|
|
290
|
+
* row shared with single-line controls (their heights can't be reconciled).
|
|
291
|
+
* min-height is two control heights so an empty textarea already reads as
|
|
292
|
+
* multi-line. */
|
|
293
|
+
|
|
294
|
+
.dds-textarea {
|
|
295
|
+
font-family: var(--dds-font-family-sans);
|
|
296
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
297
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
298
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
299
|
+
min-height: calc(var(--dds-space-40) * 2);
|
|
300
|
+
padding: var(--dds-space-8) var(--dds-space-12);
|
|
301
|
+
background: var(--dds-color-bg-default);
|
|
302
|
+
color: var(--dds-color-text-primary);
|
|
303
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
304
|
+
border-radius: var(--dds-radius-md);
|
|
305
|
+
resize: vertical;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.dds-textarea::placeholder {
|
|
309
|
+
color: var(--dds-color-text-muted);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.dds-textarea:focus-visible {
|
|
313
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
314
|
+
outline-offset: 0;
|
|
315
|
+
border-color: transparent;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
319
|
+
.dds-textarea[disabled] {
|
|
320
|
+
opacity: 0.45;
|
|
321
|
+
cursor: not-allowed;
|
|
322
|
+
background: var(--dds-color-bg-subtle);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.dds-field--error .dds-textarea,
|
|
326
|
+
.dds-textarea[aria-invalid="true"] {
|
|
327
|
+
border-color: var(--dds-color-status-danger);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Select — native <select> with the native arrow replaced. Leaving the OS
|
|
331
|
+
* arrow in place is a rejection criterion in this house: the same control
|
|
332
|
+
* then looks different on every platform and never matches the input next to
|
|
333
|
+
* it. The wrapper owns the chevron so the element stays a real <select>
|
|
334
|
+
* (keyboard, mobile pickers, form semantics all free).
|
|
335
|
+
*
|
|
336
|
+
* Markup: <span class="dds-select"><select class="dds-select__input">…
|
|
337
|
+
* Lives inside .dds-field like .dds-input, so label/help/error come for free. */
|
|
338
|
+
|
|
339
|
+
.dds-select {
|
|
340
|
+
position: relative;
|
|
341
|
+
display: inline-flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
width: 100%;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.dds-select__input {
|
|
347
|
+
appearance: none;
|
|
348
|
+
font-family: var(--dds-font-family-sans);
|
|
349
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
350
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
351
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
352
|
+
width: 100%;
|
|
353
|
+
height: var(--dds-space-40);
|
|
354
|
+
padding-block: 0;
|
|
355
|
+
padding-inline: var(--dds-space-12) var(--dds-space-32);
|
|
356
|
+
background: var(--dds-color-bg-default);
|
|
357
|
+
color: var(--dds-color-text-primary);
|
|
358
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
359
|
+
border-radius: var(--dds-radius-md);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.dds-select__input:focus-visible {
|
|
364
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
365
|
+
outline-offset: 0;
|
|
366
|
+
border-color: transparent;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
370
|
+
.dds-select__input[disabled] {
|
|
371
|
+
opacity: 0.45;
|
|
372
|
+
cursor: not-allowed;
|
|
373
|
+
background: var(--dds-color-bg-subtle);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.dds-field--error .dds-select__input,
|
|
377
|
+
.dds-select__input[aria-invalid="true"] {
|
|
378
|
+
border-color: var(--dds-color-status-danger);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Chevron glyph geometry (8px box, 1.6px stroke) is drawn, not spaced — the
|
|
382
|
+
* same allowance as the 6px badge dot. translateY(-70%) centers the drawn
|
|
383
|
+
* corner, which sits below the box center once rotated. */
|
|
384
|
+
.dds-select::after {
|
|
385
|
+
content: "";
|
|
386
|
+
position: absolute;
|
|
387
|
+
inset-block-start: 50%;
|
|
388
|
+
inset-inline-end: var(--dds-space-12);
|
|
389
|
+
width: 8px;
|
|
390
|
+
height: 8px;
|
|
391
|
+
border-inline-end: 1.6px solid var(--dds-color-text-muted);
|
|
392
|
+
border-block-end: 1.6px solid var(--dds-color-text-muted);
|
|
393
|
+
transform: translateY(-70%) rotate(45deg);
|
|
394
|
+
pointer-events: none;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Checkbox / Radio — one class for both; the input's own type decides the
|
|
398
|
+
* shape (square + tick vs circle + dot), so markup never has to declare the
|
|
399
|
+
* shape twice. The control stays a real <input> (keyboard, form data, AT
|
|
400
|
+
* roles); only its box is redrawn.
|
|
401
|
+
*
|
|
402
|
+
* Box 18px, tick 10×6, dot 8px: glyph geometry, not spacing (same allowance
|
|
403
|
+
* as the 6px badge dot). The 18px box sits inside a label whose hit area is
|
|
404
|
+
* the whole row — keep the label text clickable rather than growing the box. */
|
|
405
|
+
|
|
406
|
+
.dds-check {
|
|
407
|
+
display: inline-flex;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: var(--dds-space-8);
|
|
410
|
+
font-family: var(--dds-font-family-sans);
|
|
411
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
412
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
413
|
+
color: var(--dds-color-text-primary);
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.dds-check__input {
|
|
418
|
+
appearance: none;
|
|
419
|
+
flex: none;
|
|
420
|
+
display: grid;
|
|
421
|
+
place-items: center;
|
|
422
|
+
margin: 0;
|
|
423
|
+
width: 18px;
|
|
424
|
+
height: 18px;
|
|
425
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
426
|
+
border-radius: var(--dds-radius-sm);
|
|
427
|
+
background: var(--dds-color-bg-default);
|
|
428
|
+
cursor: inherit;
|
|
429
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.dds-check__input[type="radio"] {
|
|
433
|
+
border-radius: var(--dds-radius-full);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.dds-check__input:not([disabled]):hover {
|
|
437
|
+
border-color: var(--dds-color-text-muted);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.dds-check__input:checked {
|
|
441
|
+
background: var(--dds-color-bg-brand);
|
|
442
|
+
border-color: var(--dds-color-bg-brand);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* the mark is on-brand (zinc.950), never white — spec §3.1 on-brand note */
|
|
446
|
+
.dds-check__input[type="checkbox"]:checked::before {
|
|
447
|
+
content: "";
|
|
448
|
+
width: 10px;
|
|
449
|
+
height: 6px;
|
|
450
|
+
border-inline-start: 2px solid var(--dds-color-text-on-brand);
|
|
451
|
+
border-block-end: 2px solid var(--dds-color-text-on-brand);
|
|
452
|
+
transform: translateY(-1px) rotate(-45deg);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.dds-check__input[type="checkbox"]:indeterminate::before {
|
|
456
|
+
content: "";
|
|
457
|
+
width: 10px;
|
|
458
|
+
height: 2px;
|
|
459
|
+
background: var(--dds-color-text-on-brand);
|
|
460
|
+
}
|
|
461
|
+
.dds-check__input[type="checkbox"]:indeterminate {
|
|
462
|
+
background: var(--dds-color-bg-brand);
|
|
463
|
+
border-color: var(--dds-color-bg-brand);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.dds-check__input[type="radio"]:checked::before {
|
|
467
|
+
content: "";
|
|
468
|
+
width: 8px;
|
|
469
|
+
height: 8px;
|
|
470
|
+
border-radius: var(--dds-radius-full);
|
|
471
|
+
background: var(--dds-color-text-on-brand);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.dds-check__input:focus-visible {
|
|
475
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
476
|
+
outline-offset: var(--dds-space-2);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* opacity is allowed for disabled only (spec §4.2) — it goes on the label so
|
|
480
|
+
* the box and its text dim once, not twice (nested opacity compounds). */
|
|
481
|
+
.dds-check:has(.dds-check__input[disabled]) {
|
|
482
|
+
opacity: 0.45;
|
|
483
|
+
cursor: not-allowed;
|
|
484
|
+
}
|
|
485
|
+
.dds-check__input[disabled] {
|
|
486
|
+
cursor: not-allowed;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* Switch — an immediate on/off (a checkbox that applies itself). Use a
|
|
490
|
+
* checkbox when the value only takes effect on submit.
|
|
491
|
+
* Markup: <input type="checkbox" role="switch" class="dds-switch__input">
|
|
492
|
+
*
|
|
493
|
+
* Track 36×20 and knob 16px are glyph geometry, not spacing. The knob is
|
|
494
|
+
* on-brand (zinc.950) when the track is brand-filled — the same rule that
|
|
495
|
+
* keeps white off cyan (spec §3.1) — and an elevated surface when off, so it
|
|
496
|
+
* reads as a raised knob in both themes. */
|
|
497
|
+
|
|
498
|
+
.dds-switch {
|
|
499
|
+
display: inline-flex;
|
|
500
|
+
align-items: center;
|
|
501
|
+
gap: var(--dds-space-8);
|
|
502
|
+
font-family: var(--dds-font-family-sans);
|
|
503
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
504
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
505
|
+
color: var(--dds-color-text-primary);
|
|
506
|
+
cursor: pointer;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.dds-switch__input {
|
|
510
|
+
appearance: none;
|
|
511
|
+
position: relative;
|
|
512
|
+
flex: none;
|
|
513
|
+
margin: 0;
|
|
514
|
+
width: 36px;
|
|
515
|
+
height: 20px;
|
|
516
|
+
border-radius: var(--dds-radius-full);
|
|
517
|
+
background: var(--dds-color-text-muted);
|
|
518
|
+
cursor: inherit;
|
|
519
|
+
transition: background-color var(--dds-motion-duration-base) var(--dds-motion-easing-standard);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.dds-switch__input::before {
|
|
523
|
+
content: "";
|
|
524
|
+
position: absolute;
|
|
525
|
+
inset-block-start: 2px;
|
|
526
|
+
inset-inline-start: 2px;
|
|
527
|
+
width: 16px;
|
|
528
|
+
height: 16px;
|
|
529
|
+
border-radius: var(--dds-radius-full);
|
|
530
|
+
background: var(--dds-color-bg-elevated);
|
|
531
|
+
box-shadow: var(--dds-elevation-1);
|
|
532
|
+
transition: transform var(--dds-motion-duration-base) var(--dds-motion-easing-standard);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.dds-switch__input:checked {
|
|
536
|
+
background: var(--dds-color-bg-brand);
|
|
537
|
+
}
|
|
538
|
+
.dds-switch__input:checked::before {
|
|
539
|
+
background: var(--dds-color-text-on-brand);
|
|
540
|
+
transform: translateX(16px);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
[dir="rtl"] .dds-switch__input:checked::before {
|
|
544
|
+
transform: translateX(-16px);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.dds-switch__input:focus-visible {
|
|
548
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
549
|
+
outline-offset: var(--dds-space-2);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* opacity is allowed for disabled only (spec §4.2) — on the label, once */
|
|
553
|
+
.dds-switch:has(.dds-switch__input[disabled]) {
|
|
554
|
+
opacity: 0.45;
|
|
555
|
+
cursor: not-allowed;
|
|
556
|
+
}
|
|
557
|
+
.dds-switch__input[disabled] {
|
|
558
|
+
cursor: not-allowed;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
@media (prefers-reduced-motion: reduce) {
|
|
562
|
+
.dds-switch__input,
|
|
563
|
+
.dds-switch__input::before {
|
|
564
|
+
transition: none;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* Badge — brand + the four status pairs. Status colors always ship as
|
|
569
|
+
* foreground + -bg pair (spec §3.1); the leading dot repeats the state so
|
|
570
|
+
* color is not the only signal (spec §6). Dot size 6px is glyph geometry,
|
|
571
|
+
* not spacing (same 6px as the toast dot — keep them identical). */
|
|
572
|
+
|
|
573
|
+
.dds-badge {
|
|
574
|
+
display: inline-flex;
|
|
575
|
+
align-items: center;
|
|
576
|
+
gap: var(--dds-space-4);
|
|
577
|
+
font-family: var(--dds-font-family-sans);
|
|
578
|
+
font-size: var(--dds-typo-label-font-size);
|
|
579
|
+
line-height: var(--dds-typo-label-line-height);
|
|
580
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
581
|
+
padding: var(--dds-space-2) var(--dds-space-8);
|
|
582
|
+
border-radius: var(--dds-radius-full);
|
|
583
|
+
white-space: nowrap;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.dds-badge::before {
|
|
587
|
+
content: "";
|
|
588
|
+
flex: none;
|
|
589
|
+
width: 6px;
|
|
590
|
+
height: 6px;
|
|
591
|
+
border-radius: var(--dds-radius-full);
|
|
592
|
+
background: currentColor;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.dds-badge--brand {
|
|
596
|
+
color: var(--dds-color-text-brand);
|
|
597
|
+
background: var(--dds-color-bg-brand-subtle);
|
|
598
|
+
}
|
|
599
|
+
.dds-badge--success {
|
|
600
|
+
color: var(--dds-color-status-success);
|
|
601
|
+
background: var(--dds-color-status-success-bg);
|
|
602
|
+
}
|
|
603
|
+
.dds-badge--warning {
|
|
604
|
+
color: var(--dds-color-status-warning);
|
|
605
|
+
background: var(--dds-color-status-warning-bg);
|
|
606
|
+
}
|
|
607
|
+
.dds-badge--danger {
|
|
608
|
+
color: var(--dds-color-status-danger);
|
|
609
|
+
background: var(--dds-color-status-danger-bg);
|
|
610
|
+
}
|
|
611
|
+
.dds-badge--info {
|
|
612
|
+
color: var(--dds-color-status-info);
|
|
613
|
+
background: var(--dds-color-status-info-bg);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Chip — a small interactive control: filter toggle, suggested question,
|
|
617
|
+
* removable tag. Badge and Chip are different components on purpose: a badge
|
|
618
|
+
* is state you read, a chip is something you press. Never restyle one into
|
|
619
|
+
* the other (the duplicated-.chip collision this house has already paid for).
|
|
620
|
+
*
|
|
621
|
+
* Selected state is carried by aria-pressed="true" (toggle chips) so the
|
|
622
|
+
* state is announced, not only colored; --selected exists for markup that
|
|
623
|
+
* can't use aria-pressed (e.g. a link chip). */
|
|
624
|
+
|
|
625
|
+
.dds-chip {
|
|
626
|
+
appearance: none;
|
|
627
|
+
display: inline-flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
gap: var(--dds-space-4);
|
|
630
|
+
font-family: var(--dds-font-family-sans);
|
|
631
|
+
font-size: var(--dds-typo-label-font-size);
|
|
632
|
+
line-height: var(--dds-typo-label-line-height);
|
|
633
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
634
|
+
padding: var(--dds-space-4) var(--dds-space-12);
|
|
635
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
636
|
+
border-radius: var(--dds-radius-full);
|
|
637
|
+
background: none;
|
|
638
|
+
color: var(--dds-color-text-primary);
|
|
639
|
+
cursor: pointer;
|
|
640
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
641
|
+
-webkit-tap-highlight-color: transparent;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.dds-chip:not([disabled]):hover {
|
|
645
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
646
|
+
}
|
|
647
|
+
.dds-chip:not([disabled]):active {
|
|
648
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
649
|
+
}
|
|
650
|
+
.dds-chip:focus-visible {
|
|
651
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
652
|
+
outline-offset: var(--dds-space-2);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.dds-chip[aria-pressed="true"],
|
|
656
|
+
.dds-chip--selected {
|
|
657
|
+
background: var(--dds-color-bg-brand-subtle);
|
|
658
|
+
border-color: var(--dds-color-bg-brand);
|
|
659
|
+
color: var(--dds-color-text-brand);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
663
|
+
.dds-chip[disabled] {
|
|
664
|
+
opacity: 0.45;
|
|
665
|
+
cursor: not-allowed;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* Avatar — a person or tenant mark: image if there is one, initials if not.
|
|
669
|
+
* Sizes come from the spacing scale (32/40/48) so an avatar lines up with the
|
|
670
|
+
* control of the same size in a list row. The initials fallback uses the
|
|
671
|
+
* brand-subtle pair, which re-tints with the product brand automatically. */
|
|
672
|
+
|
|
673
|
+
.dds-avatar {
|
|
674
|
+
display: inline-flex;
|
|
675
|
+
align-items: center;
|
|
676
|
+
justify-content: center;
|
|
677
|
+
flex: none;
|
|
678
|
+
width: var(--dds-space-40);
|
|
679
|
+
height: var(--dds-space-40);
|
|
680
|
+
border-radius: var(--dds-radius-full);
|
|
681
|
+
overflow: hidden;
|
|
682
|
+
background: var(--dds-color-bg-brand-subtle);
|
|
683
|
+
color: var(--dds-color-text-brand);
|
|
684
|
+
font-family: var(--dds-font-family-sans);
|
|
685
|
+
font-size: var(--dds-typo-label-font-size);
|
|
686
|
+
line-height: var(--dds-typo-label-line-height);
|
|
687
|
+
font-weight: 600;
|
|
688
|
+
user-select: none;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.dds-avatar__img {
|
|
692
|
+
width: 100%;
|
|
693
|
+
height: 100%;
|
|
694
|
+
object-fit: cover;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.dds-avatar--sm {
|
|
698
|
+
width: var(--dds-space-32);
|
|
699
|
+
height: var(--dds-space-32);
|
|
700
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
701
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.dds-avatar--lg {
|
|
705
|
+
width: var(--dds-space-48);
|
|
706
|
+
height: var(--dds-space-48);
|
|
707
|
+
font-size: var(--dds-typo-body-1-font-size);
|
|
708
|
+
line-height: var(--dds-typo-body-1-line-height);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* square variant for tenants/logos — a company mark in a circle gets clipped */
|
|
712
|
+
.dds-avatar--square {
|
|
713
|
+
border-radius: var(--dds-radius-md);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/* Spinner — indeterminate progress. Inherits color via currentColor so it
|
|
717
|
+
* reads correctly on any surface (inside a primary button it spins in
|
|
718
|
+
* on-brand). The rotation is a continuous state indicator, not a transition,
|
|
719
|
+
* so its period is not a motion token; under reduced motion it slows down
|
|
720
|
+
* instead of freezing (a stopped spinner reads as a hang — the motion IS the
|
|
721
|
+
* state, spec §3.6). Standalone spinners need role="status" + aria-label. */
|
|
722
|
+
|
|
723
|
+
.dds-spinner {
|
|
724
|
+
display: inline-block;
|
|
725
|
+
flex: none;
|
|
726
|
+
width: var(--dds-space-16);
|
|
727
|
+
height: var(--dds-space-16);
|
|
728
|
+
border: 2px solid currentColor;
|
|
729
|
+
border-top-color: transparent;
|
|
730
|
+
border-radius: var(--dds-radius-full);
|
|
731
|
+
animation: dds-spin 800ms linear infinite;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.dds-spinner--lg {
|
|
735
|
+
width: var(--dds-space-24);
|
|
736
|
+
height: var(--dds-space-24);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@keyframes dds-spin {
|
|
740
|
+
to {
|
|
741
|
+
transform: rotate(360deg);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
@media (prefers-reduced-motion: reduce) {
|
|
746
|
+
.dds-spinner {
|
|
747
|
+
animation-duration: 1600ms;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* Skeleton — loading placeholder. The fill is an alpha tint (spec §3.1:
|
|
752
|
+
* overlays/tints are alpha-only) so it sits naturally on default and subtle
|
|
753
|
+
* backgrounds in both themes. Size is the consumer's job (width/height on
|
|
754
|
+
* the element); --text matches a line of the surrounding font. Mark the
|
|
755
|
+
* loading region with aria-busy="true" and keep skeletons aria-hidden. */
|
|
756
|
+
|
|
757
|
+
.dds-skeleton {
|
|
758
|
+
display: block;
|
|
759
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
760
|
+
border-radius: var(--dds-radius-sm);
|
|
761
|
+
animation: dds-skeleton-pulse 1600ms var(--dds-motion-easing-standard) infinite;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.dds-skeleton--text {
|
|
765
|
+
height: 1em;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.dds-skeleton--circle {
|
|
769
|
+
border-radius: var(--dds-radius-full);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@keyframes dds-skeleton-pulse {
|
|
773
|
+
50% {
|
|
774
|
+
opacity: 0.4;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
@media (prefers-reduced-motion: reduce) {
|
|
779
|
+
.dds-skeleton {
|
|
780
|
+
animation: none;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* Divider — a rule between sections. Use <hr class="dds-divider"> when the
|
|
785
|
+
* split is meaningful; when it is purely decorative next to text that already
|
|
786
|
+
* separates the groups, add role="presentation".
|
|
787
|
+
* The vertical variant needs a stretchable parent (flex row / grid cell). */
|
|
788
|
+
|
|
789
|
+
.dds-divider {
|
|
790
|
+
border: none;
|
|
791
|
+
border-top: 1px solid var(--dds-color-border-default);
|
|
792
|
+
margin: var(--dds-space-16) 0;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.dds-divider--vertical {
|
|
796
|
+
width: 0;
|
|
797
|
+
align-self: stretch;
|
|
798
|
+
border-top: none;
|
|
799
|
+
border-inline-start: 1px solid var(--dds-color-border-default);
|
|
800
|
+
margin: 0 var(--dds-space-16);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* Card — a grouped surface. Border + elevated background carry the grouping;
|
|
804
|
+
* elevation.1 is the lift. The same shadow token ships in both themes (as it
|
|
805
|
+
* does for Dialog and Toast): on a zinc.950 page it resolves to almost
|
|
806
|
+
* nothing, which is the intended dark look — a component may not branch on
|
|
807
|
+
* theme (spec §3.1: dark is a mapping swap, never an `if (dark)`).
|
|
808
|
+
*
|
|
809
|
+
* --subtle is the flat variant for cards inside an already elevated surface
|
|
810
|
+
* (a card in a dialog), where a second shadow reads as noise. */
|
|
811
|
+
|
|
812
|
+
.dds-card {
|
|
813
|
+
background: var(--dds-color-bg-elevated);
|
|
814
|
+
color: var(--dds-color-text-primary);
|
|
815
|
+
font-family: var(--dds-font-family-sans);
|
|
816
|
+
border: 1px solid var(--dds-color-border-default);
|
|
817
|
+
border-radius: var(--dds-radius-lg);
|
|
818
|
+
box-shadow: var(--dds-elevation-1);
|
|
819
|
+
padding: var(--dds-space-16);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.dds-card--subtle {
|
|
823
|
+
background: var(--dds-color-bg-subtle);
|
|
824
|
+
box-shadow: none;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.dds-card__title {
|
|
828
|
+
margin: 0 0 var(--dds-space-4);
|
|
829
|
+
font-size: var(--dds-typo-title-3-font-size);
|
|
830
|
+
line-height: var(--dds-typo-title-3-line-height);
|
|
831
|
+
font-weight: var(--dds-typo-title-3-font-weight);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.dds-card__body {
|
|
835
|
+
margin: 0;
|
|
836
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
837
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
838
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
839
|
+
color: var(--dds-color-text-secondary);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* ListRow — one item in a list: leading slot, body (title + sub), trailing
|
|
843
|
+
* slot. House rule this encodes: rows are self-contained and rich — if the
|
|
844
|
+
* information fits in the row, it does not get deferred to a detail page.
|
|
845
|
+
*
|
|
846
|
+
* --interactive is for rows that are themselves a button/link (use a real
|
|
847
|
+
* <button> or <a> so keyboard and AT get it for free). The focus ring is
|
|
848
|
+
* inset because a full-bleed row has no outer margin to draw into. */
|
|
849
|
+
|
|
850
|
+
.dds-listrow {
|
|
851
|
+
display: flex;
|
|
852
|
+
align-items: center;
|
|
853
|
+
gap: var(--dds-space-12);
|
|
854
|
+
width: 100%;
|
|
855
|
+
padding: var(--dds-space-12) var(--dds-space-16);
|
|
856
|
+
background: none;
|
|
857
|
+
border: none;
|
|
858
|
+
text-align: start;
|
|
859
|
+
font-family: var(--dds-font-family-sans);
|
|
860
|
+
color: var(--dds-color-text-primary);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.dds-listrow + .dds-listrow {
|
|
864
|
+
border-top: 1px solid var(--dds-color-border-default);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.dds-listrow__body {
|
|
868
|
+
flex: 1;
|
|
869
|
+
min-width: 0;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* display:block so the two lines stack whether the markup uses <p> or <span>
|
|
873
|
+
* (a <span> title and sub otherwise render on one line) */
|
|
874
|
+
.dds-listrow__title {
|
|
875
|
+
display: block;
|
|
876
|
+
margin: 0;
|
|
877
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
878
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
879
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
880
|
+
/* long titles truncate rather than reflowing the row height */
|
|
881
|
+
overflow: hidden;
|
|
882
|
+
text-overflow: ellipsis;
|
|
883
|
+
white-space: nowrap;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.dds-listrow__sub {
|
|
887
|
+
display: block;
|
|
888
|
+
margin: 0;
|
|
889
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
890
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
891
|
+
color: var(--dds-color-text-secondary);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.dds-listrow__actions {
|
|
895
|
+
display: flex;
|
|
896
|
+
align-items: center;
|
|
897
|
+
gap: var(--dds-space-4);
|
|
898
|
+
flex: none;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/* interactive ------------------------------------------------------------- */
|
|
902
|
+
|
|
903
|
+
.dds-listrow--interactive {
|
|
904
|
+
cursor: pointer;
|
|
905
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
906
|
+
}
|
|
907
|
+
.dds-listrow--interactive:not([disabled]):hover {
|
|
908
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
909
|
+
}
|
|
910
|
+
.dds-listrow--interactive:not([disabled]):active {
|
|
911
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
912
|
+
}
|
|
913
|
+
.dds-listrow--interactive:focus-visible {
|
|
914
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
915
|
+
outline-offset: calc(var(--dds-space-2) * -1);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
919
|
+
.dds-listrow[disabled] {
|
|
920
|
+
opacity: 0.45;
|
|
921
|
+
cursor: not-allowed;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* Tabs — ink-underline tabs (the shell the admin surfaces converged on).
|
|
925
|
+
* Markup is the ARIA tabs pattern: .dds-tabs[role="tablist"] >
|
|
926
|
+
* .dds-tab[role="tab"][aria-selected] each controlling a [role="tabpanel"].
|
|
927
|
+
* Selection is aria-selected, never a class alone — the state has to be
|
|
928
|
+
* announced, and color is not the only signal (the underline carries it too).
|
|
929
|
+
*
|
|
930
|
+
* The button border-radius reset is deliberate: a shared radius reset leaking
|
|
931
|
+
* into the underline bows its ends — this house has shipped that bug once. */
|
|
932
|
+
|
|
933
|
+
.dds-tabs {
|
|
934
|
+
display: flex;
|
|
935
|
+
gap: var(--dds-space-4);
|
|
936
|
+
border-bottom: 1px solid var(--dds-color-border-default);
|
|
937
|
+
font-family: var(--dds-font-family-sans);
|
|
938
|
+
overflow-x: auto;
|
|
939
|
+
scrollbar-width: none;
|
|
940
|
+
}
|
|
941
|
+
.dds-tabs::-webkit-scrollbar {
|
|
942
|
+
display: none;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.dds-tab {
|
|
946
|
+
appearance: none;
|
|
947
|
+
flex: none;
|
|
948
|
+
padding: var(--dds-space-12);
|
|
949
|
+
background: none;
|
|
950
|
+
border: none;
|
|
951
|
+
border-radius: 0;
|
|
952
|
+
border-bottom: var(--dds-space-2) solid transparent;
|
|
953
|
+
/* the underline sits on the tablist's own 1px rule */
|
|
954
|
+
margin-bottom: -1px;
|
|
955
|
+
font-family: inherit;
|
|
956
|
+
font-size: var(--dds-typo-label-font-size);
|
|
957
|
+
line-height: var(--dds-typo-label-line-height);
|
|
958
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
959
|
+
color: var(--dds-color-text-secondary);
|
|
960
|
+
white-space: nowrap;
|
|
961
|
+
cursor: pointer;
|
|
962
|
+
transition: color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.dds-tab:not([disabled]):hover {
|
|
966
|
+
color: var(--dds-color-text-primary);
|
|
967
|
+
}
|
|
968
|
+
.dds-tab:focus-visible {
|
|
969
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
970
|
+
outline-offset: calc(var(--dds-space-2) * -1);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.dds-tab[aria-selected="true"] {
|
|
974
|
+
color: var(--dds-color-text-brand);
|
|
975
|
+
border-bottom-color: var(--dds-color-bg-brand);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
979
|
+
.dds-tab[disabled] {
|
|
980
|
+
opacity: 0.45;
|
|
981
|
+
cursor: not-allowed;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/* Dialog — overlay + panel. Two markup shapes are supported:
|
|
985
|
+
* 1. div pattern: .dds-dialog-overlay > .dds-dialog[role="dialog"][aria-modal="true"]
|
|
986
|
+
* 2. native <dialog class="dds-dialog"> (backdrop styled via ::backdrop)
|
|
987
|
+
* Behavior contract (spec §5): closes on outside tap and Esc; focus is
|
|
988
|
+
* trapped while open and returns to the opener on close — that is the
|
|
989
|
+
* opener JS's job, this file only owns the look. Label the panel with
|
|
990
|
+
* aria-labelledby pointing at the title. */
|
|
991
|
+
|
|
992
|
+
.dds-dialog-overlay {
|
|
993
|
+
position: fixed;
|
|
994
|
+
inset: 0;
|
|
995
|
+
z-index: 1000;
|
|
996
|
+
display: grid;
|
|
997
|
+
place-items: center;
|
|
998
|
+
padding: var(--dds-space-16);
|
|
999
|
+
background: var(--dds-color-overlay-dim);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.dds-dialog {
|
|
1003
|
+
background: var(--dds-color-bg-elevated);
|
|
1004
|
+
color: var(--dds-color-text-primary);
|
|
1005
|
+
font-family: var(--dds-font-family-sans);
|
|
1006
|
+
border-radius: var(--dds-radius-lg);
|
|
1007
|
+
box-shadow: var(--dds-elevation-3);
|
|
1008
|
+
padding: var(--dds-space-20);
|
|
1009
|
+
width: 100%;
|
|
1010
|
+
max-width: 400px; /* layout default, not a token — override per product */
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/* native <dialog> resets */
|
|
1014
|
+
dialog.dds-dialog {
|
|
1015
|
+
border: none;
|
|
1016
|
+
margin: auto;
|
|
1017
|
+
}
|
|
1018
|
+
dialog.dds-dialog::backdrop {
|
|
1019
|
+
background: var(--dds-color-overlay-dim);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.dds-dialog__title {
|
|
1023
|
+
margin: 0 0 var(--dds-space-8);
|
|
1024
|
+
font-size: var(--dds-typo-title-2-font-size);
|
|
1025
|
+
line-height: var(--dds-typo-title-2-line-height);
|
|
1026
|
+
font-weight: var(--dds-typo-title-2-font-weight);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.dds-dialog__body {
|
|
1030
|
+
margin: 0 0 var(--dds-space-16);
|
|
1031
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
1032
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
1033
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
1034
|
+
color: var(--dds-color-text-secondary);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
.dds-dialog__actions {
|
|
1038
|
+
display: flex;
|
|
1039
|
+
justify-content: flex-end;
|
|
1040
|
+
gap: var(--dds-space-8);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/* Toast — transient notice. Region markup:
|
|
1044
|
+
* .dds-toast-region[role="status"] (polite) — use role="alert" per-toast
|
|
1045
|
+
* only for danger. Toasts stack bottom-end; entry animates with
|
|
1046
|
+
* duration.base + easing.enter and respects reduced motion. The status
|
|
1047
|
+
* dot repeats the tone so color is not the only signal (spec §6) — same
|
|
1048
|
+
* 6px dot as the badge. */
|
|
1049
|
+
|
|
1050
|
+
.dds-toast-region {
|
|
1051
|
+
position: fixed;
|
|
1052
|
+
z-index: 1100;
|
|
1053
|
+
inset-block-end: var(--dds-space-16);
|
|
1054
|
+
inset-inline-end: var(--dds-space-16);
|
|
1055
|
+
display: grid;
|
|
1056
|
+
gap: var(--dds-space-8);
|
|
1057
|
+
justify-items: end;
|
|
1058
|
+
pointer-events: none;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.dds-toast {
|
|
1062
|
+
pointer-events: auto;
|
|
1063
|
+
display: flex;
|
|
1064
|
+
align-items: center;
|
|
1065
|
+
gap: var(--dds-space-8);
|
|
1066
|
+
background: var(--dds-color-bg-elevated);
|
|
1067
|
+
color: var(--dds-color-text-primary);
|
|
1068
|
+
font-family: var(--dds-font-family-sans);
|
|
1069
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
1070
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
1071
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
1072
|
+
border: 1px solid var(--dds-color-border-default);
|
|
1073
|
+
border-radius: var(--dds-radius-md);
|
|
1074
|
+
box-shadow: var(--dds-elevation-2);
|
|
1075
|
+
padding: var(--dds-space-12) var(--dds-space-16);
|
|
1076
|
+
max-width: min(360px, calc(100vw - var(--dds-space-32)));
|
|
1077
|
+
animation: dds-toast-in var(--dds-motion-duration-base) var(--dds-motion-easing-enter);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.dds-toast--success::before,
|
|
1081
|
+
.dds-toast--warning::before,
|
|
1082
|
+
.dds-toast--danger::before,
|
|
1083
|
+
.dds-toast--info::before {
|
|
1084
|
+
content: "";
|
|
1085
|
+
flex: none;
|
|
1086
|
+
width: 6px;
|
|
1087
|
+
height: 6px;
|
|
1088
|
+
border-radius: var(--dds-radius-full);
|
|
1089
|
+
}
|
|
1090
|
+
.dds-toast--success::before {
|
|
1091
|
+
background: var(--dds-color-status-success);
|
|
1092
|
+
}
|
|
1093
|
+
.dds-toast--warning::before {
|
|
1094
|
+
background: var(--dds-color-status-warning);
|
|
1095
|
+
}
|
|
1096
|
+
.dds-toast--danger::before {
|
|
1097
|
+
background: var(--dds-color-status-danger);
|
|
1098
|
+
}
|
|
1099
|
+
.dds-toast--info::before {
|
|
1100
|
+
background: var(--dds-color-status-info);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
@keyframes dds-toast-in {
|
|
1104
|
+
from {
|
|
1105
|
+
opacity: 0;
|
|
1106
|
+
transform: translateY(var(--dds-space-8));
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1111
|
+
.dds-toast {
|
|
1112
|
+
animation: none;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/* Tooltip (web only — spec §4.3; on native the same information goes in a
|
|
1117
|
+
* label or a sheet). A supplementary hint on an inverse surface, shown on
|
|
1118
|
+
* hover AND focus-within so it is reachable from the keyboard.
|
|
1119
|
+
*
|
|
1120
|
+
* Contract: the tooltip may never carry information that is required to use
|
|
1121
|
+
* the control — an icon button still needs its own aria-label, and the bubble
|
|
1122
|
+
* is referenced with aria-describedby, not aria-label (a describedby bubble
|
|
1123
|
+
* that never opens still reaches AT; a hover-only one does not). */
|
|
1124
|
+
|
|
1125
|
+
.dds-tooltip {
|
|
1126
|
+
position: relative;
|
|
1127
|
+
display: inline-flex;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.dds-tooltip__bubble {
|
|
1131
|
+
position: absolute;
|
|
1132
|
+
inset-block-end: calc(100% + var(--dds-space-8));
|
|
1133
|
+
inset-inline-start: 50%;
|
|
1134
|
+
transform: translateX(-50%);
|
|
1135
|
+
z-index: 1200; /* above dialog (1000) and toast (1100) */
|
|
1136
|
+
padding: var(--dds-space-4) var(--dds-space-8);
|
|
1137
|
+
border-radius: var(--dds-radius-sm);
|
|
1138
|
+
background: var(--dds-color-bg-inverse);
|
|
1139
|
+
color: var(--dds-color-text-on-inverse);
|
|
1140
|
+
font-family: var(--dds-font-family-sans);
|
|
1141
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
1142
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
1143
|
+
white-space: nowrap;
|
|
1144
|
+
pointer-events: none;
|
|
1145
|
+
opacity: 0;
|
|
1146
|
+
transition: opacity var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.dds-tooltip:hover .dds-tooltip__bubble,
|
|
1150
|
+
.dds-tooltip:focus-within .dds-tooltip__bubble {
|
|
1151
|
+
opacity: 1;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1155
|
+
.dds-tooltip__bubble {
|
|
1156
|
+
transition: none;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/* EmptyState — what a list shows when it has nothing yet. House rule: the
|
|
1161
|
+
* empty view keeps the same skeleton as the filled one (summary chips and
|
|
1162
|
+
* filters stay put) — the empty state replaces the rows, not the screen.
|
|
1163
|
+
*
|
|
1164
|
+
* Layout note for consumers: give the element `display: none`'s job to
|
|
1165
|
+
* [hidden] and never override it with a display rule that lacks a
|
|
1166
|
+
* `[hidden]` guard — a shared .empty that did exactly that once painted
|
|
1167
|
+
* "no results" on top of a populated table. */
|
|
1168
|
+
|
|
1169
|
+
.dds-empty {
|
|
1170
|
+
display: flex;
|
|
1171
|
+
flex-direction: column;
|
|
1172
|
+
align-items: center;
|
|
1173
|
+
justify-content: center;
|
|
1174
|
+
gap: var(--dds-space-8);
|
|
1175
|
+
padding: var(--dds-space-40) var(--dds-space-24);
|
|
1176
|
+
border: 1px dashed var(--dds-color-border-strong);
|
|
1177
|
+
border-radius: var(--dds-radius-lg);
|
|
1178
|
+
text-align: center;
|
|
1179
|
+
font-family: var(--dds-font-family-sans);
|
|
1180
|
+
color: var(--dds-color-text-secondary);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.dds-empty[hidden] {
|
|
1184
|
+
display: none;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.dds-empty__title {
|
|
1188
|
+
margin: 0;
|
|
1189
|
+
font-size: var(--dds-typo-title-3-font-size);
|
|
1190
|
+
line-height: var(--dds-typo-title-3-line-height);
|
|
1191
|
+
font-weight: var(--dds-typo-title-3-font-weight);
|
|
1192
|
+
color: var(--dds-color-text-primary);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.dds-empty__desc {
|
|
1196
|
+
margin: 0;
|
|
1197
|
+
max-width: 36em;
|
|
1198
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
1199
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.dds-empty__actions {
|
|
1203
|
+
display: flex;
|
|
1204
|
+
gap: var(--dds-space-8);
|
|
1205
|
+
margin-top: var(--dds-space-8);
|
|
1206
|
+
}
|