@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,235 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 04 Layout
|
|
3
|
+
Composable primitives. You lay out with about eight classes, not forty.
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
@layer deck.layout {
|
|
7
|
+
|
|
8
|
+
/* Centres content and caps it at --container, with the responsive page
|
|
9
|
+
gutter already applied. Nest one inside another and the inner one adds
|
|
10
|
+
a second gutter, so use .container-full instead. */
|
|
11
|
+
.container {
|
|
12
|
+
inline-size: 100%;
|
|
13
|
+
max-inline-size: var(--container, 76rem);
|
|
14
|
+
margin-inline: auto;
|
|
15
|
+
padding-inline: var(--space-gutter);
|
|
16
|
+
}
|
|
17
|
+
/* A single column of prose: about 70 characters at the body size,
|
|
18
|
+
which is inside the maximum line length WCAG recommends. */
|
|
19
|
+
.container-sm { --container: 40rem; }
|
|
20
|
+
/* An article with a rail, or a form. */
|
|
21
|
+
.container-md { --container: 56rem; }
|
|
22
|
+
/* The same 76rem as the bare .container, named. Use it when a page
|
|
23
|
+
should say which width it means rather than rely on the default. */
|
|
24
|
+
.container-lg { --container: 76rem; }
|
|
25
|
+
/* A wide dashboard or a data grid. */
|
|
26
|
+
.container-xl { --container: 90rem; }
|
|
27
|
+
/* Keeps the page gutter, drops the max width. This is what to nest
|
|
28
|
+
inside another container — a second .container would apply the
|
|
29
|
+
gutter twice. */
|
|
30
|
+
.container-full { --container: none; }
|
|
31
|
+
|
|
32
|
+
/* --- The spacing scale ----------------------------------------------------
|
|
33
|
+
Every spacing utility in Deck uses the same seven steps:
|
|
34
|
+
|
|
35
|
+
0 1 2 3 4 6 8
|
|
36
|
+
0 .25rem .5rem .75rem 1rem 1.5rem 2rem
|
|
37
|
+
|
|
38
|
+
.stack, .cluster, .gap-*, the padding utilities and the margin utilities
|
|
39
|
+
all implement exactly these, so a step that exists in one family exists in
|
|
40
|
+
all of them. There is deliberately no 5: at 1.25rem it is indistinguishable
|
|
41
|
+
from 4 and 6 in a real layout, and a half step between two neighbours is
|
|
42
|
+
the step people reach for when they are not sure, which is how .stack-5
|
|
43
|
+
came to be used in the demo when no such class existed.
|
|
44
|
+
|
|
45
|
+
The --space-* tokens keep their full range (0 through 24). Tokens are the
|
|
46
|
+
palette a component composes from; the utilities are the smaller set you
|
|
47
|
+
author markup with. Those are different jobs, so they are different sizes.
|
|
48
|
+
---------------------------------------------------------------------- */
|
|
49
|
+
|
|
50
|
+
/* Vertical rhythm without margin collapse surprises */
|
|
51
|
+
.stack {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: var(--gap, var(--space-4));
|
|
55
|
+
}
|
|
56
|
+
/* No gap. For two elements that must touch. */
|
|
57
|
+
.stack-0 { --gap: 0; }
|
|
58
|
+
/* A hairline. A label and the control it names, when the two read as
|
|
59
|
+
one thing. */
|
|
60
|
+
.stack-1 { --gap: var(--space-1); }
|
|
61
|
+
/* Tight vertical rhythm, for a label and its control or a title and its
|
|
62
|
+
subtitle. */
|
|
63
|
+
.stack-2 { --gap: var(--space-2); }
|
|
64
|
+
/* The default rhythm for related blocks inside a card or a section. */
|
|
65
|
+
.stack-3 { --gap: var(--space-3); }
|
|
66
|
+
/* The default, named. Same as .stack with no step class. */
|
|
67
|
+
.stack-4 { --gap: var(--space-4); }
|
|
68
|
+
/* Loose vertical rhythm, for separating whole sections of a page. */
|
|
69
|
+
.stack-6 { --gap: var(--space-6); }
|
|
70
|
+
/* Very loose, for whole sections of a page where .section is too much. */
|
|
71
|
+
.stack-8 { --gap: var(--space-8); }
|
|
72
|
+
|
|
73
|
+
/* Horizontal group that wraps instead of overflowing on a phone */
|
|
74
|
+
.cluster {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-wrap: wrap;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: var(--gap, var(--space-3));
|
|
79
|
+
}
|
|
80
|
+
/* Kept as the readable name for step 2, which is what most clusters want */
|
|
81
|
+
.cluster-tight { --gap: var(--space-2); }
|
|
82
|
+
/* Packs the row against the end edge, which follows the writing
|
|
83
|
+
direction rather than always being the right. */
|
|
84
|
+
.cluster-end { justify-content: flex-end; }
|
|
85
|
+
/* Centres the row. */
|
|
86
|
+
.cluster-center { justify-content: center; }
|
|
87
|
+
/* First item to one end, last to the other. Beware of combining it
|
|
88
|
+
with wrapping: each line is justified separately, so a lone item on
|
|
89
|
+
the second line jumps to the far end and looks stranded. .bar with
|
|
90
|
+
.push is the predictable version. */
|
|
91
|
+
.cluster-between { justify-content: space-between; }
|
|
92
|
+
|
|
93
|
+
/* Push the last child away from the rest */
|
|
94
|
+
.bar {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: var(--space-3);
|
|
98
|
+
}
|
|
99
|
+
.bar > .push { margin-inline-start: auto; }
|
|
100
|
+
|
|
101
|
+
/* Auto-fitting grid: no breakpoints needed, it responds to its own width */
|
|
102
|
+
.grid {
|
|
103
|
+
display: grid;
|
|
104
|
+
gap: var(--gap, var(--space-4));
|
|
105
|
+
grid-template-columns: repeat(auto-fit, minmax(min(var(--min, 17rem), 100%), 1fr));
|
|
106
|
+
}
|
|
107
|
+
/* Two-ish columns: an auto-fit grid with a 24rem floor. Identical in
|
|
108
|
+
effect to .grid-wide, but it writes grid-template-columns directly
|
|
109
|
+
rather than setting --min, so --min cannot adjust it afterwards.
|
|
110
|
+
Prefer .grid-wide. */
|
|
111
|
+
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr)); }
|
|
112
|
+
/* Narrows the auto-fit column floor to 12rem, so the grid packs more
|
|
113
|
+
columns in before it wraps. */
|
|
114
|
+
.grid-tight { --min: 12rem; }
|
|
115
|
+
/* Widens the auto-fit column floor to 24rem, so the grid packs fewer,
|
|
116
|
+
wider columns before it wraps. For cards with a paragraph in them. */
|
|
117
|
+
.grid-wide { --min: 24rem; }
|
|
118
|
+
/* Exactly two equal columns at every width, including a phone. Only
|
|
119
|
+
for a count that is part of the meaning — a 2x2 comparison. It does
|
|
120
|
+
not reflow, so it does not satisfy the reflow criterion on its own. */
|
|
121
|
+
.grid-fixed-2 { grid-template-columns: 1fr 1fr; }
|
|
122
|
+
/* Exactly three equal columns at every width. See .grid-fixed-2. */
|
|
123
|
+
.grid-fixed-3 { grid-template-columns: repeat(3, 1fr); }
|
|
124
|
+
/* Exactly four equal columns at every width. On a phone that is four
|
|
125
|
+
70px columns; reach for .grid with a --min instead. */
|
|
126
|
+
.grid-fixed-4 { grid-template-columns: repeat(4, 1fr); }
|
|
127
|
+
|
|
128
|
+
/* Content + rail. Collapses to one column below the width it needs. */
|
|
129
|
+
.split {
|
|
130
|
+
display: grid;
|
|
131
|
+
gap: var(--gap, var(--space-6));
|
|
132
|
+
grid-template-columns: 1fr;
|
|
133
|
+
}
|
|
134
|
+
/* A grid item's min-inline-size is auto, so a child that cannot shrink — a
|
|
135
|
+
form row, a wide table, a long code block — makes the whole column wider
|
|
136
|
+
than its track and pushes the page sideways. This is the single most
|
|
137
|
+
common grid bug there is; the two-column form in the demo overflowed a
|
|
138
|
+
375px viewport by 16px until this line existed. */
|
|
139
|
+
.split > * { min-inline-size: 0; }
|
|
140
|
+
@media (min-width: 64rem) {
|
|
141
|
+
.split { grid-template-columns: minmax(0, 1fr) var(--rail, 20rem); }
|
|
142
|
+
/* Puts the rail on the starting edge without touching markup order, so
|
|
143
|
+
the main column stays first for a screen reader and for the keyboard.
|
|
144
|
+
Use it for supplementary navigation; not for a rail holding the first
|
|
145
|
+
thing a reader needs. */
|
|
146
|
+
.split-rail-start { grid-template-columns: var(--rail, 20rem) minmax(0, 1fr); }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Center a thing with a max width and no side effects */
|
|
150
|
+
.center {
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
align-items: center;
|
|
154
|
+
text-align: center;
|
|
155
|
+
gap: var(--gap, var(--space-4));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Vertical page rhythm: a block of --space-section, which is a clamp that
|
|
159
|
+
grows with the viewport. For spacing inside a component reach for
|
|
160
|
+
.stack instead. */
|
|
161
|
+
.section { padding-block: var(--space-section); }
|
|
162
|
+
|
|
163
|
+
/* Everything stays on one line and scrolls sideways — cards, chips, dates */
|
|
164
|
+
.scroller {
|
|
165
|
+
display: flex;
|
|
166
|
+
gap: var(--gap, var(--space-3));
|
|
167
|
+
overflow-x: auto;
|
|
168
|
+
overscroll-behavior-inline: contain;
|
|
169
|
+
scroll-snap-type: x proximity;
|
|
170
|
+
scrollbar-width: none;
|
|
171
|
+
padding-block-end: var(--space-2);
|
|
172
|
+
margin-inline: calc(var(--space-gutter) * -1);
|
|
173
|
+
padding-inline: var(--space-gutter);
|
|
174
|
+
}
|
|
175
|
+
.scroller::-webkit-scrollbar { display: none; }
|
|
176
|
+
.scroller > * { scroll-snap-align: start; flex: 0 0 auto; }
|
|
177
|
+
|
|
178
|
+
/* A sticky header. Where scroll-state container queries are supported
|
|
179
|
+
the blur and shadow live on an ::after that is transparent until the
|
|
180
|
+
header actually sticks, so in flow it looks like part of the page.
|
|
181
|
+
The treatment rides on a pseudo-element because a scroll-state
|
|
182
|
+
container can style its descendants but not itself. */
|
|
183
|
+
.sticky-top {
|
|
184
|
+
position: sticky;
|
|
185
|
+
inset-block-start: 0;
|
|
186
|
+
z-index: var(--z-sticky);
|
|
187
|
+
background: color-mix(in oklab, var(--bg) 82%, transparent);
|
|
188
|
+
backdrop-filter: saturate(1.6) blur(12px);
|
|
189
|
+
-webkit-backdrop-filter: saturate(1.6) blur(12px);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Sitting in flow it needs no treatment at all; it earns the blur and the
|
|
193
|
+
shadow at the moment it actually sticks. A scroll-state container cannot
|
|
194
|
+
style itself, so the treatment rides on ::after, which is inside it. */
|
|
195
|
+
@supports (container-type: scroll-state) {
|
|
196
|
+
.sticky-top {
|
|
197
|
+
container-type: scroll-state;
|
|
198
|
+
background: none;
|
|
199
|
+
backdrop-filter: none;
|
|
200
|
+
-webkit-backdrop-filter: none;
|
|
201
|
+
}
|
|
202
|
+
.sticky-top::after {
|
|
203
|
+
content: "";
|
|
204
|
+
position: absolute;
|
|
205
|
+
inset: 0;
|
|
206
|
+
z-index: -1;
|
|
207
|
+
opacity: 0;
|
|
208
|
+
background: color-mix(in oklab, var(--bg) 82%, transparent);
|
|
209
|
+
backdrop-filter: saturate(1.6) blur(12px);
|
|
210
|
+
-webkit-backdrop-filter: saturate(1.6) blur(12px);
|
|
211
|
+
transition: opacity var(--dur-2) var(--ease-out);
|
|
212
|
+
}
|
|
213
|
+
@container scroll-state(stuck: block-start) {
|
|
214
|
+
.sticky-top::after { opacity: 1; box-shadow: var(--shadow-2); }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Header, content, footer in a 100dvh grid whose middle row takes the
|
|
219
|
+
remaining height — the sticky footer, without the hacks. dvh rather
|
|
220
|
+
than vh so it tracks a phone browser hiding its chrome. It expects
|
|
221
|
+
exactly three children. */
|
|
222
|
+
.app-shell {
|
|
223
|
+
display: grid;
|
|
224
|
+
min-block-size: 100dvh;
|
|
225
|
+
grid-template-rows: auto 1fr auto;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Aspect boxes for media */
|
|
229
|
+
.ratio-16x9 { aspect-ratio: 16 / 9; object-fit: cover; inline-size: 100%; }
|
|
230
|
+
.ratio-4x3 { aspect-ratio: 4 / 3; object-fit: cover; inline-size: 100%; }
|
|
231
|
+
.ratio-1x1 { aspect-ratio: 1; object-fit: cover; inline-size: 100%; }
|
|
232
|
+
|
|
233
|
+
/* Container queries: components respond to their slot, not the viewport */
|
|
234
|
+
.cq { container-type: inline-size; }
|
|
235
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 19 Logical properties and direction
|
|
3
|
+
The framework is written in logical properties end to end: inline-size,
|
|
4
|
+
block-size, inset-inline-start, padding-block, margin-inline, border-start-
|
|
5
|
+
start-radius. That means Deck already lays out correctly in Arabic, Hebrew,
|
|
6
|
+
Urdu, and Farsi with nothing but dir="rtl" on the html element.
|
|
7
|
+
|
|
8
|
+
What logical properties cannot fix by themselves is content: icons that
|
|
9
|
+
point somewhere, characters that are inherently one-directional, and the few
|
|
10
|
+
places where a physical direction is genuinely meant. That is what this file
|
|
11
|
+
handles.
|
|
12
|
+
========================================================================== */
|
|
13
|
+
|
|
14
|
+
@layer deck.utilities {
|
|
15
|
+
|
|
16
|
+
/* --- Logical spacing utilities -------------------------------------------
|
|
17
|
+
The short physical-looking names in the utility layer (.mt-4, .px-3) are
|
|
18
|
+
already logical underneath — .mt-4 sets margin-block-start. These are the
|
|
19
|
+
explicitly named versions for when the distinction matters in review.
|
|
20
|
+
---------------------------------------------------------------------- */
|
|
21
|
+
.mis-auto { margin-inline-start: auto; }
|
|
22
|
+
.mie-auto { margin-inline-end: auto; }
|
|
23
|
+
|
|
24
|
+
/* Same seven steps as every other spacing family: 0 1 2 3 4 6 8. */
|
|
25
|
+
.mis-0 { margin-inline-start: 0; }
|
|
26
|
+
.mis-1 { margin-inline-start: var(--space-1); }
|
|
27
|
+
.mis-2 { margin-inline-start: var(--space-2); }
|
|
28
|
+
.mis-3 { margin-inline-start: var(--space-3); }
|
|
29
|
+
.mis-4 { margin-inline-start: var(--space-4); }
|
|
30
|
+
.mis-6 { margin-inline-start: var(--space-6); }
|
|
31
|
+
.mis-8 { margin-inline-start: var(--space-8); }
|
|
32
|
+
.mie-0 { margin-inline-end: 0; }
|
|
33
|
+
.mie-1 { margin-inline-end: var(--space-1); }
|
|
34
|
+
.mie-2 { margin-inline-end: var(--space-2); }
|
|
35
|
+
.mie-3 { margin-inline-end: var(--space-3); }
|
|
36
|
+
.mie-4 { margin-inline-end: var(--space-4); }
|
|
37
|
+
.mie-6 { margin-inline-end: var(--space-6); }
|
|
38
|
+
.mie-8 { margin-inline-end: var(--space-8); }
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
.pis-0 { padding-inline-start: 0; }
|
|
42
|
+
.pis-1 { padding-inline-start: var(--space-1); }
|
|
43
|
+
.pis-2 { padding-inline-start: var(--space-2); }
|
|
44
|
+
.pis-3 { padding-inline-start: var(--space-3); }
|
|
45
|
+
.pis-4 { padding-inline-start: var(--space-4); }
|
|
46
|
+
.pis-6 { padding-inline-start: var(--space-6); }
|
|
47
|
+
.pis-8 { padding-inline-start: var(--space-8); }
|
|
48
|
+
.pie-0 { padding-inline-end: 0; }
|
|
49
|
+
.pie-1 { padding-inline-end: var(--space-1); }
|
|
50
|
+
.pie-2 { padding-inline-end: var(--space-2); }
|
|
51
|
+
.pie-3 { padding-inline-end: var(--space-3); }
|
|
52
|
+
.pie-4 { padding-inline-end: var(--space-4); }
|
|
53
|
+
.pie-6 { padding-inline-end: var(--space-6); }
|
|
54
|
+
.pie-8 { padding-inline-end: var(--space-8); }
|
|
55
|
+
|
|
56
|
+
.pbs-0 { padding-block-start: 0; }
|
|
57
|
+
.pbs-1 { padding-block-start: var(--space-1); }
|
|
58
|
+
.pbs-2 { padding-block-start: var(--space-2); }
|
|
59
|
+
.pbs-3 { padding-block-start: var(--space-3); }
|
|
60
|
+
.pbs-4 { padding-block-start: var(--space-4); }
|
|
61
|
+
.pbs-6 { padding-block-start: var(--space-6); }
|
|
62
|
+
.pbs-8 { padding-block-start: var(--space-8); }
|
|
63
|
+
.pbe-0 { padding-block-end: 0; }
|
|
64
|
+
.pbe-1 { padding-block-end: var(--space-1); }
|
|
65
|
+
.pbe-2 { padding-block-end: var(--space-2); }
|
|
66
|
+
.pbe-3 { padding-block-end: var(--space-3); }
|
|
67
|
+
.pbe-4 { padding-block-end: var(--space-4); }
|
|
68
|
+
.pbe-6 { padding-block-end: var(--space-6); }
|
|
69
|
+
.pbe-8 { padding-block-end: var(--space-8); }
|
|
70
|
+
|
|
71
|
+
.bis { border-inline-start: 1px solid var(--line); }
|
|
72
|
+
.bie { border-inline-end: 1px solid var(--line); }
|
|
73
|
+
|
|
74
|
+
.min-is-0 { min-inline-size: 0; }
|
|
75
|
+
|
|
76
|
+
.inset-is-0 { inset-inline-start: 0; }
|
|
77
|
+
.inset-ie-0 { inset-inline-end: 0; }
|
|
78
|
+
.inset-bs-0 { inset-block-start: 0; }
|
|
79
|
+
.inset-be-0 { inset-block-end: 0; }
|
|
80
|
+
|
|
81
|
+
/* Radius on the leading and trailing edges, not left and right */
|
|
82
|
+
.r-start { border-start-start-radius: var(--r-md); border-end-start-radius: var(--r-md); }
|
|
83
|
+
.r-end { border-start-end-radius: var(--r-md); border-end-end-radius: var(--r-md); }
|
|
84
|
+
|
|
85
|
+
/* --- Writing mode --------------------------------------------------------
|
|
86
|
+
Vertical text for a table header that would otherwise need a 40 character
|
|
87
|
+
column, or for a sidebar label.
|
|
88
|
+
---------------------------------------------------------------------- */
|
|
89
|
+
.writing-vertical {
|
|
90
|
+
writing-mode: vertical-rl;
|
|
91
|
+
text-orientation: mixed;
|
|
92
|
+
}
|
|
93
|
+
.writing-vertical-lr { writing-mode: vertical-lr; }
|
|
94
|
+
.writing-upright { text-orientation: upright; letter-spacing: .1em; }
|
|
95
|
+
.writing-sideways { writing-mode: sideways-lr; }
|
|
96
|
+
.writing-normal { writing-mode: horizontal-tb; }
|
|
97
|
+
|
|
98
|
+
/* A rotated column header that still measures and aligns correctly */
|
|
99
|
+
.th-vertical {
|
|
100
|
+
writing-mode: vertical-rl;
|
|
101
|
+
rotate: 180deg;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
block-size: 8rem;
|
|
104
|
+
padding-block: var(--space-3);
|
|
105
|
+
text-align: start;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* --- Explicit direction --------------------------------------------------
|
|
109
|
+
Sometimes a value has one true direction regardless of the page. An order
|
|
110
|
+
number, an account number, a URL, and a code block read left to right even
|
|
111
|
+
on an Arabic page. Isolate them so they do not scramble the text around
|
|
112
|
+
them.
|
|
113
|
+
---------------------------------------------------------------------- */
|
|
114
|
+
.dir-ltr { direction: ltr; unicode-bidi: isolate; }
|
|
115
|
+
.dir-rtl { direction: rtl; unicode-bidi: isolate; }
|
|
116
|
+
.bidi-isolate { unicode-bidi: isolate; }
|
|
117
|
+
.bidi-plaintext { unicode-bidi: plaintext; }
|
|
118
|
+
|
|
119
|
+
/* Identifiers read left to right in every language: an order number, a commit
|
|
120
|
+
hash, an API key, a part code. Keep them LTR wherever they appear, and keep
|
|
121
|
+
them from scrambling the text around them. */
|
|
122
|
+
.mono, code, kbd, samp, .nums { unicode-bidi: isolate; }
|
|
123
|
+
.code-ltr { direction: ltr; unicode-bidi: isolate; text-align: start; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@layer deck.rtl {
|
|
127
|
+
|
|
128
|
+
/* --- Icons that point ----------------------------------------------------
|
|
129
|
+
An arrow, a chevron, a reply glyph, and a back button all mean "the
|
|
130
|
+
direction of reading". They must mirror. A checkmark, a clock, a wrench,
|
|
131
|
+
and a logo must not. Mark the ones that point.
|
|
132
|
+
---------------------------------------------------------------------- */
|
|
133
|
+
[dir="rtl"] .mirror-rtl { scale: -1 1; }
|
|
134
|
+
|
|
135
|
+
/* The pointing icons in Deck's own sprite, mirrored automatically */
|
|
136
|
+
[dir="rtl"] :is(
|
|
137
|
+
.icon:has(use[href$="#chevron-left"]),
|
|
138
|
+
.icon:has(use[href$="#chevron-right"]),
|
|
139
|
+
.icon:has(use[href$="#arrow-left"]),
|
|
140
|
+
.icon:has(use[href$="#arrow-right"]),
|
|
141
|
+
.icon:has(use[href$="#arrow-up-right"]),
|
|
142
|
+
.icon:has(use[href$="#external"]),
|
|
143
|
+
.icon:has(use[href$="#log-out"]),
|
|
144
|
+
.icon:has(use[href$="#send"]),
|
|
145
|
+
.icon:has(use[href$="#trend-up"]),
|
|
146
|
+
.icon:has(use[href$="#trend-down"])
|
|
147
|
+
) { scale: -1 1; }
|
|
148
|
+
|
|
149
|
+
/* Things that must never mirror, even inside a flipped ancestor */
|
|
150
|
+
[dir="rtl"] .no-flip { scale: 1 1; }
|
|
151
|
+
|
|
152
|
+
/* --- Direction-aware motion ---------------------------------------------
|
|
153
|
+
The entrance animations move toward the reader. In RTL the start edge is
|
|
154
|
+
on the right, so the keyframes flip too.
|
|
155
|
+
---------------------------------------------------------------------- */
|
|
156
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
157
|
+
[dir="rtl"] .enter-start { animation-name: dk-in-start-rtl; }
|
|
158
|
+
[dir="rtl"] .enter-end { animation-name: dk-in-end-rtl; }
|
|
159
|
+
@keyframes dk-in-start-rtl { from { opacity: 0; translate: calc(var(--travel) * 1.6) 0; } }
|
|
160
|
+
@keyframes dk-in-end-rtl { from { opacity: 0; translate: calc(var(--travel) * -1.6) 0; } }
|
|
161
|
+
|
|
162
|
+
[dir="rtl"] .icon-follow { transition: translate var(--dur-2) var(--ease-spring); }
|
|
163
|
+
[dir="rtl"] .btn:hover .icon-follow,
|
|
164
|
+
[dir="rtl"] .list-row:hover .icon-follow,
|
|
165
|
+
[dir="rtl"] .card-link:hover .icon-follow { translate: -3px 0; }
|
|
166
|
+
|
|
167
|
+
[dir="rtl"] .sweep { background-position: 100% 100%; }
|
|
168
|
+
[dir="rtl"] .marquee-track { animation-name: dk-marquee-rtl; }
|
|
169
|
+
@keyframes dk-marquee-rtl { to { translate: 100% 0; } }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* --- Components that hard-code a direction ------------------------------- */
|
|
173
|
+
|
|
174
|
+
/* The select arrow is drawn with background-position from the right edge */
|
|
175
|
+
[dir="rtl"] .select {
|
|
176
|
+
background-position: 19px calc(50% + 1px), 14px calc(50% + 1px);
|
|
177
|
+
background-image: linear-gradient(135deg, transparent 50%, currentColor 50%),
|
|
178
|
+
linear-gradient(45deg, currentColor 50%, transparent 50%);
|
|
179
|
+
padding-inline: var(--space-10) var(--space-3);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Search icon sits on the start edge, which is the right in RTL */
|
|
183
|
+
[dir="rtl"] .search > .icon { inset-inline-start: var(--space-3); }
|
|
184
|
+
|
|
185
|
+
/* Pinned grid columns and their shadows. Which edge is "start" is already
|
|
186
|
+
logical in both branches of 12-datagrid; only the offsets are physical,
|
|
187
|
+
so mirroring the four variables flips the scroll-state path and the
|
|
188
|
+
class-based fallback at the same time. */
|
|
189
|
+
[dir="rtl"] .dg-wrap {
|
|
190
|
+
--dg-pin-shadow-start: -6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
|
|
191
|
+
--dg-pin-shadow-end: 6px 0 8px -6px oklch(20% .03 var(--hue-neutral) / .28);
|
|
192
|
+
--dg-pin-clip-start: inset(0 0 0 -12px);
|
|
193
|
+
--dg-pin-clip-end: inset(0 -12px 0 0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Switch knob travels toward the end edge */
|
|
197
|
+
[dir="rtl"] .switch input::after { translate: 0 0; }
|
|
198
|
+
[dir="rtl"] .switch input:checked::after { translate: -18px 0; }
|
|
199
|
+
|
|
200
|
+
/* Toast swipe and chart bars grow from the start edge, and transform-origin
|
|
201
|
+
has no logical form.
|
|
202
|
+
|
|
203
|
+
FINDINGS 59: .progress::-webkit-progress-value is also in this list and
|
|
204
|
+
does nothing. A pseudo-element is not valid inside :is(), and :is() parses
|
|
205
|
+
forgivingly -- the browser drops the invalid item and keeps the rest, so
|
|
206
|
+
this rule serialises as [dir="rtl"] :is(.chart-bar-fill, .toast-timer).
|
|
207
|
+
No warning, no failed rule, no visual difference. It is harmless here
|
|
208
|
+
because the progress fill animates inline-size rather than a transform,
|
|
209
|
+
and because the engine mirrors <progress> under dir="rtl" on its own.
|
|
210
|
+
|
|
211
|
+
Due for deletion. If a vendor pseudo-element ever does need a logical
|
|
212
|
+
flip, it has to be its own rule -- grouping it will fail silently. */
|
|
213
|
+
[dir="rtl"] :is(.chart-bar-fill, .progress::-webkit-progress-value, .toast-timer) {
|
|
214
|
+
transform-origin: right center;
|
|
215
|
+
}
|
|
216
|
+
[dir="rtl"] .scroll-progress { transform-origin: right center; }
|
|
217
|
+
|
|
218
|
+
/* Breadcrumb separator */
|
|
219
|
+
[dir="rtl"] .breadcrumb li + li::before { content: "\\\\"; }
|
|
220
|
+
|
|
221
|
+
/* Timeline rail and dots */
|
|
222
|
+
[dir="rtl"] .timeline-item:not(:last-child)::before { inset-inline-start: 10px; }
|
|
223
|
+
|
|
224
|
+
/* Mixed-direction pages: a block that is LTR inside an RTL document */
|
|
225
|
+
[dir="ltr"] [dir="rtl"], [dir="rtl"] [dir="ltr"] { unicode-bidi: isolate; }
|
|
226
|
+
}
|