@apliteni/apliteni-ui 0.70.0 → 0.72.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/guidelines/accessibility-floor.md +28 -0
- package/package.json +1 -1
- package/react/dist/index.css +85 -2
- package/src/index.css +3 -0
- package/src/inline.js +4 -0
- package/src/styles/callout.css +2 -2
- package/src/styles/card.css +11 -7
- package/src/styles/command-palette.css +7 -5
- package/src/styles/confirm.css +7 -5
- package/src/styles/drawer.css +8 -8
- package/src/styles/dropdown.css +7 -5
- package/src/styles/feedback.css +6 -2
- package/src/styles/layout.css +12 -7
- package/src/styles/success.css +4 -1
- package/src/styles/tap-zone.css +167 -0
- package/src/styles/tooltip.css +6 -4
- package/src/styles/topbar.css +13 -9
- package/src/tokens/tokens.css +46 -10
|
@@ -14,6 +14,34 @@
|
|
|
14
14
|
|
|
15
15
|
**Except:** WCAG 2.5.8 permits spacing, equivalent controls, inline text, user-agent sizes and essential presentation. Overlays must not reach neighbours.
|
|
16
16
|
|
|
17
|
+
## Tap target on a phone
|
|
18
|
+
|
|
19
|
+
<!-- rule: tap-zone -->
|
|
20
|
+
|
|
21
|
+
**Rule:** Below the phone step, reach 44x44 with a transparent layer outside the control, never by growing the control.
|
|
22
|
+
|
|
23
|
+
**Why:** A finger covers more than a cursor, and a control that grows on a phone moves the page a reader already knows.
|
|
24
|
+
|
|
25
|
+
**Do:** Centre a transparent layer on the control and let it stop at half the gap to the nearest neighbour.
|
|
26
|
+
|
|
27
|
+
**Don't:** Grow the drawn control to 44 and push out every row that holds one.
|
|
28
|
+
|
|
29
|
+
**Except:** Only a coarse pointer gets the layer, or a cursor lights a control it is not over. Rows that share an edge — a menu, a dense table, a settings list — have nothing to grow into and stay as they are drawn. Fields generate no layer, so a field reaches the floor by its own height or not at all.
|
|
30
|
+
|
|
31
|
+
## Space between small targets
|
|
32
|
+
|
|
33
|
+
<!-- rule: tap-spacing -->
|
|
34
|
+
|
|
35
|
+
**Rule:** Below the phone step, open a row of small controls to 44 minus their drawn size, so both tap zones fit between the drawn edges.
|
|
36
|
+
|
|
37
|
+
**Why:** A zone reaches half the gap before it crosses its neighbour's edge, so the gap is what decides whether either control reaches the floor.
|
|
38
|
+
|
|
39
|
+
**Do:** Open a row of 24px marks to 20px on a phone, where both zones fit and neither mark changes size.
|
|
40
|
+
|
|
41
|
+
**Don't:** Keep the desktop 8px and let the control written later in the markup win the overlap.
|
|
42
|
+
|
|
43
|
+
**Except:** A row that is already full opens downward only — widening it across makes the controls narrower, which is the one thing the floor must not do. Rows that share an edge — a menu, a dense table, a settings list — have nothing to open and keep their drawn targets. A row that opens nothing grows no zone at all.
|
|
44
|
+
|
|
17
45
|
## Focus ring contrast
|
|
18
46
|
|
|
19
47
|
<!-- rule: ring-contrast -->
|
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
|
@@ -26,6 +26,89 @@
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/* ../src/styles/tap-zone.css */
|
|
30
|
+
:root {
|
|
31
|
+
--tap-min: 44px;
|
|
32
|
+
--tap-aa: 24px;
|
|
33
|
+
--tap-gap: var(--space-5, 20px);
|
|
34
|
+
--tap-clear-x: 0px;
|
|
35
|
+
--tap-clear-y: 0px;
|
|
36
|
+
}
|
|
37
|
+
@media (max-width: 560px) and (pointer: coarse) {
|
|
38
|
+
:where(.ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social) {
|
|
39
|
+
position: relative;
|
|
40
|
+
}
|
|
41
|
+
:where(.ui-btn, .ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-switch)::after {
|
|
42
|
+
content: "";
|
|
43
|
+
position: absolute;
|
|
44
|
+
left: 50%;
|
|
45
|
+
top: 50%;
|
|
46
|
+
transform: translate(-50%, -50%);
|
|
47
|
+
width: min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-x)));
|
|
48
|
+
height: min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-y)));
|
|
49
|
+
}
|
|
50
|
+
.ui-check input::before,
|
|
51
|
+
.ui-toast__close::before {
|
|
52
|
+
width: max(var(--tap-aa), min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-x))));
|
|
53
|
+
height: max(var(--tap-aa), min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-y))));
|
|
54
|
+
}
|
|
55
|
+
.ui-seg {
|
|
56
|
+
row-gap: var(--tap-gap) !important;
|
|
57
|
+
--tap-clear-x: var(--space-1, 4px);
|
|
58
|
+
--tap-clear-y: var(--tap-gap);
|
|
59
|
+
}
|
|
60
|
+
.ui-tabs__list {
|
|
61
|
+
--tap-clear-x: 2px;
|
|
62
|
+
--tap-clear-y: var(--tap-gap);
|
|
63
|
+
}
|
|
64
|
+
.ui-pager__steps {
|
|
65
|
+
row-gap: var(--tap-gap) !important;
|
|
66
|
+
--tap-clear-x: var(--space-1, 4px);
|
|
67
|
+
--tap-clear-y: var(--tap-gap);
|
|
68
|
+
}
|
|
69
|
+
.ui-pager {
|
|
70
|
+
row-gap: var(--tap-gap) !important;
|
|
71
|
+
--tap-clear-y: var(--tap-gap);
|
|
72
|
+
}
|
|
73
|
+
.ui-filter-bar {
|
|
74
|
+
gap: var(--tap-gap) !important;
|
|
75
|
+
--tap-clear-x: var(--tap-gap);
|
|
76
|
+
--tap-clear-y: var(--tap-gap);
|
|
77
|
+
}
|
|
78
|
+
.ui-filter-bar__chip {
|
|
79
|
+
--tap-clear-x: 0px;
|
|
80
|
+
--tap-clear-y: 0px;
|
|
81
|
+
}
|
|
82
|
+
.ui-table__act .ui-btn + .ui-btn {
|
|
83
|
+
margin-inline-start: var(--tap-gap) !important;
|
|
84
|
+
}
|
|
85
|
+
.ui-table__act {
|
|
86
|
+
--tap-clear-x: var(--tap-gap);
|
|
87
|
+
--tap-clear-y: 0px;
|
|
88
|
+
}
|
|
89
|
+
.ui-toast {
|
|
90
|
+
row-gap: var(--tap-gap) !important;
|
|
91
|
+
--tap-clear-y: var(--tap-gap);
|
|
92
|
+
}
|
|
93
|
+
.ui-confirm__acts,
|
|
94
|
+
.ui-toolbar,
|
|
95
|
+
.ui-empty__actions,
|
|
96
|
+
.ui-sx__actions,
|
|
97
|
+
.ui-fbc__foot,
|
|
98
|
+
.ui-drawer__footer,
|
|
99
|
+
.ui-snippet__bar,
|
|
100
|
+
.ui-app__main .ui-card__row {
|
|
101
|
+
row-gap: var(--tap-gap) !important;
|
|
102
|
+
--tap-clear-y: var(--tap-gap);
|
|
103
|
+
}
|
|
104
|
+
.ui-footer__socials,
|
|
105
|
+
.ui-drawer__header {
|
|
106
|
+
gap: var(--tap-gap) !important;
|
|
107
|
+
--tap-clear-x: var(--tap-gap);
|
|
108
|
+
--tap-clear-y: var(--tap-gap);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
29
112
|
/* ../src/styles/input.css */
|
|
30
113
|
.ui-field {
|
|
31
114
|
display: block;
|
|
@@ -298,8 +381,8 @@
|
|
|
298
381
|
max-width: var(--prose-caption);
|
|
299
382
|
padding: var(--space-2) var(--space-3);
|
|
300
383
|
background: var(--ui-tip-bg);
|
|
301
|
-
border: 1px solid var(--
|
|
302
|
-
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--
|
|
384
|
+
border: 1px solid var(--float-edge);
|
|
385
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
303
386
|
border-radius: var(--radius-sm);
|
|
304
387
|
font-family: var(--font-sans);
|
|
305
388
|
font-size: var(--text-xs);
|
package/src/index.css
CHANGED
|
@@ -44,3 +44,6 @@
|
|
|
44
44
|
@import "./styles/feedback.css";
|
|
45
45
|
@import "./styles/success.css";
|
|
46
46
|
@import "./styles/loading.css";
|
|
47
|
+
/* Last, and that is the point: the tap zone opens a gap a component sheet set,
|
|
48
|
+
so it has to be read after every one of them. why: specification, the tap floor */
|
|
49
|
+
@import "./styles/tap-zone.css";
|
package/src/inline.js
CHANGED
|
@@ -30,6 +30,7 @@ export const styles = {
|
|
|
30
30
|
base: baseCss,
|
|
31
31
|
reducedMotion: read('styles/reduced-motion.css'),
|
|
32
32
|
fieldZoom: read('styles/field-zoom.css'),
|
|
33
|
+
tapZone: read('styles/tap-zone.css'),
|
|
33
34
|
motion: read('styles/motion.css'),
|
|
34
35
|
button: read('styles/button.css'),
|
|
35
36
|
card: read('styles/card.css'),
|
|
@@ -92,4 +93,7 @@ export const cssText = [
|
|
|
92
93
|
styles.feedback,
|
|
93
94
|
styles.success,
|
|
94
95
|
styles.loading,
|
|
96
|
+
// Last in the list for the reason src/index.css imports it last: it opens a
|
|
97
|
+
// gap a component sheet set, so it is read after every one of them.
|
|
98
|
+
styles.tapZone,
|
|
95
99
|
].join('\n');
|
package/src/styles/callout.css
CHANGED
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
max-width: var(--panel-md);
|
|
47
47
|
overflow: hidden; /* clips the timer bar to the rounded corners */
|
|
48
48
|
background: var(--surface); --ring-gap: var(--surface);
|
|
49
|
-
border: 1px solid var(--
|
|
50
|
-
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--
|
|
49
|
+
border: 1px solid var(--float-edge);
|
|
50
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
51
51
|
animation: ui-toast-in var(--dur-med) var(--ease-out) both;
|
|
52
52
|
}
|
|
53
53
|
|
package/src/styles/card.css
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
* Card — .ui-card (a filled surface one step off the page
|
|
2
|
+
* Card — .ui-card (a filled surface one step off the page: edged in dark, dropped in light)
|
|
3
3
|
* Rows: .ui-card__row / headers: .ui-card__title / .ui-card__sub
|
|
4
4
|
* ========================================================================== */
|
|
5
5
|
|
|
6
|
-
/*
|
|
7
|
-
|
|
6
|
+
/* The surface is the step; what makes it legible depends on the theme. Dark edges the
|
|
7
|
+
card with the hairline (--card-edge). Light draws no line at all and casts --elev-rest
|
|
8
|
+
instead — a 22px penumbra off a 10px offset, the width Artur's reference uses on a
|
|
9
|
+
card. Chosen by him on #490 round t3. why: docs/specification.md#elevation */
|
|
8
10
|
.ui-card {
|
|
9
11
|
background: var(--surface); --ring-gap: var(--surface);
|
|
10
|
-
border: 1px solid var(--
|
|
12
|
+
border: 1px solid var(--card-edge);
|
|
13
|
+
box-shadow: var(--elev-rest);
|
|
11
14
|
border-radius: var(--radius-xl);
|
|
12
15
|
/* Was `var(--space-6) 26px`; 26 is off the scale and broke the column's text edge.
|
|
13
16
|
why: docs/specification.md#spacing-and-rhythm */
|
|
@@ -50,13 +53,14 @@
|
|
|
50
53
|
cursor: pointer;
|
|
51
54
|
/* Written as a <button>, the card would take the browser's frame, centred text and
|
|
52
55
|
`font: 400 13.3333px Arial` — a shorthand, so `font: inherit` and not the family
|
|
53
|
-
longhand. .ui-card states the background
|
|
54
|
-
`border: 0` here would only delete the
|
|
56
|
+
longhand. .ui-card states the background, the border and its rung, so a
|
|
57
|
+
`border: 0` here would only delete the edge dark draws. #251 */
|
|
55
58
|
text-align: left;
|
|
56
59
|
font: inherit;
|
|
57
60
|
transition: transform var(--dur-fast) var(--ease);
|
|
58
61
|
}
|
|
59
|
-
/* The lift alone: the
|
|
62
|
+
/* The lift alone: the card's own rung is what says it is raised, and hover is not a
|
|
63
|
+
change of level. why: docs/specification.md#elevation */
|
|
60
64
|
.ui-card--interactive:hover { transform: translateY(-2px); }
|
|
61
65
|
.ui-card--interactive:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
|
|
62
66
|
|
|
@@ -69,10 +69,12 @@
|
|
|
69
69
|
flex-direction: column;
|
|
70
70
|
font-family: var(--font-sans);
|
|
71
71
|
background: var(--cmdk-surface); --ring-gap: var(--cmdk-surface);
|
|
72
|
-
/* The two-step edge and the drop:
|
|
73
|
-
inside it, then the
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
73
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
74
|
+
both lines are transparent and the drop carries the whole separation.
|
|
75
|
+
why: docs/specification.md#elevation */
|
|
76
|
+
border: 1px solid var(--float-edge);
|
|
77
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
76
78
|
border-radius: var(--radius-lg);
|
|
77
79
|
overflow: hidden;
|
|
78
80
|
opacity: 0;
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
box-shadow list replaces the whole list, so `box-shadow: var(--ring)` alone
|
|
87
89
|
took the panel's edge and its drop off for as long as it held focus.
|
|
88
90
|
why: docs/specification.md#elevation */
|
|
89
|
-
.ui-cmdk__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--
|
|
91
|
+
.ui-cmdk__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); }
|
|
90
92
|
|
|
91
93
|
/* -- The text box --------------------------------------------------------- */
|
|
92
94
|
|
package/src/styles/confirm.css
CHANGED
|
@@ -62,10 +62,12 @@
|
|
|
62
62
|
gap: var(--space-2);
|
|
63
63
|
padding: var(--space-5);
|
|
64
64
|
background: var(--confirm-surface); --ring-gap: var(--confirm-surface);
|
|
65
|
-
/* The two-step edge and the drop:
|
|
66
|
-
inside it, then the
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
66
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
67
|
+
both lines are transparent and the drop carries the whole separation.
|
|
68
|
+
why: docs/specification.md#elevation */
|
|
69
|
+
border: 1px solid var(--float-edge);
|
|
70
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
69
71
|
border-radius: var(--radius-lg);
|
|
70
72
|
opacity: 0;
|
|
71
73
|
transform: translateY(8px);
|
|
@@ -78,7 +80,7 @@
|
|
|
78
80
|
box-shadow list replaces the whole list, so `box-shadow: var(--ring)` alone
|
|
79
81
|
took the panel's edge and its drop off for as long as it held focus.
|
|
80
82
|
why: docs/specification.md#elevation */
|
|
81
|
-
.ui-confirm__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--
|
|
83
|
+
.ui-confirm__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); }
|
|
82
84
|
|
|
83
85
|
/* -- Slots ----------------------------------------------------------------- */
|
|
84
86
|
/* The question and the answers keep their size; the consequence between them is
|
package/src/styles/drawer.css
CHANGED
|
@@ -82,14 +82,14 @@
|
|
|
82
82
|
}
|
|
83
83
|
.ui-drawer--right .ui-drawer__panel {
|
|
84
84
|
right: 0;
|
|
85
|
-
border-left: 1px solid var(--
|
|
86
|
-
--drawer-line: inset 1px 0 0 var(--
|
|
85
|
+
border-left: 1px solid var(--float-edge);
|
|
86
|
+
--drawer-line: inset 1px 0 0 var(--float-edge-inner);
|
|
87
87
|
transform: translateX(100%);
|
|
88
88
|
}
|
|
89
89
|
.ui-drawer--left .ui-drawer__panel {
|
|
90
90
|
left: 0;
|
|
91
|
-
border-right: 1px solid var(--
|
|
92
|
-
--drawer-line: inset -1px 0 0 var(--
|
|
91
|
+
border-right: 1px solid var(--float-edge);
|
|
92
|
+
--drawer-line: inset -1px 0 0 var(--float-edge-inner);
|
|
93
93
|
transform: translateX(-100%);
|
|
94
94
|
}
|
|
95
95
|
.ui-drawer--top .ui-drawer__panel,
|
|
@@ -100,14 +100,14 @@
|
|
|
100
100
|
}
|
|
101
101
|
.ui-drawer--top .ui-drawer__panel {
|
|
102
102
|
top: 0;
|
|
103
|
-
border-bottom: 1px solid var(--
|
|
104
|
-
--drawer-line: inset 0 -1px 0 var(--
|
|
103
|
+
border-bottom: 1px solid var(--float-edge);
|
|
104
|
+
--drawer-line: inset 0 -1px 0 var(--float-edge-inner);
|
|
105
105
|
transform: translateY(-100%);
|
|
106
106
|
}
|
|
107
107
|
.ui-drawer--bottom .ui-drawer__panel {
|
|
108
108
|
bottom: 0;
|
|
109
|
-
border-top: 1px solid var(--
|
|
110
|
-
--drawer-line: inset 0 1px 0 var(--
|
|
109
|
+
border-top: 1px solid var(--float-edge);
|
|
110
|
+
--drawer-line: inset 0 1px 0 var(--float-edge-inner);
|
|
111
111
|
transform: translateY(100%);
|
|
112
112
|
}
|
|
113
113
|
|
package/src/styles/dropdown.css
CHANGED
|
@@ -71,10 +71,12 @@
|
|
|
71
71
|
/* The floating step, not the sunken one — a panel held open over a card cannot be
|
|
72
72
|
painted the colour of an inset. why: docs/specification.md#elevation */
|
|
73
73
|
background: var(--bg-elevated); --ring-gap: var(--bg-elevated);
|
|
74
|
-
/* The two-step edge and the drop:
|
|
75
|
-
inside it, then the
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
75
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
76
|
+
both lines are transparent and the drop carries the whole separation.
|
|
77
|
+
why: docs/specification.md#elevation */
|
|
78
|
+
border: 1px solid var(--float-edge);
|
|
79
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
78
80
|
border-radius: var(--radius-lg);
|
|
79
81
|
padding: var(--ui-dropdown-pad);
|
|
80
82
|
opacity: 0;
|
|
@@ -102,7 +104,7 @@
|
|
|
102
104
|
a panel that takes focus any other way has the same claim on the ring. The
|
|
103
105
|
ring is COMPOSED with the floating treatment rather than over it: a box-shadow
|
|
104
106
|
list replaces the whole list. why: docs/specification.md#elevation */
|
|
105
|
-
.ui-dropdown__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--
|
|
107
|
+
.ui-dropdown__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); }
|
|
106
108
|
|
|
107
109
|
/* Opening upward. The kit releases its own `top` here so that nothing outside
|
|
108
110
|
the kit has to: an absolute box with both edges pinned is stretched between
|
package/src/styles/feedback.css
CHANGED
|
@@ -67,8 +67,12 @@
|
|
|
67
67
|
.ui-fbcomposer {
|
|
68
68
|
position: fixed; z-index: 71; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(14px);
|
|
69
69
|
width: min(520px, calc(100vw - 32px)); background: var(--bg-elevated); --ring-gap: var(--bg-elevated); border-radius: 18px;
|
|
70
|
-
/* The shadow was its only edge until #295
|
|
71
|
-
|
|
70
|
+
/* The shadow was its only edge until #295, which replaced it with the hairline; since
|
|
71
|
+
#490 light is back on the drop and dark keeps the line.
|
|
72
|
+
why: docs/specification.md#elevation */
|
|
73
|
+
border: 1px solid var(--float-edge);
|
|
74
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
75
|
+
opacity: 0; pointer-events: none;
|
|
72
76
|
transition: opacity var(--dur-med) var(--ease), transform var(--dur-med) var(--ease); overflow: hidden;
|
|
73
77
|
}
|
|
74
78
|
.ui-fbcomposer.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
|
package/src/styles/layout.css
CHANGED
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
.ui-app-page .topbar { position: sticky; top: 0; z-index: var(--z-sticky); }
|
|
29
29
|
|
|
30
30
|
/* The rail uses the card surface so its labels stay on a reading ground.
|
|
31
|
-
|
|
31
|
+
Dark keeps its edge (--rail-edge); light casts --elev-rail, sideways, because the rail
|
|
32
|
+
is flush to a screen edge. Reopens the light half of #454.
|
|
33
|
+
why: docs/specification.md#elevation
|
|
32
34
|
overflow-y, because the box is pinned at the viewport's height for the whole
|
|
33
35
|
scroll of .ui-app — without it a row past that height paints below the fold
|
|
34
36
|
and no amount of document scrolling brings it back. The rail's own padding is
|
|
@@ -38,7 +40,8 @@
|
|
|
38
40
|
flex-direction: column;
|
|
39
41
|
gap: var(--space-5);
|
|
40
42
|
padding: var(--space-5) var(--space-4);
|
|
41
|
-
border-right: var(--ui-rail-rule) solid var(--
|
|
43
|
+
border-right: var(--ui-rail-rule) solid var(--rail-edge);
|
|
44
|
+
box-shadow: var(--elev-rail);
|
|
42
45
|
background: var(--surface); --ring-gap: var(--surface);
|
|
43
46
|
position: sticky;
|
|
44
47
|
top: var(--ui-app-top, 0px);
|
|
@@ -296,7 +299,7 @@
|
|
|
296
299
|
why: docs/specification.md#elevation */
|
|
297
300
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
|
|
298
301
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who,
|
|
299
|
-
.ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--
|
|
302
|
+
.ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--float-edge); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); opacity: 1; pointer-events: none; }
|
|
300
303
|
@supports (anchor-name: --a) and (anchor-scope: --a) {
|
|
301
304
|
.ui-app.is-collapsed,
|
|
302
305
|
.ui-app:has(.ui-app__fold) { anchor-scope: --ui-rail; }
|
|
@@ -395,7 +398,7 @@
|
|
|
395
398
|
.ui-app__search:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-color: var(--accent); }
|
|
396
399
|
/* The same glyph the palette draws in its own search box, at the same box and the
|
|
397
400
|
same stroke: the trigger and the thing it opens are not two search marks.
|
|
398
|
-
src/styles/command-palette.css:
|
|
401
|
+
src/styles/command-palette.css:109 `.ui-cmdk__search-ic svg` */
|
|
399
402
|
.ui-app__search-ic { display: flex; flex: none; }
|
|
400
403
|
.ui-app__search-ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; }
|
|
401
404
|
/* Part of the button's name, so it is squeezed rather than dropped: an ellipsis
|
|
@@ -497,7 +500,7 @@
|
|
|
497
500
|
.ui-app__rail .ui-nav__item,
|
|
498
501
|
.ui-app__rail .ui-app__user-trigger { min-height: 44px; }
|
|
499
502
|
.ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
|
|
500
|
-
.ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--
|
|
503
|
+
.ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--float-edge); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); opacity: 1; pointer-events: none; }
|
|
501
504
|
@supports (anchor-name: --a) and (anchor-scope: --a) {
|
|
502
505
|
.ui-app { anchor-scope: --ui-rail; }
|
|
503
506
|
.ui-app__rail { anchor-name: --ui-rail; }
|
|
@@ -529,10 +532,12 @@
|
|
|
529
532
|
z-index: 1;
|
|
530
533
|
width: 100%;
|
|
531
534
|
max-width: var(--panel-md);
|
|
532
|
-
/* Floats over a spotlit page, so it takes the floating step and the
|
|
535
|
+
/* Floats over a spotlit page, so it takes the floating step and the floating
|
|
536
|
+
treatment: the two-step edge in dark, the drop alone in light.
|
|
533
537
|
why: docs/specification.md#elevation */
|
|
534
538
|
background: var(--bg-elevated); --ring-gap: var(--bg-elevated);
|
|
535
|
-
border: 1px solid var(--
|
|
539
|
+
border: 1px solid var(--float-edge);
|
|
540
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
536
541
|
border-radius: 22px;
|
|
537
542
|
padding: 38px 36px 32px;
|
|
538
543
|
}
|
package/src/styles/success.css
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
overflow: hidden;
|
|
18
18
|
border-radius: 20px;
|
|
19
19
|
background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* Elevated surface for the confirmation and its actions. */
|
|
20
|
-
|
|
20
|
+
/* The floating treatment, like every other surface on that step.
|
|
21
|
+
why: docs/specification.md#elevation */
|
|
22
|
+
border: 1px solid var(--float-edge);
|
|
23
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
21
24
|
isolation: isolate;
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/* apliteni-ui — the tap zone. Below the phone step, to a coarse pointer, a
|
|
2
|
+
* control that draws smaller than --tap-min carries a transparent layer that
|
|
3
|
+
* reaches the floor, and no control is drawn any bigger. Zones alone do not
|
|
4
|
+
* finish it: a layer takes HALF the clear space its container declares, so
|
|
5
|
+
* where the kit packs small controls closer than that, the GAP opens to
|
|
6
|
+
* --tap-gap. The controls keep their size; the space between them grows.
|
|
7
|
+
*
|
|
8
|
+
* Read LAST, after every component sheet — see src/index.css — because each
|
|
9
|
+
* opening overrides a gap one of them already set.
|
|
10
|
+
*
|
|
11
|
+
* why: docs/specification.md#a-tap-reaches-the-floor-below-the-phone-step
|
|
12
|
+
* Held by stories/tap-zone.test.js, measured in a browser at 390.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
/* WCAG 2.5.5, and where iOS and Android land. Not on the spacing scale,
|
|
17
|
+
which runs 40 then 48, because a fingertip is not spacing. Declared here
|
|
18
|
+
rather than in tokens.css for the reason field-zoom.css carries its own 16:
|
|
19
|
+
react/src/index.ts imports this sheet without the token file, and a var()
|
|
20
|
+
resolving to nothing there would size the layer to `auto` in silence. */
|
|
21
|
+
--tap-min: 44px;
|
|
22
|
+
|
|
23
|
+
/* WCAG 2.5.8 (AA), the target the kit holds at every width: the floor this
|
|
24
|
+
sheet raises FROM and may never drop below. */
|
|
25
|
+
--tap-aa: 24px;
|
|
26
|
+
|
|
27
|
+
/* 44 − 24: two zones fit between two of the smallest marks the kit draws. */
|
|
28
|
+
--tap-gap: var(--space-5, 20px);
|
|
29
|
+
|
|
30
|
+
/* Clear space to the nearest neighbour, declared by the container that knows
|
|
31
|
+
its own gap; a zone takes half on each side.
|
|
32
|
+
|
|
33
|
+
ZERO by default, so a zone grows nowhere until a container has opened the
|
|
34
|
+
room — assuming --tap-gap everywhere was measured handing `Keep the
|
|
35
|
+
workspace` a pixel of its own box that answered for `Delete it
|
|
36
|
+
permanently`. */
|
|
37
|
+
--tap-clear-x: 0px;
|
|
38
|
+
--tap-clear-y: 0px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (max-width: 560px) and (pointer: coarse) {
|
|
42
|
+
/* The containing block for every layer below. `.ui-btn` already has it; the
|
|
43
|
+
rest take it only inside this query, because `position: relative` lifts a
|
|
44
|
+
control over the in-flow one beside it and that changes who wins a shared
|
|
45
|
+
pixel. */
|
|
46
|
+
:where(
|
|
47
|
+
.ui-seg button,
|
|
48
|
+
.ui-tabs__tab,
|
|
49
|
+
.ui-dropdown__trigger,
|
|
50
|
+
.ui-toast__action,
|
|
51
|
+
.ui-drawer__close,
|
|
52
|
+
.ui-snippet__copy,
|
|
53
|
+
.ui-footer__social
|
|
54
|
+
) { position: relative; }
|
|
55
|
+
|
|
56
|
+
/* The layer: centred, transparent, sized to the floor, clamped to half the
|
|
57
|
+
clearance. */
|
|
58
|
+
:where(
|
|
59
|
+
.ui-btn,
|
|
60
|
+
.ui-seg button,
|
|
61
|
+
.ui-tabs__tab,
|
|
62
|
+
.ui-dropdown__trigger,
|
|
63
|
+
.ui-toast__action,
|
|
64
|
+
.ui-drawer__close,
|
|
65
|
+
.ui-snippet__copy,
|
|
66
|
+
.ui-footer__social,
|
|
67
|
+
.ui-switch
|
|
68
|
+
)::after {
|
|
69
|
+
content: "";
|
|
70
|
+
position: absolute;
|
|
71
|
+
left: 50%;
|
|
72
|
+
top: 50%;
|
|
73
|
+
transform: translate(-50%, -50%);
|
|
74
|
+
width: min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-x)));
|
|
75
|
+
height: min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-y)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ::after on these is spoken for — the tick and the dot, the toast close's
|
|
79
|
+
glyph plate — so the existing ::before grows, floored at --tap-aa. The
|
|
80
|
+
floor is load-bearing: `100%` is the PADDING box, 16 on a 19px checkbox
|
|
81
|
+
with a 1.5px border, so without it the clamp takes this layer DOWN to 22
|
|
82
|
+
and under the kit's own minimum. */
|
|
83
|
+
.ui-check input::before,
|
|
84
|
+
.ui-toast__close::before {
|
|
85
|
+
width: max(var(--tap-aa), min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-x))));
|
|
86
|
+
height: max(var(--tap-aa), min(max(100%, var(--tap-min)), calc(100% + var(--tap-clear-y))));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* -- Where the kit opens a gap, and where it cannot --------------------
|
|
90
|
+
* `!important` for the reason field-zoom.css takes it: each opening
|
|
91
|
+
* overrides a `gap` shorthand a component sheet set, and the React bundle
|
|
92
|
+
* ships this sheet without those sheets, where order is the consumer's. */
|
|
93
|
+
|
|
94
|
+
/* A strip keeps its pills packed across — that tight track IS the control —
|
|
95
|
+
and opens down, where the room is free until it wraps. */
|
|
96
|
+
.ui-seg {
|
|
97
|
+
row-gap: var(--tap-gap) !important;
|
|
98
|
+
--tap-clear-x: var(--space-1, 4px);
|
|
99
|
+
--tap-clear-y: var(--tap-gap);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Tabs are wide enough across; down, the panel under them has the room. */
|
|
103
|
+
.ui-tabs__list { --tap-clear-x: 2px; --tap-clear-y: var(--tap-gap); }
|
|
104
|
+
|
|
105
|
+
/* Equal cells read as one strip, so they stay packed across and open down,
|
|
106
|
+
which is where this strip wraps at this width. */
|
|
107
|
+
.ui-pager__steps {
|
|
108
|
+
row-gap: var(--tap-gap) !important;
|
|
109
|
+
--tap-clear-x: var(--space-1, 4px);
|
|
110
|
+
--tap-clear-y: var(--tap-gap);
|
|
111
|
+
}
|
|
112
|
+
.ui-pager { row-gap: var(--tap-gap) !important; --tap-clear-y: var(--tap-gap); }
|
|
113
|
+
|
|
114
|
+
/* Chips open both ways: the row wraps, and the button at the end of it is a
|
|
115
|
+
target like the chips are. */
|
|
116
|
+
.ui-filter-bar { gap: var(--tap-gap) !important; --tap-clear-x: var(--tap-gap); --tap-clear-y: var(--tap-gap); }
|
|
117
|
+
|
|
118
|
+
/* Inside a chip there is nothing to open: parting the value from its remove
|
|
119
|
+
mark would draw two controls where the reader sees one. Named on the
|
|
120
|
+
Accessibility minimums page. */
|
|
121
|
+
.ui-filter-bar__chip { --tap-clear-x: 0px; --tap-clear-y: 0px; }
|
|
122
|
+
|
|
123
|
+
/* Row actions open across — the pair #488 names — and cannot open down:
|
|
124
|
+
a dense table's rows share an edge, and parting them is the row height the
|
|
125
|
+
guideline protects. Down, they get the row's own padding, twice. */
|
|
126
|
+
/* Row actions — the pair #488 names — part ACROSS with a margin: the cell is
|
|
127
|
+
a `table-cell`, where `gap` does nothing, and declaring it anyway left the
|
|
128
|
+
mark's zone 9px inside `Revoke`. Nothing down, because a dense table's
|
|
129
|
+
rows share an edge and its padding is not clearance to spend. */
|
|
130
|
+
.ui-table__act .ui-btn + .ui-btn { margin-inline-start: var(--tap-gap) !important; }
|
|
131
|
+
.ui-table__act { --tap-clear-x: var(--tap-gap); --tap-clear-y: 0px; }
|
|
132
|
+
|
|
133
|
+
/* Down only, like the nine rows below: a toast is a full row at this width,
|
|
134
|
+
and opening it across took 24px out of the message for no reach at all —
|
|
135
|
+
its action already clears the floor on the vertical half. The box does not
|
|
136
|
+
open either: a toast clips to its own rounded corners for the timer bar,
|
|
137
|
+
and widening its padding grows the surface rather than the space between
|
|
138
|
+
controls, measured at four marks for 8px on every toast. */
|
|
139
|
+
.ui-toast { row-gap: var(--tap-gap) !important; --tap-clear-y: var(--tap-gap); }
|
|
140
|
+
|
|
141
|
+
/* The kit's remaining control rows open DOWN only. A row of labelled buttons
|
|
142
|
+
at 390 is often already full, so widening its column gap does not move the
|
|
143
|
+
buttons apart — it makes them narrower, and that is the half #488 rules
|
|
144
|
+
out. Measured on a toolbar of three buttons and a dropdown. These rows
|
|
145
|
+
wrap, which is also where the review measured them colliding. */
|
|
146
|
+
.ui-confirm__acts,
|
|
147
|
+
.ui-toolbar,
|
|
148
|
+
.ui-empty__actions,
|
|
149
|
+
.ui-sx__actions,
|
|
150
|
+
.ui-fbc__foot,
|
|
151
|
+
.ui-drawer__footer,
|
|
152
|
+
.ui-snippet__bar,
|
|
153
|
+
.ui-app__main .ui-card__row {
|
|
154
|
+
row-gap: var(--tap-gap) !important;
|
|
155
|
+
--tap-clear-y: var(--tap-gap);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Both ways: fixed squares cannot overflow the way labels can, and a drawer
|
|
159
|
+
header spends its column gap on the title. Not the accent picker — its
|
|
160
|
+
swatches are on no carrier list, so opening it bought no reach. */
|
|
161
|
+
.ui-footer__socials,
|
|
162
|
+
.ui-drawer__header {
|
|
163
|
+
gap: var(--tap-gap) !important;
|
|
164
|
+
--tap-clear-x: var(--tap-gap);
|
|
165
|
+
--tap-clear-y: var(--tap-gap);
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/styles/tooltip.css
CHANGED
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
max-width: var(--prose-caption);
|
|
27
27
|
padding: var(--space-2) var(--space-3);
|
|
28
28
|
background: var(--ui-tip-bg); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
|
|
29
|
-
/* The two-step edge and the drop:
|
|
30
|
-
inside it, then the
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
30
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
31
|
+
both lines are transparent and the drop carries the whole separation.
|
|
32
|
+
why: docs/specification.md#elevation */
|
|
33
|
+
border: 1px solid var(--float-edge);
|
|
34
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
33
35
|
border-radius: var(--radius-sm);
|
|
34
36
|
font-family: var(--font-sans);
|
|
35
37
|
font-size: var(--text-xs);
|
package/src/styles/topbar.css
CHANGED
|
@@ -129,10 +129,12 @@
|
|
|
129
129
|
left: 0;
|
|
130
130
|
min-width: 266px;
|
|
131
131
|
background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
|
|
132
|
-
/* The two-step edge and the drop:
|
|
133
|
-
inside it, then the
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
133
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
134
|
+
both lines are transparent and the drop carries the whole separation.
|
|
135
|
+
why: docs/specification.md#elevation */
|
|
136
|
+
border: 1px solid var(--float-edge);
|
|
137
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
136
138
|
border-radius: 16px;
|
|
137
139
|
padding: 6px;
|
|
138
140
|
opacity: 0;
|
|
@@ -186,7 +188,7 @@
|
|
|
186
188
|
transition: background var(--dur-fast) var(--ease);
|
|
187
189
|
}
|
|
188
190
|
/* Menu rows use the reading surface and an edge on hover — see
|
|
189
|
-
src/styles/dropdown.css:
|
|
191
|
+
src/styles/dropdown.css:166 `.ui-dropdown__item:hover`.
|
|
190
192
|
why: docs/specification.md#elevation */
|
|
191
193
|
.vopt:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
|
|
192
194
|
/* The row is a keyboard stop: src/components/dropdown.js:554
|
|
@@ -238,10 +240,12 @@
|
|
|
238
240
|
right: 0; top: 44px;
|
|
239
241
|
width: 252px;
|
|
240
242
|
background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
|
|
241
|
-
/* The two-step edge and the drop:
|
|
242
|
-
inside it, then the
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
/* The two-step edge and the drop, through the level's own names: in DARK that is
|
|
244
|
+
--border-strong outside and --border one pixel inside it, then the drop; in LIGHT
|
|
245
|
+
both lines are transparent and the drop carries the whole separation.
|
|
246
|
+
why: docs/specification.md#elevation */
|
|
247
|
+
border: 1px solid var(--float-edge);
|
|
248
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
245
249
|
border-radius: 14px;
|
|
246
250
|
overflow: hidden;
|
|
247
251
|
opacity: 0;
|
package/src/tokens/tokens.css
CHANGED
|
@@ -133,10 +133,10 @@
|
|
|
133
133
|
--pink: #e97ca5;
|
|
134
134
|
--amber: #ffcf6a;
|
|
135
135
|
|
|
136
|
-
/* The elevation ladder, bottom to top. A surface says how high it is with its step
|
|
137
|
-
with the hairline around it
|
|
138
|
-
below the floating step
|
|
139
|
-
|
|
136
|
+
/* The elevation ladder, bottom to top. A surface says how high it is with its step
|
|
137
|
+
and, IN DARK, with the hairline around it; --muted caps how far the ladder can go.
|
|
138
|
+
Nothing casts in dark below the floating step, and the floating step adds
|
|
139
|
+
--elev-drop under its inner line. why: docs/specification.md#elevation */
|
|
140
140
|
--bg: #0e0d14;
|
|
141
141
|
--table-bg: var(--bg);
|
|
142
142
|
--surface-2: #161520; /* non-text sunken marks and tracks */
|
|
@@ -150,6 +150,15 @@
|
|
|
150
150
|
--border: #332f45;
|
|
151
151
|
--border-strong: #453f5c;
|
|
152
152
|
|
|
153
|
+
/* WHICH EDGE EACH LEVEL DRAWS. One name per level, so a sheet never has to ask
|
|
154
|
+
which theme it is in. Dark answers all four with the hairline, because a drop
|
|
155
|
+
on a near-black page reaches 1.20 at its core and has nowhere else to go —
|
|
156
|
+
light answers them with the drop instead. why: docs/specification.md#elevation */
|
|
157
|
+
--card-edge: var(--border); /* a card */
|
|
158
|
+
--rail-edge: var(--border); /* the shell's rail */
|
|
159
|
+
--float-edge: var(--border-strong); /* a floating surface, outer line */
|
|
160
|
+
--float-edge-inner: var(--border); /* a floating surface, inner line */
|
|
161
|
+
|
|
153
162
|
/* Fields use the card surface and an edge in both themes. why: docs/specification.md#elevation */
|
|
154
163
|
--field-bg: var(--surface);
|
|
155
164
|
--field-edge: var(--border);
|
|
@@ -257,13 +266,20 @@
|
|
|
257
266
|
token and cannot be — a var() inside a custom property is substituted on the
|
|
258
267
|
element that DECLARES it, so an --elev-edge read here resolves at :root and
|
|
259
268
|
nothing below could re-point it. A floating surface writes the pair itself:
|
|
260
|
-
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--
|
|
269
|
+
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
261
270
|
Dark spends 62% / 50% of the ink and reaches 1.20 at the drop's core, having
|
|
262
271
|
nowhere to go on a near-black page. why: docs/specification.md#elevation */
|
|
263
272
|
--elev-drop:
|
|
264
273
|
0 14px 30px -12px color-mix(in srgb, var(--shadow-ink) 62%, transparent),
|
|
265
274
|
0 3px 9px -4px color-mix(in srgb, var(--shadow-ink) 50%, transparent);
|
|
266
275
|
|
|
276
|
+
/* The ladder's two resting rungs, which only light climbs. Transparent here and
|
|
277
|
+
not `none`, for the reason the deprecated --shadow-* tokens are: these are read
|
|
278
|
+
in a layer list, and `none` is valid only alone — it would invalidate the whole
|
|
279
|
+
declaration and take the focus ring with it. why: docs/specification.md#elevation */
|
|
280
|
+
--elev-rest: 0 0 #0000; /* a card */
|
|
281
|
+
--elev-rail: 0 0 #0000; /* the shell's rail, cast sideways */
|
|
282
|
+
|
|
267
283
|
/* Backdrop that dims the page behind an overlay (drawer, modal, composer). */
|
|
268
284
|
--scrim: rgba(0, 0, 0, 0.58);
|
|
269
285
|
|
|
@@ -312,6 +328,17 @@
|
|
|
312
328
|
--border: #e4e7ee;
|
|
313
329
|
--border-strong: #cdd2dc;
|
|
314
330
|
|
|
331
|
+
/* Light draws a LEVEL with a drop and no line at all. Decided by Artur on the
|
|
332
|
+
#490 triage round t3: "It's better" — the soft shadow over the hairline, against
|
|
333
|
+
the reference he supplied. A line divides two regions of ONE surface (a card's
|
|
334
|
+
rows, a table's rules, the rail's head band, the topbar); a drop says a surface
|
|
335
|
+
is HIGHER, and only a higher surface may cast one.
|
|
336
|
+
why: docs/specification.md#elevation */
|
|
337
|
+
--card-edge: transparent;
|
|
338
|
+
--rail-edge: transparent;
|
|
339
|
+
--float-edge: transparent;
|
|
340
|
+
--float-edge-inner: transparent;
|
|
341
|
+
|
|
315
342
|
/* Light draws a field with its EDGE, because since #448 it has no well left to draw it
|
|
316
343
|
with: a card and a panel are both white, the sunken step falls two rungs under either,
|
|
317
344
|
and --border on it measures 1.047:1. why: docs/specification.md#elevation */
|
|
@@ -389,12 +416,21 @@
|
|
|
389
416
|
--shadow-ink: #1e1e32;
|
|
390
417
|
--sheen: #ffffff;
|
|
391
418
|
|
|
392
|
-
/*
|
|
393
|
-
|
|
394
|
-
|
|
419
|
+
/* THE LIGHT LADDER. Three rungs, each two layers: a wide faint one and a short one.
|
|
420
|
+
Each layer is offset on one axis, blurred at least twice as wide as it is offset,
|
|
421
|
+
and held in by a negative spread. None is a glow — a glow is a zero-offset layer of
|
|
422
|
+
the signal's own colour. A level with no line casts a rung that reaches on every
|
|
423
|
+
side it exposes, which is why the wide layer's offset stays under half its blur.
|
|
424
|
+
why: docs/specification.md#elevation */
|
|
425
|
+
--elev-rest:
|
|
426
|
+
0 2px 26px -3px color-mix(in srgb, var(--shadow-ink) 9%, transparent),
|
|
427
|
+
0 1px 3px -1px color-mix(in srgb, var(--shadow-ink) 7%, transparent);
|
|
428
|
+
--elev-rail:
|
|
429
|
+
10px 0 24px -14px color-mix(in srgb, var(--shadow-ink) 16%, transparent),
|
|
430
|
+
1px 0 3px -1px color-mix(in srgb, var(--shadow-ink) 7%, transparent);
|
|
395
431
|
--elev-drop:
|
|
396
|
-
0
|
|
397
|
-
0
|
|
432
|
+
0 20px 46px -16px color-mix(in srgb, var(--shadow-ink) 22%, transparent),
|
|
433
|
+
0 1px 14px -4px color-mix(in srgb, var(--shadow-ink) 12%, transparent);
|
|
398
434
|
|
|
399
435
|
--scrim: rgba(20, 22, 40, 0.38);
|
|
400
436
|
|