@echodial/deck 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1327 -0
- package/bin/deck.mjs +219 -0
- package/build.mjs +338 -0
- package/dist/api-buckets.json +10486 -0
- package/dist/api.json +57896 -0
- package/dist/brand/deck-apple-touch-icon.png +0 -0
- package/dist/brand/deck-logo-dark.svg +1 -0
- package/dist/brand/deck-logo-light.svg +1 -0
- package/dist/brand/deck-logo-stacked.svg +1 -0
- package/dist/brand/deck-logo.svg +1 -0
- package/dist/brand/deck-mark.svg +7 -0
- package/dist/brand/deck-og.png +0 -0
- package/dist/collisions.json +223 -0
- package/dist/deck-adapters.js +482 -0
- package/dist/deck-adapters.min.js +23 -0
- package/dist/deck-extras.js +823 -0
- package/dist/deck-extras.min.js +5 -0
- package/dist/deck-icons.svg +184 -0
- package/dist/deck.bundle.js +2638 -0
- package/dist/deck.bundle.min.js +86 -0
- package/dist/deck.css +7904 -0
- package/dist/deck.esm.js +2644 -0
- package/dist/deck.js +1331 -0
- package/dist/deck.min.css +32 -0
- package/dist/deck.min.js +60 -0
- package/dist/layers/anchor.css +192 -0
- package/dist/layers/buttons.css +210 -0
- package/dist/layers/charts.css +337 -0
- package/dist/layers/combobox.css +248 -0
- package/dist/layers/components.css +868 -0
- package/dist/layers/container.css +199 -0
- package/dist/layers/datagrid.css +335 -0
- package/dist/layers/datepicker.css +305 -0
- package/dist/layers/forms.css +401 -0
- package/dist/layers/gradients.css +302 -0
- package/dist/layers/inputs.css +522 -0
- package/dist/layers/layers.css +87 -0
- package/dist/layers/layout.css +235 -0
- package/dist/layers/logical.css +226 -0
- package/dist/layers/media.css +400 -0
- package/dist/layers/mobile.css +245 -0
- package/dist/layers/motion.css +379 -0
- package/dist/layers/nav.css +771 -0
- package/dist/layers/perf.css +234 -0
- package/dist/layers/print.css +198 -0
- package/dist/layers/reset.css +103 -0
- package/dist/layers/space3d.css +280 -0
- package/dist/layers/toasts.css +201 -0
- package/dist/layers/tokens.css +229 -0
- package/dist/layers/type.css +114 -0
- package/dist/layers/utilities.css +241 -0
- package/dist/sizes.json +52 -0
- package/dist/usage.json +6340 -0
- package/package.json +94 -0
- package/src/00-layers.css +87 -0
- package/src/01-tokens.css +229 -0
- package/src/02-reset.css +103 -0
- package/src/03-type.css +114 -0
- package/src/04-layout.css +235 -0
- package/src/05-buttons.css +210 -0
- package/src/06-forms.css +401 -0
- package/src/07-components.css +868 -0
- package/src/08-mobile.css +245 -0
- package/src/09-utilities.css +241 -0
- package/src/10-datepicker.css +305 -0
- package/src/11-combobox.css +248 -0
- package/src/12-datagrid.css +335 -0
- package/src/13-toasts.css +201 -0
- package/src/14-charts.css +337 -0
- package/src/16-motion.css +379 -0
- package/src/18-container.css +199 -0
- package/src/19-logical.css +226 -0
- package/src/20-gradients.css +302 -0
- package/src/21-space3d.css +280 -0
- package/src/22-nav.css +771 -0
- package/src/23-inputs.css +522 -0
- package/src/24-media.css +400 -0
- package/src/25-anchor.css +192 -0
- package/src/26-perf.css +234 -0
- package/src/99-print.css +198 -0
- package/src/brand/deck-apple-touch-icon.png +0 -0
- package/src/brand/deck-logo-dark.svg +1 -0
- package/src/brand/deck-logo-light.svg +1 -0
- package/src/brand/deck-logo-stacked.svg +1 -0
- package/src/brand/deck-logo.svg +1 -0
- package/src/brand/deck-mark.svg +7 -0
- package/src/brand/deck-og.png +0 -0
- package/src/brand/sources.json +7 -0
- package/src/deck-icons.svg +184 -0
- package/src/js/deck-adapters.js +482 -0
- package/src/js/deck-extras.js +823 -0
- package/src/js/deck.js +1331 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 18 Container queries
|
|
3
|
+
A component should respond to the space it was given, not to the size of the
|
|
4
|
+
window. A card in a narrow sidebar and the same card in a wide main column
|
|
5
|
+
are the same markup and should lay out differently without either one
|
|
6
|
+
knowing where it was placed.
|
|
7
|
+
========================================================================== */
|
|
8
|
+
|
|
9
|
+
@layer deck.layout {
|
|
10
|
+
|
|
11
|
+
/* --- Declaring a container ----------------------------------------------
|
|
12
|
+
.cq is the plain one. The named versions let a nested component query a
|
|
13
|
+
specific ancestor instead of the closest one.
|
|
14
|
+
---------------------------------------------------------------------- */
|
|
15
|
+
.cq { container-type: inline-size; }
|
|
16
|
+
.cq-size { container-type: size; }
|
|
17
|
+
.cq-normal { container-type: normal; }
|
|
18
|
+
|
|
19
|
+
.cq-panel { container: panel / inline-size; }
|
|
20
|
+
.cq-pane { container: pane / inline-size; }
|
|
21
|
+
.cq-row { container: row / inline-size; }
|
|
22
|
+
.cq-shell { container: shell / inline-size; }
|
|
23
|
+
|
|
24
|
+
/* Every layout primitive can become a container without extra markup */
|
|
25
|
+
.card.cq, .panel.cq, .list.cq, .split > .cq, .grid > .cq { container-type: inline-size; }
|
|
26
|
+
|
|
27
|
+
/* --- Container query units -----------------------------------------------
|
|
28
|
+
cqi is 1% of the container's inline size. Type that scales with its column
|
|
29
|
+
rather than with the viewport — a heading in a sidebar stays small even on
|
|
30
|
+
a 32 inch monitor.
|
|
31
|
+
---------------------------------------------------------------------- */
|
|
32
|
+
.cq :is(.text-cq) { font-size: clamp(var(--text-sm), 3.4cqi, var(--text-lg)); }
|
|
33
|
+
.cq :is(.display-cq) {
|
|
34
|
+
font-size: clamp(1.5rem, 8cqi, 3.25rem);
|
|
35
|
+
line-height: 1.05;
|
|
36
|
+
letter-spacing: -.03em;
|
|
37
|
+
}
|
|
38
|
+
.cq :is(.pad-cq) { padding: clamp(var(--space-3), 4cqi, var(--space-8)); }
|
|
39
|
+
.cq :is(.gap-cq) { gap: clamp(var(--space-2), 2.5cqi, var(--space-6)); }
|
|
40
|
+
|
|
41
|
+
/* --- Container-driven components ---------------------------------------- */
|
|
42
|
+
|
|
43
|
+
/* A card that turns into a horizontal media object once it has the room */
|
|
44
|
+
.card-flex { display: flex; flex-direction: column; }
|
|
45
|
+
.card-flex .card-media { flex: 0 0 auto; }
|
|
46
|
+
.card-flex .card-media img { inline-size: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
|
|
47
|
+
|
|
48
|
+
@container (min-width: 30rem) {
|
|
49
|
+
.card-flex { flex-direction: row; }
|
|
50
|
+
.card-flex .card-media { inline-size: 40%; max-inline-size: 15rem; }
|
|
51
|
+
.card-flex .card-media img { block-size: 100%; aspect-ratio: auto; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A stat block that stacks tight, then spreads, then goes inline */
|
|
55
|
+
.stat-cq { display: flex; flex-direction: column; gap: var(--space-1); }
|
|
56
|
+
@container (min-width: 18rem) {
|
|
57
|
+
.stat-cq .stat-value { font-size: var(--text-2xl); }
|
|
58
|
+
}
|
|
59
|
+
@container (min-width: 28rem) {
|
|
60
|
+
.stat-cq {
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
align-items: baseline;
|
|
63
|
+
gap: var(--space-3);
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
}
|
|
66
|
+
.stat-cq .stat-label { order: -1; min-inline-size: 100%; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* List rows that reveal their metadata as space allows */
|
|
70
|
+
.metarow { display: flex; align-items: center; gap: var(--space-3); }
|
|
71
|
+
.metarow .row-meta { display: none; }
|
|
72
|
+
@container (min-width: 26rem) {
|
|
73
|
+
.metarow .row-meta { display: block; color: var(--text-muted); font-size: var(--text-sm); }
|
|
74
|
+
}
|
|
75
|
+
@container (min-width: 38rem) {
|
|
76
|
+
.metarow .row-meta-wide { display: block; }
|
|
77
|
+
}
|
|
78
|
+
.metarow .row-meta-wide { display: none; }
|
|
79
|
+
|
|
80
|
+
/* Form fields that pair up inside their own container, not the viewport.
|
|
81
|
+
This is the one that keeps biting people: a two-up field row inside a
|
|
82
|
+
narrow modal should stay stacked even on a desktop. */
|
|
83
|
+
.field-row-cq { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
|
|
84
|
+
@container (min-width: 32rem) {
|
|
85
|
+
.field-row-cq { grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Button rows that go full width when cramped */
|
|
89
|
+
.actions-cq { display: flex; flex-direction: column; gap: var(--space-2); }
|
|
90
|
+
.actions-cq > .btn { inline-size: 100%; }
|
|
91
|
+
@container (min-width: 24rem) {
|
|
92
|
+
.actions-cq { flex-direction: row; }
|
|
93
|
+
.actions-cq > .btn { inline-size: auto; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* A data grid that drops to cards based on its own width. This is strictly
|
|
97
|
+
better than the media-query version, because a grid inside a sheet is
|
|
98
|
+
narrow no matter how wide the screen is. */
|
|
99
|
+
@container (max-width: 44rem) {
|
|
100
|
+
.dg-cq thead, .dg-cq tfoot { display: none; }
|
|
101
|
+
.dg-cq, .dg-cq tbody, .dg-cq tr, .dg-cq td { display: block; inline-size: 100%; }
|
|
102
|
+
.dg-cq tr {
|
|
103
|
+
background: var(--surface);
|
|
104
|
+
border: 1px solid var(--line);
|
|
105
|
+
border-radius: var(--r-md);
|
|
106
|
+
margin-block-end: var(--space-3);
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
}
|
|
109
|
+
.dg-cq td {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
gap: var(--space-4);
|
|
114
|
+
block-size: auto;
|
|
115
|
+
min-block-size: var(--tap);
|
|
116
|
+
text-align: end;
|
|
117
|
+
white-space: normal;
|
|
118
|
+
position: static !important;
|
|
119
|
+
box-shadow: none !important;
|
|
120
|
+
clip-path: none !important;
|
|
121
|
+
}
|
|
122
|
+
.dg-cq td::before {
|
|
123
|
+
content: attr(data-label);
|
|
124
|
+
font-size: var(--text-xs);
|
|
125
|
+
font-weight: 620;
|
|
126
|
+
color: var(--text-muted);
|
|
127
|
+
text-align: start;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Named-container example: a sidebar nav that collapses to icons when the
|
|
132
|
+
shell is narrow, regardless of what is nested inside it. */
|
|
133
|
+
/* FINDINGS 64: display:none removes the label from the accessibility tree,
|
|
134
|
+
not just from the screen. The icon inside the link is aria-hidden by
|
|
135
|
+
convention, so a collapsed .sidebar-link has NO accessible name -- a
|
|
136
|
+
screen reader announces "link" for every destination in the app's primary
|
|
137
|
+
navigation.
|
|
138
|
+
|
|
139
|
+
Note that .sidebar-group two lines down gets it right: font-size: 0 hides
|
|
140
|
+
the heading visually and keeps it in the tree. The link rule should hide
|
|
141
|
+
the span the same way, or with an .sr-only clip. Until it does, authors
|
|
142
|
+
have to put aria-label on every .sidebar-link by hand. */
|
|
143
|
+
@container shell (max-width: 15rem) {
|
|
144
|
+
.sidebar-link span { display: none; }
|
|
145
|
+
.sidebar-link { justify-content: center; }
|
|
146
|
+
.sidebar-group { text-align: center; font-size: 0; }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* --- Container breakpoint utilities --------------------------------------
|
|
150
|
+
Same idea as the responsive utilities, but keyed to the container.
|
|
151
|
+
---------------------------------------------------------------------- */
|
|
152
|
+
@container (min-width: 24rem) {
|
|
153
|
+
.cq-sm\:row { flex-direction: row; }
|
|
154
|
+
.cq-sm\:flex { display: flex; }
|
|
155
|
+
.cq-sm\:hidden { display: none; }
|
|
156
|
+
.cq-sm\:block { display: block; }
|
|
157
|
+
.cq-sm\:w-auto { inline-size: auto; }
|
|
158
|
+
}
|
|
159
|
+
@container (min-width: 32rem) {
|
|
160
|
+
.cq-md\:row { flex-direction: row; }
|
|
161
|
+
.cq-md\:col { flex-direction: column; }
|
|
162
|
+
.cq-md\:flex { display: flex; }
|
|
163
|
+
.cq-md\:grid { display: grid; }
|
|
164
|
+
.cq-md\:hidden { display: none; }
|
|
165
|
+
.cq-md\:block { display: block; }
|
|
166
|
+
.cq-md\:items-center { align-items: center; }
|
|
167
|
+
.cq-md\:justify-between { justify-content: space-between; }
|
|
168
|
+
}
|
|
169
|
+
@container (min-width: 44rem) {
|
|
170
|
+
.cq-lg\:row { flex-direction: row; }
|
|
171
|
+
.cq-lg\:flex { display: flex; }
|
|
172
|
+
.cq-lg\:hidden { display: none; }
|
|
173
|
+
.cq-lg\:block { display: block; }
|
|
174
|
+
.cq-lg\:grid-2 { display: grid; grid-template-columns: 1fr 1fr; }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* --- Style queries -------------------------------------------------------
|
|
178
|
+
Query a custom property rather than a size. Set --tone on a container and
|
|
179
|
+
everything inside it adapts, with no extra classes on the children.
|
|
180
|
+
Supported in current Chrome and Safari; the fallback is simply no change.
|
|
181
|
+
---------------------------------------------------------------------- */
|
|
182
|
+
.cq-tone { container-type: inline-size; }
|
|
183
|
+
|
|
184
|
+
@container style(--tone: critical) {
|
|
185
|
+
.tone-surface { background: var(--bad-100); border-color: color-mix(in oklab, var(--bad-500) 35%, transparent); }
|
|
186
|
+
.tone-text { color: var(--bad-700); }
|
|
187
|
+
.tone-icon { color: var(--bad-500); }
|
|
188
|
+
}
|
|
189
|
+
@container style(--tone: caution) {
|
|
190
|
+
.tone-surface { background: var(--warn-100); border-color: color-mix(in oklab, var(--warn-500) 35%, transparent); }
|
|
191
|
+
.tone-text { color: var(--warn-700); }
|
|
192
|
+
.tone-icon { color: var(--warn-500); }
|
|
193
|
+
}
|
|
194
|
+
@container style(--tone: clear) {
|
|
195
|
+
.tone-surface { background: var(--good-100); border-color: color-mix(in oklab, var(--good-500) 35%, transparent); }
|
|
196
|
+
.tone-text { color: var(--good-700); }
|
|
197
|
+
.tone-icon { color: var(--good-500); }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 12 Data grid
|
|
3
|
+
A real table with a frozen header and pinned columns. Add .dg-pin-start to
|
|
4
|
+
the cells of a column you want frozen on the left, .dg-pin-end for the
|
|
5
|
+
right. Set --dg-pin-start to the pinned column's width.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
@layer deck.components {
|
|
9
|
+
|
|
10
|
+
.dg-wrap {
|
|
11
|
+
--dg-pin-start: 0px;
|
|
12
|
+
--dg-pin-end: 0px;
|
|
13
|
+
--dg-row-h: 44px;
|
|
14
|
+
position: relative;
|
|
15
|
+
inline-size: 100%;
|
|
16
|
+
max-block-size: var(--dg-height, 70dvh);
|
|
17
|
+
overflow: auto;
|
|
18
|
+
overscroll-behavior: contain;
|
|
19
|
+
-webkit-overflow-scrolling: touch;
|
|
20
|
+
background: var(--surface);
|
|
21
|
+
border: 1px solid var(--line);
|
|
22
|
+
border-radius: var(--r-md);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* A real table with a frozen header and pinned columns. Use it when the
|
|
26
|
+
reader operates on data — sorts, selects, scans — rather than reads it;
|
|
27
|
+
.table is lighter for presentation. Must sit inside .dg-wrap, which
|
|
28
|
+
carries the scroll container and every custom property these rules read. */
|
|
29
|
+
.dg {
|
|
30
|
+
inline-size: 100%;
|
|
31
|
+
border-collapse: separate;
|
|
32
|
+
border-spacing: 0;
|
|
33
|
+
font-size: var(--text-sm);
|
|
34
|
+
font-variant-numeric: tabular-nums;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dg :is(th, td) {
|
|
38
|
+
padding: 0 var(--space-4);
|
|
39
|
+
block-size: var(--dg-row-h);
|
|
40
|
+
text-align: start;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
background: var(--surface);
|
|
43
|
+
border-block-end: 1px solid var(--line);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* --- Frozen header ------------------------------------------------------- */
|
|
47
|
+
.dg thead th {
|
|
48
|
+
position: sticky;
|
|
49
|
+
inset-block-start: 0;
|
|
50
|
+
z-index: 3;
|
|
51
|
+
background: var(--surface-2);
|
|
52
|
+
font-size: var(--text-xs);
|
|
53
|
+
font-weight: 620;
|
|
54
|
+
color: var(--text-muted);
|
|
55
|
+
border-block-end: 1px solid var(--line-strong);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* --- Pinned columns ------------------------------------------------------ */
|
|
59
|
+
.dg :is(th, td).dg-pin-start {
|
|
60
|
+
position: sticky;
|
|
61
|
+
inset-inline-start: 0;
|
|
62
|
+
z-index: 2;
|
|
63
|
+
}
|
|
64
|
+
.dg thead th.dg-pin-start { z-index: 4; }
|
|
65
|
+
|
|
66
|
+
.dg :is(th, td).dg-pin-start-2 {
|
|
67
|
+
position: sticky;
|
|
68
|
+
inset-inline-start: var(--dg-pin-start);
|
|
69
|
+
z-index: 2;
|
|
70
|
+
}
|
|
71
|
+
.dg thead th.dg-pin-start-2 { z-index: 4; }
|
|
72
|
+
|
|
73
|
+
.dg :is(th, td).dg-pin-end {
|
|
74
|
+
position: sticky;
|
|
75
|
+
inset-inline-end: 0;
|
|
76
|
+
z-index: 2;
|
|
77
|
+
}
|
|
78
|
+
.dg thead th.dg-pin-end { z-index: 4; }
|
|
79
|
+
|
|
80
|
+
/* The shadow only appears once the grid is actually scrolled sideways.
|
|
81
|
+
A box-shadow offset is physical, so the geometry lives here and 19-logical
|
|
82
|
+
mirrors it for RTL once, rather than once per branch below. */
|
|
83
|
+
.dg-wrap {
|
|
84
|
+
--dg-pin-shadow-start: 6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
|
|
85
|
+
--dg-pin-shadow-end: -6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
|
|
86
|
+
--dg-pin-clip-start: inset(0 -12px 0 0);
|
|
87
|
+
--dg-pin-clip-end: inset(0 0 0 -12px);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Ask the scroll container directly. No scroll handler, no class to keep in
|
|
91
|
+
sync, and the query is already logical so RTL needs nothing here. */
|
|
92
|
+
@supports (container-type: scroll-state) {
|
|
93
|
+
.dg-wrap { container-type: scroll-state; }
|
|
94
|
+
@container scroll-state(scrollable: inline-start) {
|
|
95
|
+
.dg :is(th, td).dg-pin-start:last-of-type,
|
|
96
|
+
.dg :is(th, td).dg-pin-start-2,
|
|
97
|
+
.dg :is(th, td).dg-pin-start:not(:has(~ .dg-pin-start-2)) {
|
|
98
|
+
box-shadow: var(--dg-pin-shadow-start);
|
|
99
|
+
clip-path: var(--dg-pin-clip-start);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
@container scroll-state(scrollable: inline-end) {
|
|
103
|
+
.dg :is(th, td).dg-pin-end {
|
|
104
|
+
box-shadow: var(--dg-pin-shadow-end);
|
|
105
|
+
clip-path: var(--dg-pin-clip-end);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Where it is missing, Grid.shadows() in deck.js toggles these two classes. */
|
|
111
|
+
@supports not (container-type: scroll-state) {
|
|
112
|
+
.dg-wrap.is-scrolled-x .dg :is(th, td).dg-pin-start:last-of-type,
|
|
113
|
+
.dg-wrap.is-scrolled-x .dg :is(th, td).dg-pin-start-2,
|
|
114
|
+
.dg-wrap.is-scrolled-x .dg :is(th, td).dg-pin-start:not(:has(~ .dg-pin-start-2)) {
|
|
115
|
+
box-shadow: var(--dg-pin-shadow-start);
|
|
116
|
+
clip-path: var(--dg-pin-clip-start);
|
|
117
|
+
}
|
|
118
|
+
.dg-wrap.is-scrolled-end .dg :is(th, td).dg-pin-end {
|
|
119
|
+
box-shadow: var(--dg-pin-shadow-end);
|
|
120
|
+
clip-path: var(--dg-pin-clip-end);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* --- Rows ---------------------------------------------------------------- */
|
|
125
|
+
.dg tbody tr:hover :is(td) { background: var(--surface-hover); }
|
|
126
|
+
.dg tbody tr:last-child td { border-block-end: 0; }
|
|
127
|
+
|
|
128
|
+
.dg tbody tr[aria-selected="true"] td {
|
|
129
|
+
background: var(--brand-soft);
|
|
130
|
+
color: var(--brand-soft-text);
|
|
131
|
+
}
|
|
132
|
+
.dg tbody tr[aria-selected="true"]:hover td {
|
|
133
|
+
background: color-mix(in oklab, var(--brand-soft) 80%, var(--brand-200));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Zebra is opt-in, and it never fights the pinned column background */
|
|
137
|
+
.dg-zebra tbody tr:nth-child(even) td { background: var(--surface-2); }
|
|
138
|
+
.dg-zebra tbody tr:nth-child(even):hover td { background: var(--surface-hover); }
|
|
139
|
+
|
|
140
|
+
.dg tbody tr.is-new td { animation: dg-flash 1.4s var(--ease-out); }
|
|
141
|
+
@keyframes dg-flash { from { background: var(--good-100); } }
|
|
142
|
+
|
|
143
|
+
/* --- Cell variants ------------------------------------------------------- */
|
|
144
|
+
/* A numeric column in the data grid: aligned to the end edge, and sorted
|
|
145
|
+
numerically when the header carries data-sort="num". */
|
|
146
|
+
/* A numeric column: aligned to the end edge, and sorted numerically when
|
|
147
|
+
the header carries data-sort="num". */
|
|
148
|
+
.dg .dg-num { text-align: end; }
|
|
149
|
+
/* A centred column. */
|
|
150
|
+
.dg .dg-center { text-align: center; }
|
|
151
|
+
.dg .dg-wrap-text { white-space: normal; min-inline-size: 16rem; }
|
|
152
|
+
.dg .dg-tight { padding-inline: var(--space-2); }
|
|
153
|
+
/* The selection column. 44px wide, and the one place Deck strips .check of
|
|
154
|
+
its min-block-size — a dense grid of 34px rows cannot also have 44px
|
|
155
|
+
checkboxes. The checkbox still needs its own accessible name. */
|
|
156
|
+
.dg .dg-check { inline-size: 44px; padding-inline: 0; text-align: center; }
|
|
157
|
+
.dg .dg-check .check { justify-content: center; min-block-size: auto; padding: 0; }
|
|
158
|
+
/* A column shrunk to its content, for a row menu or a pair of buttons. */
|
|
159
|
+
.dg .dg-actions { inline-size: 1%; padding-inline: var(--space-2); }
|
|
160
|
+
|
|
161
|
+
/* --- Density ------------------------------------------------------------- */
|
|
162
|
+
/* 34px rows and smaller text, for scanning many rows at once. */
|
|
163
|
+
.dg-compact { --dg-row-h: 34px; font-size: var(--text-xs); }
|
|
164
|
+
.dg-compact :is(th, td) { padding-inline: var(--space-3); }
|
|
165
|
+
/* 56px rows, for a grid with two lines of content per cell. */
|
|
166
|
+
.dg-comfy { --dg-row-h: 56px; }
|
|
167
|
+
|
|
168
|
+
/* --- Sortable headers ---------------------------------------------------- */
|
|
169
|
+
/* The button deck.js builds inside a th[data-sort]. The styling keys off
|
|
170
|
+
th[aria-sort], so the arrow direction and the announced sort state are
|
|
171
|
+
the same value and cannot drift apart. */
|
|
172
|
+
.dg-sort {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: var(--space-2);
|
|
176
|
+
inline-size: 100%;
|
|
177
|
+
block-size: 100%;
|
|
178
|
+
color: inherit;
|
|
179
|
+
font: inherit;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
user-select: none;
|
|
182
|
+
}
|
|
183
|
+
.dg-num .dg-sort { justify-content: flex-end; }
|
|
184
|
+
.dg-sort:hover { color: var(--text); }
|
|
185
|
+
.dg-sort .dg-sort-icon {
|
|
186
|
+
inline-size: 14px;
|
|
187
|
+
block-size: 14px;
|
|
188
|
+
opacity: 0;
|
|
189
|
+
flex: 0 0 auto;
|
|
190
|
+
transition: opacity var(--dur-1) var(--ease-out), rotate var(--dur-2) var(--ease-out);
|
|
191
|
+
}
|
|
192
|
+
.dg-sort:hover .dg-sort-icon { opacity: .4; }
|
|
193
|
+
th[aria-sort] .dg-sort { color: var(--brand); }
|
|
194
|
+
th[aria-sort] .dg-sort-icon { opacity: 1; }
|
|
195
|
+
th[aria-sort="descending"] .dg-sort-icon { rotate: 180deg; }
|
|
196
|
+
|
|
197
|
+
/* Column resize grip. deck.js writes the width to the th. */
|
|
198
|
+
.dg-resize {
|
|
199
|
+
position: absolute;
|
|
200
|
+
inset-block: 0;
|
|
201
|
+
inset-inline-end: -3px;
|
|
202
|
+
inline-size: 7px;
|
|
203
|
+
cursor: col-resize;
|
|
204
|
+
z-index: 1;
|
|
205
|
+
touch-action: none;
|
|
206
|
+
}
|
|
207
|
+
.dg-resize::after {
|
|
208
|
+
content: "";
|
|
209
|
+
position: absolute;
|
|
210
|
+
inset-block: 8px;
|
|
211
|
+
inset-inline-start: 3px;
|
|
212
|
+
inline-size: 1px;
|
|
213
|
+
background: var(--line-strong);
|
|
214
|
+
opacity: 0;
|
|
215
|
+
transition: opacity var(--dur-1) var(--ease-out);
|
|
216
|
+
}
|
|
217
|
+
.dg-resize:hover::after, .dg-wrap.is-resizing .dg-resize::after { opacity: 1; background: var(--brand); }
|
|
218
|
+
.dg thead th { position: sticky; }
|
|
219
|
+
.dg thead th:has(.dg-resize) { position: sticky; }
|
|
220
|
+
|
|
221
|
+
/* --- Footer totals ------------------------------------------------------- */
|
|
222
|
+
.dg tfoot td {
|
|
223
|
+
position: sticky;
|
|
224
|
+
inset-block-end: 0;
|
|
225
|
+
z-index: 3;
|
|
226
|
+
background: var(--surface-2);
|
|
227
|
+
border-block-start: 1px solid var(--line-strong);
|
|
228
|
+
border-block-end: 0;
|
|
229
|
+
font-weight: 640;
|
|
230
|
+
color: var(--text);
|
|
231
|
+
}
|
|
232
|
+
.dg tfoot td.dg-pin-start { z-index: 4; }
|
|
233
|
+
|
|
234
|
+
/* --- Toolbar and status bar ---------------------------------------------- */
|
|
235
|
+
.dg-toolbar {
|
|
236
|
+
display: flex;
|
|
237
|
+
align-items: center;
|
|
238
|
+
gap: var(--space-2);
|
|
239
|
+
flex-wrap: wrap;
|
|
240
|
+
padding-block-end: var(--space-3);
|
|
241
|
+
}
|
|
242
|
+
.dg-toolbar .search { flex: 1 1 14rem; }
|
|
243
|
+
|
|
244
|
+
.dg-statusbar {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: var(--space-3);
|
|
248
|
+
flex-wrap: wrap;
|
|
249
|
+
padding-block-start: var(--space-3);
|
|
250
|
+
font-size: var(--text-sm);
|
|
251
|
+
color: var(--text-muted);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Selection bar that slides in once rows are checked */
|
|
255
|
+
.dg-selection {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
gap: var(--space-3);
|
|
259
|
+
padding: var(--space-2) var(--space-3);
|
|
260
|
+
border-radius: var(--r-sm);
|
|
261
|
+
background: var(--brand-soft);
|
|
262
|
+
color: var(--brand-soft-text);
|
|
263
|
+
font-size: var(--text-sm);
|
|
264
|
+
font-weight: 560;
|
|
265
|
+
animation: dk-combo-in var(--dur-2) var(--ease-out);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* --- Loading and empty --------------------------------------------------- */
|
|
269
|
+
.dg-wrap.is-loading { pointer-events: none; }
|
|
270
|
+
.dg-wrap.is-loading .dg tbody { opacity: .45; }
|
|
271
|
+
.dg-wrap.is-loading::after {
|
|
272
|
+
content: "";
|
|
273
|
+
position: absolute;
|
|
274
|
+
inset-block-start: 0;
|
|
275
|
+
inset-inline: 0;
|
|
276
|
+
block-size: 2px;
|
|
277
|
+
background: linear-gradient(90deg, transparent, var(--brand-500), transparent);
|
|
278
|
+
background-size: 40% 100%;
|
|
279
|
+
background-repeat: no-repeat;
|
|
280
|
+
animation: dg-bar 1.1s var(--ease-in-out) infinite;
|
|
281
|
+
z-index: 5;
|
|
282
|
+
}
|
|
283
|
+
@keyframes dg-bar {
|
|
284
|
+
from { background-position: -40% 0; }
|
|
285
|
+
to { background-position: 140% 0; }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* The row that holds an empty state, with its height and padding released
|
|
289
|
+
so the state can be any size. */
|
|
290
|
+
.dg-empty td { block-size: auto; padding: 0; }
|
|
291
|
+
|
|
292
|
+
/* --- Phone: swap to cards ------------------------------------------------
|
|
293
|
+
Add .dg-cards and put the column name in data-label on each td.
|
|
294
|
+
---------------------------------------------------------------------- */
|
|
295
|
+
@media (max-width: 44rem) {
|
|
296
|
+
.dg-cards-wrap { max-block-size: none; overflow: visible; border: 0; background: none; }
|
|
297
|
+
.dg-cards thead, .dg-cards tfoot { display: none; }
|
|
298
|
+
/* Below a breakpoint, restacks each row into a labelled card, taking the
|
|
299
|
+
column name from data-label on the cell. Costs the column alignment that
|
|
300
|
+
makes a grid comparable; prefer .dg-cq, which asks the container rather
|
|
301
|
+
than the viewport. */
|
|
302
|
+
.dg-cards, .dg-cards tbody, .dg-cards tr, .dg-cards td { display: block; inline-size: 100%; }
|
|
303
|
+
.dg-cards tr {
|
|
304
|
+
background: var(--surface);
|
|
305
|
+
border: 1px solid var(--line);
|
|
306
|
+
border-radius: var(--r-md);
|
|
307
|
+
margin-block-end: var(--space-3);
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
}
|
|
310
|
+
.dg-cards td {
|
|
311
|
+
display: flex;
|
|
312
|
+
align-items: center;
|
|
313
|
+
justify-content: space-between;
|
|
314
|
+
gap: var(--space-4);
|
|
315
|
+
block-size: auto;
|
|
316
|
+
min-block-size: var(--tap);
|
|
317
|
+
padding: var(--space-2) var(--space-4);
|
|
318
|
+
white-space: normal;
|
|
319
|
+
text-align: end;
|
|
320
|
+
position: static !important;
|
|
321
|
+
box-shadow: none !important;
|
|
322
|
+
clip-path: none !important;
|
|
323
|
+
}
|
|
324
|
+
.dg-cards td::before {
|
|
325
|
+
content: attr(data-label);
|
|
326
|
+
font-size: var(--text-xs);
|
|
327
|
+
font-weight: 620;
|
|
328
|
+
color: var(--text-muted);
|
|
329
|
+
text-align: start;
|
|
330
|
+
flex: 0 0 auto;
|
|
331
|
+
}
|
|
332
|
+
.dg-cards td:empty { display: none; }
|
|
333
|
+
.dg-cards td.dg-num { text-align: end; }
|
|
334
|
+
}
|
|
335
|
+
}
|