@devslab/dds-css 0.10.0 → 0.11.1

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.
@@ -29,3 +29,18 @@
29
29
  outline-color: Highlight;
30
30
  }
31
31
  }
32
+
33
+ /* Visually hidden — content that stays in the accessibility tree (screen
34
+ * readers, AT) but takes up no visual space. Unlike display: none, which
35
+ * removes the element from the tree too, this only clips it out of view.
36
+ * Consumers: DataTable captions, icon-only header labels, skip links.
37
+ * The ancestor needs position: relative for this to clip against the right
38
+ * box (see table.css's .dds-table-wrap note) rather than the page. */
39
+ .dds-visually-hidden {
40
+ position: absolute;
41
+ inline-size: 1px;
42
+ block-size: 1px;
43
+ overflow: hidden;
44
+ clip-path: inset(50%);
45
+ white-space: nowrap;
46
+ }
@@ -0,0 +1,360 @@
1
+ /* Console shell — the 232px rail + below-900px drawer that a console route
2
+ * renders around its content. The fold breakpoint below is written in the
3
+ * same rem form as table.css's column fold (56.25rem = 900px at a 16px root
4
+ * font size), so a visitor with any browser default font size sees the rail
5
+ * change shape and the table's columns fold at the same viewport width
6
+ * inside the same console.
7
+ *
8
+ * Tokens only. Every class that declares `display` re-declares
9
+ * `[hidden] { display: none }` beside it, at a class+attribute specificity
10
+ * that beats every later `display` declaration for that class — including
11
+ * the ones inside the media query below — so a hidden rail, toggle, or
12
+ * scrim can never be drawn on screen regardless of viewport width. */
13
+
14
+ .dds-console-shell {
15
+ min-block-size: 100vh;
16
+ display: grid;
17
+ grid-template-columns: 14.5rem minmax(0, 1fr);
18
+ background: var(--dds-color-bg-default);
19
+ color: var(--dds-color-text-primary);
20
+ font-size: 1rem;
21
+ line-height: 1.5;
22
+ }
23
+ .dds-console-shell[hidden] {
24
+ display: none;
25
+ }
26
+
27
+ /* The skip link is hidden by .dds-visually-hidden (applied by the
28
+ * component) and only needs to say what it looks like once focused —
29
+ * a visually-hidden element is never in the focus-visible/`:focus` chain
30
+ * while `hidden`, so no `[hidden]` guard applies here. */
31
+ .dds-console-skip:focus {
32
+ position: fixed;
33
+ inset-block-start: var(--dds-space-8);
34
+ inset-inline-start: var(--dds-space-8);
35
+ z-index: 100;
36
+ clip-path: none;
37
+ inline-size: auto;
38
+ block-size: auto;
39
+ overflow: visible;
40
+ white-space: normal;
41
+ padding: var(--dds-space-12) var(--dds-space-16);
42
+ background: var(--dds-color-bg-elevated);
43
+ color: var(--dds-color-text-primary);
44
+ border: 2px solid var(--dds-color-border-focus);
45
+ }
46
+
47
+ .dds-console-rail {
48
+ position: sticky;
49
+ inset-block-start: 0;
50
+ block-size: 100vh;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: var(--dds-space-16);
54
+ padding: var(--dds-space-20) var(--dds-space-12);
55
+ overflow-y: auto;
56
+ border-inline-end: 1px solid var(--dds-color-border-default);
57
+ background: var(--dds-color-bg-elevated);
58
+ }
59
+ .dds-console-rail[hidden] {
60
+ display: none;
61
+ }
62
+
63
+ .dds-console-rail__brand {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: var(--dds-space-8);
67
+ padding-inline: var(--dds-space-8);
68
+ color: inherit;
69
+ font-size: .9375rem;
70
+ font-weight: 700;
71
+ letter-spacing: -.02em;
72
+ text-decoration: none;
73
+ }
74
+ .dds-console-rail__brand[hidden] {
75
+ display: none;
76
+ }
77
+ .dds-console-rail__brand img {
78
+ inline-size: 20px;
79
+ block-size: 20px;
80
+ }
81
+
82
+ .dds-console-rail__context {
83
+ display: grid;
84
+ gap: var(--dds-space-4);
85
+ margin: 0;
86
+ padding-inline: var(--dds-space-8);
87
+ font-size: var(--dds-typo-caption-font-size);
88
+ line-height: var(--dds-typo-caption-line-height);
89
+ }
90
+ .dds-console-rail__context[hidden] {
91
+ display: none;
92
+ }
93
+ .dds-console-rail__context dt {
94
+ color: var(--dds-color-text-muted);
95
+ }
96
+ .dds-console-rail__context dd {
97
+ margin: 0;
98
+ font-family: var(--dds-font-family-mono);
99
+ overflow-wrap: anywhere;
100
+ }
101
+
102
+ .dds-console-rail__nav {
103
+ display: grid;
104
+ gap: var(--dds-space-16);
105
+ }
106
+ .dds-console-rail__nav[hidden] {
107
+ display: none;
108
+ }
109
+
110
+ .dds-console-rail__group {
111
+ display: grid;
112
+ gap: var(--dds-space-2, 2px);
113
+ }
114
+ .dds-console-rail__group[hidden] {
115
+ display: none;
116
+ }
117
+ .dds-console-rail__group ul {
118
+ margin: 0;
119
+ padding: 0;
120
+ list-style: none;
121
+ display: grid;
122
+ gap: 2px;
123
+ }
124
+ .dds-console-rail__group ul[hidden] {
125
+ display: none;
126
+ }
127
+ .dds-console-rail__group-label {
128
+ margin: 0;
129
+ padding: 0 var(--dds-space-8) var(--dds-space-4);
130
+ color: var(--dds-color-text-muted);
131
+ font-size: .6875rem;
132
+ font-weight: 600;
133
+ letter-spacing: .05em;
134
+ text-transform: uppercase;
135
+ }
136
+
137
+ .dds-console-rail__item {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ gap: var(--dds-space-8);
142
+ min-block-size: var(--dds-space-40);
143
+ padding-inline: var(--dds-space-8);
144
+ border-radius: var(--dds-radius-md);
145
+ color: var(--dds-color-text-secondary);
146
+ font-size: .875rem;
147
+ font-weight: 500;
148
+ text-decoration: none;
149
+ }
150
+ .dds-console-rail__item[hidden] {
151
+ display: none;
152
+ }
153
+ .dds-console-rail__item:hover {
154
+ background: var(--dds-color-bg-subtle);
155
+ color: var(--dds-color-text-primary);
156
+ }
157
+ .dds-console-rail__item[aria-current="page"] {
158
+ background: var(--dds-color-bg-brand-subtle);
159
+ color: var(--dds-color-text-brand);
160
+ font-weight: 650;
161
+ }
162
+ .dds-console-rail__item:focus-visible {
163
+ outline: 2px solid var(--dds-color-border-focus);
164
+ outline-offset: 2px;
165
+ }
166
+
167
+ .dds-console-rail__badge {
168
+ display: inline-grid;
169
+ place-items: center;
170
+ min-inline-size: 1.125rem;
171
+ block-size: 1.125rem;
172
+ padding-inline: .3125rem;
173
+ border-radius: var(--dds-radius-full);
174
+ background: var(--dds-color-bg-brand);
175
+ color: var(--dds-color-text-on-brand);
176
+ font-size: .6875rem;
177
+ font-weight: 700;
178
+ font-variant-numeric: tabular-nums;
179
+ }
180
+ .dds-console-rail__badge[hidden] {
181
+ display: none;
182
+ }
183
+
184
+ .dds-console-rail__foot {
185
+ margin-block-start: auto;
186
+ display: grid;
187
+ gap: var(--dds-space-8);
188
+ justify-items: start;
189
+ padding: var(--dds-space-12) var(--dds-space-8) 0;
190
+ border-block-start: 1px solid var(--dds-color-border-default);
191
+ }
192
+ .dds-console-rail__foot[hidden] {
193
+ display: none;
194
+ }
195
+
196
+ .dds-console-rail__toggle {
197
+ display: none;
198
+ }
199
+ .dds-console-rail__toggle[hidden] {
200
+ display: none;
201
+ }
202
+
203
+ .dds-console-rail__scrim {
204
+ display: none;
205
+ }
206
+ .dds-console-rail__scrim[hidden] {
207
+ display: none;
208
+ }
209
+
210
+ .dds-console-main {
211
+ min-inline-size: 0;
212
+ padding: clamp(1rem, 3vw, 2rem);
213
+ }
214
+
215
+ .dds-console-page-header {
216
+ display: flex;
217
+ align-items: flex-start;
218
+ justify-content: space-between;
219
+ gap: var(--dds-space-32);
220
+ margin-block-end: var(--dds-space-32);
221
+ }
222
+ .dds-console-page-header[hidden] {
223
+ display: none;
224
+ }
225
+ .dds-console-page-header h1 {
226
+ max-inline-size: 24ch;
227
+ margin: .25rem 0 0;
228
+ font-size: clamp(1.75rem, 2.2vw, 2.25rem);
229
+ line-height: 1.1;
230
+ letter-spacing: -.03em;
231
+ text-wrap: balance;
232
+ }
233
+ .dds-console-page-header__actions {
234
+ display: flex;
235
+ gap: var(--dds-space-8);
236
+ }
237
+ .dds-console-page-header__actions[hidden] {
238
+ display: none;
239
+ }
240
+ .dds-console-page-eyebrow {
241
+ margin: 0;
242
+ color: var(--dds-color-text-muted);
243
+ font-size: var(--dds-typo-caption-font-size);
244
+ line-height: var(--dds-typo-caption-line-height);
245
+ }
246
+
247
+ /* Console density: every dds-css control inside the shell reads at the body-2
248
+ * size (14px), not dds-btn's own default (13px, --dds-typo-label-font-size).
249
+ * Scoped to .dds-console-shell rather than left as a global override on
250
+ * .dds-btn/.dds-input/.dds-select/.dds-textarea themselves — a shell that
251
+ * establishes a density owns the override for controls inside it, the same
252
+ * way a shared class must never carry a layout rule that only makes sense
253
+ * for one consumer's container.
254
+ *
255
+ * .dds-btn's :not() exclusions matter: this selector's specificity (0,2,0)
256
+ * beats every size modifier in button.css (.dds-btn--lg, .dds-btn--sm, both
257
+ * 0,1,0), so an unqualified `.dds-console-shell .dds-btn` would override
258
+ * --lg's own font-size back down to body-2 and would give --sm the same
259
+ * font-size as a default button (--sm declares none of its own, so it was
260
+ * inheriting whichever rule won for plain .dds-btn) — the one outcome a
261
+ * size modifier exists to prevent. .dds-input/.dds-select/.dds-textarea
262
+ * carry no such modifiers today, so no matching exclusion is needed there. */
263
+ .dds-console-shell .dds-btn:not(.dds-btn--lg):not(.dds-btn--sm),
264
+ .dds-console-shell .dds-input,
265
+ .dds-console-shell .dds-select,
266
+ .dds-console-shell .dds-textarea {
267
+ font-size: var(--dds-typo-body-2-font-size);
268
+ }
269
+
270
+ @media (max-width: 56.25rem) {
271
+ .dds-console-shell {
272
+ grid-template-columns: minmax(0, 1fr);
273
+ }
274
+ .dds-console-rail__toggle {
275
+ position: fixed;
276
+ inset-block-start: .75rem;
277
+ inset-inline-start: .75rem;
278
+ z-index: 70;
279
+ display: inline-grid;
280
+ place-items: center;
281
+ inline-size: 2.5rem;
282
+ block-size: 2.5rem;
283
+ border: 1px solid var(--dds-color-border-default);
284
+ border-radius: var(--dds-radius-md);
285
+ background: var(--dds-color-bg-elevated);
286
+ cursor: pointer;
287
+ }
288
+ .dds-console-rail__toggle > span[aria-hidden] {
289
+ position: relative;
290
+ display: block;
291
+ inline-size: 16px;
292
+ block-size: 2px;
293
+ border-radius: 2px;
294
+ background: currentColor;
295
+ }
296
+ .dds-console-rail__toggle > span[aria-hidden]::before,
297
+ .dds-console-rail__toggle > span[aria-hidden]::after {
298
+ content: "";
299
+ position: absolute;
300
+ inset-inline-start: 0;
301
+ inline-size: 16px;
302
+ block-size: 2px;
303
+ border-radius: 2px;
304
+ background: currentColor;
305
+ }
306
+ .dds-console-rail__toggle > span[aria-hidden]::before {
307
+ inset-block-start: -5px;
308
+ }
309
+ .dds-console-rail__toggle > span[aria-hidden]::after {
310
+ inset-block-start: 5px;
311
+ }
312
+ /* Translated off-screen is not gone: a closed drawer still takes Tab. Hide
313
+ it from the focus order with visibility, delayed by the slide's
314
+ duration so closing still animates, and lifted with no delay when it
315
+ opens. */
316
+ .dds-console-rail {
317
+ position: fixed;
318
+ inset: 0 auto 0 0;
319
+ z-index: 60;
320
+ inline-size: min(84vw, 18rem);
321
+ block-size: 100dvh;
322
+ visibility: hidden;
323
+ translate: -100% 0;
324
+ transition: translate .22s cubic-bezier(.16, 1, .3, 1), visibility 0s linear .22s;
325
+ box-shadow: var(--dds-elevation-2);
326
+ }
327
+ .dds-console-rail[data-open="true"] {
328
+ visibility: visible;
329
+ translate: 0 0;
330
+ transition-delay: 0s, 0s;
331
+ }
332
+ .dds-console-rail__scrim {
333
+ display: block;
334
+ position: fixed;
335
+ inset: 0;
336
+ z-index: 55;
337
+ background: var(--dds-color-overlay-dim);
338
+ opacity: 0;
339
+ pointer-events: none;
340
+ transition: opacity .22s ease;
341
+ }
342
+ .dds-console-rail__scrim[data-open="true"] {
343
+ opacity: 1;
344
+ pointer-events: auto;
345
+ }
346
+ .dds-console-main {
347
+ padding-block-start: 4rem;
348
+ }
349
+ .dds-console-page-header {
350
+ display: grid;
351
+ gap: var(--dds-space-16);
352
+ }
353
+ }
354
+
355
+ @media (prefers-reduced-motion: reduce) {
356
+ .dds-console-rail,
357
+ .dds-console-rail__scrim {
358
+ transition: none;
359
+ }
360
+ }
@@ -0,0 +1,226 @@
1
+ /* Table — the console list surface. Geometry comes from VisionLinq's shipped
2
+ * console (40px rows, 36px dense, sticky header in the tall variant); the
3
+ * colours are tokens only.
4
+ *
5
+ * .dds-table-wrap keeps position: relative even though it looks like dead
6
+ * positioning: it is what lets an absolutely-positioned .dds-visually-hidden
7
+ * caption or header label (see base.css) resolve against the table's own
8
+ * box instead of the page once the wrap is scrolling horizontally. Don't
9
+ * "clean up" this rule. */
10
+
11
+ .dds-table-wrap {
12
+ position: relative;
13
+ overflow-x: auto;
14
+ border: 1px solid var(--dds-color-border-default);
15
+ }
16
+
17
+ .dds-table-wrap--tall {
18
+ max-block-size: 70vh;
19
+ overflow: auto;
20
+ }
21
+
22
+ .dds-table {
23
+ inline-size: 100%;
24
+ border-collapse: collapse;
25
+ background: var(--dds-color-bg-elevated);
26
+ }
27
+
28
+ .dds-table caption {
29
+ padding: var(--dds-space-16);
30
+ text-align: start;
31
+ font-weight: 650;
32
+ }
33
+
34
+ .dds-table th,
35
+ .dds-table td {
36
+ padding: .625rem var(--dds-space-16);
37
+ border-block-start: 1px solid var(--dds-color-border-default);
38
+ font-size: .875rem;
39
+ line-height: 1.5;
40
+ text-align: start;
41
+ vertical-align: middle;
42
+ white-space: nowrap;
43
+ }
44
+
45
+ .dds-table tbody tr {
46
+ block-size: var(--dds-space-40);
47
+ }
48
+
49
+ .dds-table--fixed {
50
+ table-layout: fixed;
51
+ }
52
+
53
+ .dds-table [data-numeric] {
54
+ text-align: end;
55
+ font-variant-numeric: tabular-nums;
56
+ }
57
+
58
+ /* Code cells: ids, keys and hashes are read character by character, so a
59
+ * <code> inside a cell takes the mono family at 13px — a notch under the
60
+ * 14px cell text, which is the size mono needs to sit at the same visual
61
+ * weight as the sans around it. */
62
+ .dds-table code {
63
+ font-family: var(--dds-font-family-mono);
64
+ font-size: .8125rem;
65
+ }
66
+
67
+ .dds-table th small {
68
+ display: block;
69
+ color: var(--dds-color-text-muted);
70
+ font-weight: 400;
71
+ }
72
+ .dds-table th small[hidden] {
73
+ display: none;
74
+ }
75
+
76
+ .dds-table--dense th,
77
+ .dds-table--dense td {
78
+ padding-block: .375rem;
79
+ }
80
+
81
+ .dds-table--dense tbody tr {
82
+ block-size: 2.25rem;
83
+ }
84
+
85
+ .dds-table-wrap--tall thead th {
86
+ position: sticky;
87
+ inset-block-start: 0;
88
+ z-index: 1;
89
+ background: var(--dds-color-bg-elevated);
90
+ }
91
+
92
+ /* Separate borders under the sticky header: with border-collapse: collapse
93
+ * the cell borders belong to the table rather than the cells, and that can
94
+ * keep a sticky header from actually sticking when the wrap scrolls (the
95
+ * header row slides away, or its border stays behind). Every cell border
96
+ * here is block-start only, so separate borders with zero spacing draw the
97
+ * same lines. */
98
+ .dds-table-wrap--tall .dds-table {
99
+ border-collapse: separate;
100
+ border-spacing: 0;
101
+ }
102
+
103
+ .dds-table__sort {
104
+ appearance: none;
105
+ display: inline-flex;
106
+ align-items: center;
107
+ gap: .375rem;
108
+ padding: 0;
109
+ border: 0;
110
+ background: none;
111
+ font: inherit;
112
+ font-weight: 650;
113
+ color: inherit;
114
+ cursor: pointer;
115
+ }
116
+ .dds-table__sort[hidden] {
117
+ display: none;
118
+ }
119
+ .dds-table__sort:focus-visible {
120
+ outline: 2px solid var(--dds-color-border-focus);
121
+ outline-offset: 2px;
122
+ }
123
+
124
+ .dds-table__sort-mark {
125
+ inline-size: .5rem;
126
+ block-size: .5rem;
127
+ border-inline-end: 2px solid currentColor;
128
+ border-block-end: 2px solid currentColor;
129
+ rotate: 45deg;
130
+ opacity: .35;
131
+ translate: 0 -.125rem;
132
+ }
133
+ .dds-table__sort-mark[data-sort-state="ascending"] {
134
+ rotate: -135deg;
135
+ opacity: 1;
136
+ translate: 0 .125rem;
137
+ }
138
+ .dds-table__sort-mark[data-sort-state="descending"] {
139
+ opacity: 1;
140
+ }
141
+
142
+ .dds-table__actions {
143
+ display: flex;
144
+ justify-content: flex-end;
145
+ gap: var(--dds-space-8);
146
+ }
147
+ .dds-table__actions[hidden] {
148
+ display: none;
149
+ }
150
+
151
+ /* Row actions are 32px and stay on one line. A button's own geometry (40px
152
+ * default, 32px --sm, both with wrapping labels) plus the cell's 10px block
153
+ * padding made a row carrying a button measure ~53px against its 40px
154
+ * neighbours — a table whose rows are not one height reads as broken. So an
155
+ * action button is held to the --sm height whatever its size modifier
156
+ * (padding included, or a default button would still be 34px), its label
157
+ * never wraps (the wrap scrolls horizontally, nothing is clipped), and the
158
+ * actions cell trims its block padding so 32px fits inside the 40px row. */
159
+ .dds-table__actions .dds-btn {
160
+ min-block-size: var(--dds-space-32);
161
+ padding-block: var(--dds-space-4);
162
+ white-space: nowrap;
163
+ }
164
+
165
+ /* The same trim for any cell holding a small button (a copy button beside
166
+ * an id): its own 10px padding around a 32px button is a 53px row, and even
167
+ * in a dense table 6px around a 24px button plus the 1px border is 37px, a
168
+ * pixel over the 36px row. A default-size button outside the actions cell
169
+ * keeps full padding — that is an explicit choice to be taller. */
170
+ .dds-table td:has(> .dds-table__actions),
171
+ .dds-table td:has(.dds-btn--sm) {
172
+ padding-block: var(--dds-space-4);
173
+ }
174
+
175
+ /* Small buttons in a dense table are 24px — a copy button beside an id in
176
+ * any cell, and every row action whatever its size modifier (the same
177
+ * "whatever its modifier" as the 32px rule above, one density down) — so
178
+ * they fit the 36px dense row. Zero block padding is what gets there:
179
+ * --sm's own 4px padding around a 16px label already makes 26px, which
180
+ * min-block-size can't pull back down. 24px is exactly the §6 web floor.
181
+ * A default-size button outside the actions cell is left alone: that is an
182
+ * explicit choice to be taller. The actions selector out-specifies the
183
+ * 32px rule; the --sm one ties it and wins on source order. Middle
184
+ * alignment keeps an inline button beside text from growing the line box
185
+ * past its own 24px by sitting on the text baseline. */
186
+ .dds-table--dense .dds-btn--sm,
187
+ .dds-table--dense .dds-table__actions .dds-btn {
188
+ min-block-size: var(--dds-space-24);
189
+ padding-block: 0;
190
+ vertical-align: middle;
191
+ }
192
+
193
+ /* Touch pointers keep the 44px floor (spec §6, button.css). The two rules
194
+ * above out-specify button.css's coarse-pointer rule, so without restating
195
+ * it here a table would silently shrink every action below the touch
196
+ * target on a phone. Uniform row height yields on touch; the target does
197
+ * not (docs/decisions.md D-025). */
198
+ @media (pointer: coarse) {
199
+ .dds-table__actions .dds-btn,
200
+ .dds-table--dense .dds-btn--sm,
201
+ .dds-table--dense .dds-table__actions .dds-btn {
202
+ min-block-size: 44px;
203
+ }
204
+ }
205
+
206
+ .dds-table__order {
207
+ margin: 0 0 var(--dds-space-8);
208
+ font-size: .8125rem;
209
+ color: var(--dds-color-text-muted);
210
+ font-family: var(--dds-font-family-mono);
211
+ }
212
+
213
+ .dds-table__pagination {
214
+ display: flex;
215
+ justify-content: flex-end;
216
+ margin-block-start: var(--dds-space-16);
217
+ }
218
+ .dds-table__pagination[hidden] {
219
+ display: none;
220
+ }
221
+
222
+ @media (max-width: 56.25rem) {
223
+ .dds-table [data-fold] {
224
+ display: none;
225
+ }
226
+ }
package/dist/dds.css CHANGED
@@ -33,6 +33,21 @@
33
33
  }
34
34
  }
35
35
 
36
+ /* Visually hidden — content that stays in the accessibility tree (screen
37
+ * readers, AT) but takes up no visual space. Unlike display: none, which
38
+ * removes the element from the tree too, this only clips it out of view.
39
+ * Consumers: DataTable captions, icon-only header labels, skip links.
40
+ * The ancestor needs position: relative for this to clip against the right
41
+ * box (see table.css's .dds-table-wrap note) rather than the page. */
42
+ .dds-visually-hidden {
43
+ position: absolute;
44
+ inline-size: 1px;
45
+ block-size: 1px;
46
+ overflow: hidden;
47
+ clip-path: inset(50%);
48
+ white-space: nowrap;
49
+ }
50
+
36
51
  /* Button — spec §4.2: default → hover → pressed → focus-visible → disabled → loading.
37
52
  * Variants: primary / secondary / ghost / danger. Sizes: sm / md(default) / lg.
38
53
  * Control heights come from the spacing scale (32/40/48); type from the typo
@@ -1207,3 +1222,591 @@ dialog.dds-dialog::backdrop {
1207
1222
  gap: var(--dds-space-8);
1208
1223
  margin-top: var(--dds-space-8);
1209
1224
  }
1225
+
1226
+ /* Table — the console list surface. Geometry comes from VisionLinq's shipped
1227
+ * console (40px rows, 36px dense, sticky header in the tall variant); the
1228
+ * colours are tokens only.
1229
+ *
1230
+ * .dds-table-wrap keeps position: relative even though it looks like dead
1231
+ * positioning: it is what lets an absolutely-positioned .dds-visually-hidden
1232
+ * caption or header label (see base.css) resolve against the table's own
1233
+ * box instead of the page once the wrap is scrolling horizontally. Don't
1234
+ * "clean up" this rule. */
1235
+
1236
+ .dds-table-wrap {
1237
+ position: relative;
1238
+ overflow-x: auto;
1239
+ border: 1px solid var(--dds-color-border-default);
1240
+ }
1241
+
1242
+ .dds-table-wrap--tall {
1243
+ max-block-size: 70vh;
1244
+ overflow: auto;
1245
+ }
1246
+
1247
+ .dds-table {
1248
+ inline-size: 100%;
1249
+ border-collapse: collapse;
1250
+ background: var(--dds-color-bg-elevated);
1251
+ }
1252
+
1253
+ .dds-table caption {
1254
+ padding: var(--dds-space-16);
1255
+ text-align: start;
1256
+ font-weight: 650;
1257
+ }
1258
+
1259
+ .dds-table th,
1260
+ .dds-table td {
1261
+ padding: .625rem var(--dds-space-16);
1262
+ border-block-start: 1px solid var(--dds-color-border-default);
1263
+ font-size: .875rem;
1264
+ line-height: 1.5;
1265
+ text-align: start;
1266
+ vertical-align: middle;
1267
+ white-space: nowrap;
1268
+ }
1269
+
1270
+ .dds-table tbody tr {
1271
+ block-size: var(--dds-space-40);
1272
+ }
1273
+
1274
+ .dds-table--fixed {
1275
+ table-layout: fixed;
1276
+ }
1277
+
1278
+ .dds-table [data-numeric] {
1279
+ text-align: end;
1280
+ font-variant-numeric: tabular-nums;
1281
+ }
1282
+
1283
+ /* Code cells: ids, keys and hashes are read character by character, so a
1284
+ * <code> inside a cell takes the mono family at 13px — a notch under the
1285
+ * 14px cell text, which is the size mono needs to sit at the same visual
1286
+ * weight as the sans around it. */
1287
+ .dds-table code {
1288
+ font-family: var(--dds-font-family-mono);
1289
+ font-size: .8125rem;
1290
+ }
1291
+
1292
+ .dds-table th small {
1293
+ display: block;
1294
+ color: var(--dds-color-text-muted);
1295
+ font-weight: 400;
1296
+ }
1297
+ .dds-table th small[hidden] {
1298
+ display: none;
1299
+ }
1300
+
1301
+ .dds-table--dense th,
1302
+ .dds-table--dense td {
1303
+ padding-block: .375rem;
1304
+ }
1305
+
1306
+ .dds-table--dense tbody tr {
1307
+ block-size: 2.25rem;
1308
+ }
1309
+
1310
+ .dds-table-wrap--tall thead th {
1311
+ position: sticky;
1312
+ inset-block-start: 0;
1313
+ z-index: 1;
1314
+ background: var(--dds-color-bg-elevated);
1315
+ }
1316
+
1317
+ /* Separate borders under the sticky header: with border-collapse: collapse
1318
+ * the cell borders belong to the table rather than the cells, and that can
1319
+ * keep a sticky header from actually sticking when the wrap scrolls (the
1320
+ * header row slides away, or its border stays behind). Every cell border
1321
+ * here is block-start only, so separate borders with zero spacing draw the
1322
+ * same lines. */
1323
+ .dds-table-wrap--tall .dds-table {
1324
+ border-collapse: separate;
1325
+ border-spacing: 0;
1326
+ }
1327
+
1328
+ .dds-table__sort {
1329
+ appearance: none;
1330
+ display: inline-flex;
1331
+ align-items: center;
1332
+ gap: .375rem;
1333
+ padding: 0;
1334
+ border: 0;
1335
+ background: none;
1336
+ font: inherit;
1337
+ font-weight: 650;
1338
+ color: inherit;
1339
+ cursor: pointer;
1340
+ }
1341
+ .dds-table__sort[hidden] {
1342
+ display: none;
1343
+ }
1344
+ .dds-table__sort:focus-visible {
1345
+ outline: 2px solid var(--dds-color-border-focus);
1346
+ outline-offset: 2px;
1347
+ }
1348
+
1349
+ .dds-table__sort-mark {
1350
+ inline-size: .5rem;
1351
+ block-size: .5rem;
1352
+ border-inline-end: 2px solid currentColor;
1353
+ border-block-end: 2px solid currentColor;
1354
+ rotate: 45deg;
1355
+ opacity: .35;
1356
+ translate: 0 -.125rem;
1357
+ }
1358
+ .dds-table__sort-mark[data-sort-state="ascending"] {
1359
+ rotate: -135deg;
1360
+ opacity: 1;
1361
+ translate: 0 .125rem;
1362
+ }
1363
+ .dds-table__sort-mark[data-sort-state="descending"] {
1364
+ opacity: 1;
1365
+ }
1366
+
1367
+ .dds-table__actions {
1368
+ display: flex;
1369
+ justify-content: flex-end;
1370
+ gap: var(--dds-space-8);
1371
+ }
1372
+ .dds-table__actions[hidden] {
1373
+ display: none;
1374
+ }
1375
+
1376
+ /* Row actions are 32px and stay on one line. A button's own geometry (40px
1377
+ * default, 32px --sm, both with wrapping labels) plus the cell's 10px block
1378
+ * padding made a row carrying a button measure ~53px against its 40px
1379
+ * neighbours — a table whose rows are not one height reads as broken. So an
1380
+ * action button is held to the --sm height whatever its size modifier
1381
+ * (padding included, or a default button would still be 34px), its label
1382
+ * never wraps (the wrap scrolls horizontally, nothing is clipped), and the
1383
+ * actions cell trims its block padding so 32px fits inside the 40px row. */
1384
+ .dds-table__actions .dds-btn {
1385
+ min-block-size: var(--dds-space-32);
1386
+ padding-block: var(--dds-space-4);
1387
+ white-space: nowrap;
1388
+ }
1389
+
1390
+ /* The same trim for any cell holding a small button (a copy button beside
1391
+ * an id): its own 10px padding around a 32px button is a 53px row, and even
1392
+ * in a dense table 6px around a 24px button plus the 1px border is 37px, a
1393
+ * pixel over the 36px row. A default-size button outside the actions cell
1394
+ * keeps full padding — that is an explicit choice to be taller. */
1395
+ .dds-table td:has(> .dds-table__actions),
1396
+ .dds-table td:has(.dds-btn--sm) {
1397
+ padding-block: var(--dds-space-4);
1398
+ }
1399
+
1400
+ /* Small buttons in a dense table are 24px — a copy button beside an id in
1401
+ * any cell, and every row action whatever its size modifier (the same
1402
+ * "whatever its modifier" as the 32px rule above, one density down) — so
1403
+ * they fit the 36px dense row. Zero block padding is what gets there:
1404
+ * --sm's own 4px padding around a 16px label already makes 26px, which
1405
+ * min-block-size can't pull back down. 24px is exactly the §6 web floor.
1406
+ * A default-size button outside the actions cell is left alone: that is an
1407
+ * explicit choice to be taller. The actions selector out-specifies the
1408
+ * 32px rule; the --sm one ties it and wins on source order. Middle
1409
+ * alignment keeps an inline button beside text from growing the line box
1410
+ * past its own 24px by sitting on the text baseline. */
1411
+ .dds-table--dense .dds-btn--sm,
1412
+ .dds-table--dense .dds-table__actions .dds-btn {
1413
+ min-block-size: var(--dds-space-24);
1414
+ padding-block: 0;
1415
+ vertical-align: middle;
1416
+ }
1417
+
1418
+ /* Touch pointers keep the 44px floor (spec §6, button.css). The two rules
1419
+ * above out-specify button.css's coarse-pointer rule, so without restating
1420
+ * it here a table would silently shrink every action below the touch
1421
+ * target on a phone. Uniform row height yields on touch; the target does
1422
+ * not (docs/decisions.md D-025). */
1423
+ @media (pointer: coarse) {
1424
+ .dds-table__actions .dds-btn,
1425
+ .dds-table--dense .dds-btn--sm,
1426
+ .dds-table--dense .dds-table__actions .dds-btn {
1427
+ min-block-size: 44px;
1428
+ }
1429
+ }
1430
+
1431
+ .dds-table__order {
1432
+ margin: 0 0 var(--dds-space-8);
1433
+ font-size: .8125rem;
1434
+ color: var(--dds-color-text-muted);
1435
+ font-family: var(--dds-font-family-mono);
1436
+ }
1437
+
1438
+ .dds-table__pagination {
1439
+ display: flex;
1440
+ justify-content: flex-end;
1441
+ margin-block-start: var(--dds-space-16);
1442
+ }
1443
+ .dds-table__pagination[hidden] {
1444
+ display: none;
1445
+ }
1446
+
1447
+ @media (max-width: 56.25rem) {
1448
+ .dds-table [data-fold] {
1449
+ display: none;
1450
+ }
1451
+ }
1452
+
1453
+ /* Console shell — the 232px rail + below-900px drawer that a console route
1454
+ * renders around its content. The fold breakpoint below is written in the
1455
+ * same rem form as table.css's column fold (56.25rem = 900px at a 16px root
1456
+ * font size), so a visitor with any browser default font size sees the rail
1457
+ * change shape and the table's columns fold at the same viewport width
1458
+ * inside the same console.
1459
+ *
1460
+ * Tokens only. Every class that declares `display` re-declares
1461
+ * `[hidden] { display: none }` beside it, at a class+attribute specificity
1462
+ * that beats every later `display` declaration for that class — including
1463
+ * the ones inside the media query below — so a hidden rail, toggle, or
1464
+ * scrim can never be drawn on screen regardless of viewport width. */
1465
+
1466
+ .dds-console-shell {
1467
+ min-block-size: 100vh;
1468
+ display: grid;
1469
+ grid-template-columns: 14.5rem minmax(0, 1fr);
1470
+ background: var(--dds-color-bg-default);
1471
+ color: var(--dds-color-text-primary);
1472
+ font-size: 1rem;
1473
+ line-height: 1.5;
1474
+ }
1475
+ .dds-console-shell[hidden] {
1476
+ display: none;
1477
+ }
1478
+
1479
+ /* The skip link is hidden by .dds-visually-hidden (applied by the
1480
+ * component) and only needs to say what it looks like once focused —
1481
+ * a visually-hidden element is never in the focus-visible/`:focus` chain
1482
+ * while `hidden`, so no `[hidden]` guard applies here. */
1483
+ .dds-console-skip:focus {
1484
+ position: fixed;
1485
+ inset-block-start: var(--dds-space-8);
1486
+ inset-inline-start: var(--dds-space-8);
1487
+ z-index: 100;
1488
+ clip-path: none;
1489
+ inline-size: auto;
1490
+ block-size: auto;
1491
+ overflow: visible;
1492
+ white-space: normal;
1493
+ padding: var(--dds-space-12) var(--dds-space-16);
1494
+ background: var(--dds-color-bg-elevated);
1495
+ color: var(--dds-color-text-primary);
1496
+ border: 2px solid var(--dds-color-border-focus);
1497
+ }
1498
+
1499
+ .dds-console-rail {
1500
+ position: sticky;
1501
+ inset-block-start: 0;
1502
+ block-size: 100vh;
1503
+ display: flex;
1504
+ flex-direction: column;
1505
+ gap: var(--dds-space-16);
1506
+ padding: var(--dds-space-20) var(--dds-space-12);
1507
+ overflow-y: auto;
1508
+ border-inline-end: 1px solid var(--dds-color-border-default);
1509
+ background: var(--dds-color-bg-elevated);
1510
+ }
1511
+ .dds-console-rail[hidden] {
1512
+ display: none;
1513
+ }
1514
+
1515
+ .dds-console-rail__brand {
1516
+ display: inline-flex;
1517
+ align-items: center;
1518
+ gap: var(--dds-space-8);
1519
+ padding-inline: var(--dds-space-8);
1520
+ color: inherit;
1521
+ font-size: .9375rem;
1522
+ font-weight: 700;
1523
+ letter-spacing: -.02em;
1524
+ text-decoration: none;
1525
+ }
1526
+ .dds-console-rail__brand[hidden] {
1527
+ display: none;
1528
+ }
1529
+ .dds-console-rail__brand img {
1530
+ inline-size: 20px;
1531
+ block-size: 20px;
1532
+ }
1533
+
1534
+ .dds-console-rail__context {
1535
+ display: grid;
1536
+ gap: var(--dds-space-4);
1537
+ margin: 0;
1538
+ padding-inline: var(--dds-space-8);
1539
+ font-size: var(--dds-typo-caption-font-size);
1540
+ line-height: var(--dds-typo-caption-line-height);
1541
+ }
1542
+ .dds-console-rail__context[hidden] {
1543
+ display: none;
1544
+ }
1545
+ .dds-console-rail__context dt {
1546
+ color: var(--dds-color-text-muted);
1547
+ }
1548
+ .dds-console-rail__context dd {
1549
+ margin: 0;
1550
+ font-family: var(--dds-font-family-mono);
1551
+ overflow-wrap: anywhere;
1552
+ }
1553
+
1554
+ .dds-console-rail__nav {
1555
+ display: grid;
1556
+ gap: var(--dds-space-16);
1557
+ }
1558
+ .dds-console-rail__nav[hidden] {
1559
+ display: none;
1560
+ }
1561
+
1562
+ .dds-console-rail__group {
1563
+ display: grid;
1564
+ gap: var(--dds-space-2, 2px);
1565
+ }
1566
+ .dds-console-rail__group[hidden] {
1567
+ display: none;
1568
+ }
1569
+ .dds-console-rail__group ul {
1570
+ margin: 0;
1571
+ padding: 0;
1572
+ list-style: none;
1573
+ display: grid;
1574
+ gap: 2px;
1575
+ }
1576
+ .dds-console-rail__group ul[hidden] {
1577
+ display: none;
1578
+ }
1579
+ .dds-console-rail__group-label {
1580
+ margin: 0;
1581
+ padding: 0 var(--dds-space-8) var(--dds-space-4);
1582
+ color: var(--dds-color-text-muted);
1583
+ font-size: .6875rem;
1584
+ font-weight: 600;
1585
+ letter-spacing: .05em;
1586
+ text-transform: uppercase;
1587
+ }
1588
+
1589
+ .dds-console-rail__item {
1590
+ display: flex;
1591
+ align-items: center;
1592
+ justify-content: space-between;
1593
+ gap: var(--dds-space-8);
1594
+ min-block-size: var(--dds-space-40);
1595
+ padding-inline: var(--dds-space-8);
1596
+ border-radius: var(--dds-radius-md);
1597
+ color: var(--dds-color-text-secondary);
1598
+ font-size: .875rem;
1599
+ font-weight: 500;
1600
+ text-decoration: none;
1601
+ }
1602
+ .dds-console-rail__item[hidden] {
1603
+ display: none;
1604
+ }
1605
+ .dds-console-rail__item:hover {
1606
+ background: var(--dds-color-bg-subtle);
1607
+ color: var(--dds-color-text-primary);
1608
+ }
1609
+ .dds-console-rail__item[aria-current="page"] {
1610
+ background: var(--dds-color-bg-brand-subtle);
1611
+ color: var(--dds-color-text-brand);
1612
+ font-weight: 650;
1613
+ }
1614
+ .dds-console-rail__item:focus-visible {
1615
+ outline: 2px solid var(--dds-color-border-focus);
1616
+ outline-offset: 2px;
1617
+ }
1618
+
1619
+ .dds-console-rail__badge {
1620
+ display: inline-grid;
1621
+ place-items: center;
1622
+ min-inline-size: 1.125rem;
1623
+ block-size: 1.125rem;
1624
+ padding-inline: .3125rem;
1625
+ border-radius: var(--dds-radius-full);
1626
+ background: var(--dds-color-bg-brand);
1627
+ color: var(--dds-color-text-on-brand);
1628
+ font-size: .6875rem;
1629
+ font-weight: 700;
1630
+ font-variant-numeric: tabular-nums;
1631
+ }
1632
+ .dds-console-rail__badge[hidden] {
1633
+ display: none;
1634
+ }
1635
+
1636
+ .dds-console-rail__foot {
1637
+ margin-block-start: auto;
1638
+ display: grid;
1639
+ gap: var(--dds-space-8);
1640
+ justify-items: start;
1641
+ padding: var(--dds-space-12) var(--dds-space-8) 0;
1642
+ border-block-start: 1px solid var(--dds-color-border-default);
1643
+ }
1644
+ .dds-console-rail__foot[hidden] {
1645
+ display: none;
1646
+ }
1647
+
1648
+ .dds-console-rail__toggle {
1649
+ display: none;
1650
+ }
1651
+ .dds-console-rail__toggle[hidden] {
1652
+ display: none;
1653
+ }
1654
+
1655
+ .dds-console-rail__scrim {
1656
+ display: none;
1657
+ }
1658
+ .dds-console-rail__scrim[hidden] {
1659
+ display: none;
1660
+ }
1661
+
1662
+ .dds-console-main {
1663
+ min-inline-size: 0;
1664
+ padding: clamp(1rem, 3vw, 2rem);
1665
+ }
1666
+
1667
+ .dds-console-page-header {
1668
+ display: flex;
1669
+ align-items: flex-start;
1670
+ justify-content: space-between;
1671
+ gap: var(--dds-space-32);
1672
+ margin-block-end: var(--dds-space-32);
1673
+ }
1674
+ .dds-console-page-header[hidden] {
1675
+ display: none;
1676
+ }
1677
+ .dds-console-page-header h1 {
1678
+ max-inline-size: 24ch;
1679
+ margin: .25rem 0 0;
1680
+ font-size: clamp(1.75rem, 2.2vw, 2.25rem);
1681
+ line-height: 1.1;
1682
+ letter-spacing: -.03em;
1683
+ text-wrap: balance;
1684
+ }
1685
+ .dds-console-page-header__actions {
1686
+ display: flex;
1687
+ gap: var(--dds-space-8);
1688
+ }
1689
+ .dds-console-page-header__actions[hidden] {
1690
+ display: none;
1691
+ }
1692
+ .dds-console-page-eyebrow {
1693
+ margin: 0;
1694
+ color: var(--dds-color-text-muted);
1695
+ font-size: var(--dds-typo-caption-font-size);
1696
+ line-height: var(--dds-typo-caption-line-height);
1697
+ }
1698
+
1699
+ /* Console density: every dds-css control inside the shell reads at the body-2
1700
+ * size (14px), not dds-btn's own default (13px, --dds-typo-label-font-size).
1701
+ * Scoped to .dds-console-shell rather than left as a global override on
1702
+ * .dds-btn/.dds-input/.dds-select/.dds-textarea themselves — a shell that
1703
+ * establishes a density owns the override for controls inside it, the same
1704
+ * way a shared class must never carry a layout rule that only makes sense
1705
+ * for one consumer's container.
1706
+ *
1707
+ * .dds-btn's :not() exclusions matter: this selector's specificity (0,2,0)
1708
+ * beats every size modifier in button.css (.dds-btn--lg, .dds-btn--sm, both
1709
+ * 0,1,0), so an unqualified `.dds-console-shell .dds-btn` would override
1710
+ * --lg's own font-size back down to body-2 and would give --sm the same
1711
+ * font-size as a default button (--sm declares none of its own, so it was
1712
+ * inheriting whichever rule won for plain .dds-btn) — the one outcome a
1713
+ * size modifier exists to prevent. .dds-input/.dds-select/.dds-textarea
1714
+ * carry no such modifiers today, so no matching exclusion is needed there. */
1715
+ .dds-console-shell .dds-btn:not(.dds-btn--lg):not(.dds-btn--sm),
1716
+ .dds-console-shell .dds-input,
1717
+ .dds-console-shell .dds-select,
1718
+ .dds-console-shell .dds-textarea {
1719
+ font-size: var(--dds-typo-body-2-font-size);
1720
+ }
1721
+
1722
+ @media (max-width: 56.25rem) {
1723
+ .dds-console-shell {
1724
+ grid-template-columns: minmax(0, 1fr);
1725
+ }
1726
+ .dds-console-rail__toggle {
1727
+ position: fixed;
1728
+ inset-block-start: .75rem;
1729
+ inset-inline-start: .75rem;
1730
+ z-index: 70;
1731
+ display: inline-grid;
1732
+ place-items: center;
1733
+ inline-size: 2.5rem;
1734
+ block-size: 2.5rem;
1735
+ border: 1px solid var(--dds-color-border-default);
1736
+ border-radius: var(--dds-radius-md);
1737
+ background: var(--dds-color-bg-elevated);
1738
+ cursor: pointer;
1739
+ }
1740
+ .dds-console-rail__toggle > span[aria-hidden] {
1741
+ position: relative;
1742
+ display: block;
1743
+ inline-size: 16px;
1744
+ block-size: 2px;
1745
+ border-radius: 2px;
1746
+ background: currentColor;
1747
+ }
1748
+ .dds-console-rail__toggle > span[aria-hidden]::before,
1749
+ .dds-console-rail__toggle > span[aria-hidden]::after {
1750
+ content: "";
1751
+ position: absolute;
1752
+ inset-inline-start: 0;
1753
+ inline-size: 16px;
1754
+ block-size: 2px;
1755
+ border-radius: 2px;
1756
+ background: currentColor;
1757
+ }
1758
+ .dds-console-rail__toggle > span[aria-hidden]::before {
1759
+ inset-block-start: -5px;
1760
+ }
1761
+ .dds-console-rail__toggle > span[aria-hidden]::after {
1762
+ inset-block-start: 5px;
1763
+ }
1764
+ /* Translated off-screen is not gone: a closed drawer still takes Tab. Hide
1765
+ it from the focus order with visibility, delayed by the slide's
1766
+ duration so closing still animates, and lifted with no delay when it
1767
+ opens. */
1768
+ .dds-console-rail {
1769
+ position: fixed;
1770
+ inset: 0 auto 0 0;
1771
+ z-index: 60;
1772
+ inline-size: min(84vw, 18rem);
1773
+ block-size: 100dvh;
1774
+ visibility: hidden;
1775
+ translate: -100% 0;
1776
+ transition: translate .22s cubic-bezier(.16, 1, .3, 1), visibility 0s linear .22s;
1777
+ box-shadow: var(--dds-elevation-2);
1778
+ }
1779
+ .dds-console-rail[data-open="true"] {
1780
+ visibility: visible;
1781
+ translate: 0 0;
1782
+ transition-delay: 0s, 0s;
1783
+ }
1784
+ .dds-console-rail__scrim {
1785
+ display: block;
1786
+ position: fixed;
1787
+ inset: 0;
1788
+ z-index: 55;
1789
+ background: var(--dds-color-overlay-dim);
1790
+ opacity: 0;
1791
+ pointer-events: none;
1792
+ transition: opacity .22s ease;
1793
+ }
1794
+ .dds-console-rail__scrim[data-open="true"] {
1795
+ opacity: 1;
1796
+ pointer-events: auto;
1797
+ }
1798
+ .dds-console-main {
1799
+ padding-block-start: 4rem;
1800
+ }
1801
+ .dds-console-page-header {
1802
+ display: grid;
1803
+ gap: var(--dds-space-16);
1804
+ }
1805
+ }
1806
+
1807
+ @media (prefers-reduced-motion: reduce) {
1808
+ .dds-console-rail,
1809
+ .dds-console-rail__scrim {
1810
+ transition: none;
1811
+ }
1812
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devslab/dds-css",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "DDS CSS component layer — class-based, framework-neutral, semantic-token-only (spec §4.3 v1 inventory, 20 components)",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",