@rio-m3e/styles 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/base/anchored-position.css +10 -0
- package/src/base/reduced-motion.css +1 -2
- package/src/base/shape-morph.css +28 -4
- package/src/base/state-layer.css +15 -1
- package/src/button-group.css +120 -0
- package/src/button.css +27 -7
- package/src/dialog.css +201 -0
- package/src/menu.css +255 -0
- package/src/split-button.css +185 -0
- package/src/switch.css +12 -4
- package/src/tooltip.css +93 -0
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pairs with `attachAnchoredPosition` from `@rio-m3e/core`, which marks the element with
|
|
3
|
+
* `data-anchored` and writes `left` and `top` in viewport coordinates. The element is shown in the
|
|
4
|
+
* top layer, as a popover, which browsers center by default; this lets the coordinates apply.
|
|
5
|
+
*/
|
|
6
|
+
:where([data-anchored]) {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: auto;
|
|
9
|
+
margin: 0;
|
|
10
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Reduced motion: when the user asks the OS to reduce motion, spatial motion (position, size,
|
|
3
3
|
* rotation, corner radius) becomes instant. Effects motion (color, opacity) keeps its short fades,
|
|
4
|
-
* which don't cause the discomfort reduced motion is meant to prevent.
|
|
5
|
-
* M3 pages Rio has read don't define reduced-motion behavior.
|
|
4
|
+
* which don't cause the discomfort reduced motion is meant to prevent.
|
|
6
5
|
*
|
|
7
6
|
* Every element gets the override, with !important, so it beats the theme variables `applyTheme`
|
|
8
7
|
* sets inline on the root, and any subtree that swaps motion schemes. Components that use the
|
package/src/base/shape-morph.css
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Shape morphing, driven by `attachShapeMorph` from `@rio-m3e/core`. A component sets
|
|
3
|
-
* `--rio-shape-morph-target` to the corner radius it wants in each state
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* `--rio-shape-morph-target` to the corner radius it wants in each state. The primitive reads
|
|
4
|
+
* the targets in px, which needs the registrations below, and springs the inline radii toward
|
|
5
|
+
* them. It marks the element with `data-shape-morph`.
|
|
6
|
+
*
|
|
7
|
+
* Shapes whose sides differ set `--rio-shape-morph-start-target` and `--rio-shape-morph-end-target`
|
|
8
|
+
* (the corners at the start and end of the line, in the writing direction) with any class or
|
|
9
|
+
* attribute selector; otherwise both follow `--rio-shape-morph-target`.
|
|
10
|
+
*
|
|
11
|
+
* The component also draws its corners from the per-side targets, so it still works without
|
|
12
|
+
* JavaScript, and lists both targets in its `transition` as `1ms step-end`, so the springs see
|
|
13
|
+
* them change (see "Primitives" in docs/conventions.md, and button.css).
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
@property --rio-shape-morph-target {
|
|
@@ -11,3 +18,20 @@
|
|
|
11
18
|
inherits: false;
|
|
12
19
|
initial-value: 0px;
|
|
13
20
|
}
|
|
21
|
+
|
|
22
|
+
@property --rio-shape-morph-start-target {
|
|
23
|
+
syntax: '<length>';
|
|
24
|
+
inherits: false;
|
|
25
|
+
initial-value: 0px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@property --rio-shape-morph-end-target {
|
|
29
|
+
syntax: '<length>';
|
|
30
|
+
inherits: false;
|
|
31
|
+
initial-value: 0px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where([data-shape-morph]) {
|
|
35
|
+
--rio-shape-morph-start-target: var(--rio-shape-morph-target);
|
|
36
|
+
--rio-shape-morph-end-target: var(--rio-shape-morph-target);
|
|
37
|
+
}
|
package/src/base/state-layer.css
CHANGED
|
@@ -41,7 +41,21 @@
|
|
|
41
41
|
opacity: var(--rio-state-pressed-state-layer-opacity);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/*
|
|
45
|
+
* A button that opened a menu stays pressed while the menu is open. Only a button that opens a
|
|
46
|
+
* popup: other buttons that expand something, such as an accordion's, keep their look. Disabled
|
|
47
|
+
* ones are left out here, since this selector outweighs the disabled rule below.
|
|
48
|
+
*/
|
|
49
|
+
.rio-state-layer[aria-haspopup]:not(
|
|
50
|
+
[aria-haspopup='false'],
|
|
51
|
+
:disabled,
|
|
52
|
+
[aria-disabled='true']
|
|
53
|
+
)[aria-expanded='true']::before {
|
|
54
|
+
opacity: var(--rio-state-pressed-state-layer-opacity);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* `attachDrag` from `@rio-m3e/core` marks an element with `data-dragging` while it moves. */
|
|
58
|
+
.rio-state-layer[data-dragging]::before {
|
|
45
59
|
opacity: var(--rio-state-dragged-state-layer-opacity);
|
|
46
60
|
}
|
|
47
61
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* M3E button group: a row of buttons and icon buttons (`button.css`, `icon-button.css`). The group
|
|
3
|
+
* has no color of its own. It carries `data-variant`, `data-size`, and `data-shape`, which
|
|
4
|
+
* `createButtonGroup` from `@rio-m3e/core` sets; the size and shape reach the buttons through the
|
|
5
|
+
* framework adapter, so these rules read the buttons' own `data-size` and `data-shape`.
|
|
6
|
+
*
|
|
7
|
+
* Values come from Material Web's component tokens (`_md-comp-button-group-standard-*.scss` and
|
|
8
|
+
* `_md-comp-button-group-connected-*.scss`, Material 3 v34.0.21). The pressed button's width
|
|
9
|
+
* change in standard groups is JavaScript (`attachButtonGroup` from `@rio-m3e/core`).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.rio-button-group {
|
|
13
|
+
/* Standard, small */
|
|
14
|
+
--_between: 12px;
|
|
15
|
+
|
|
16
|
+
display: inline-flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: var(--_between);
|
|
19
|
+
vertical-align: middle;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Standard: the space keeps every button's 48px target clear of its neighbors' */
|
|
23
|
+
|
|
24
|
+
.rio-button-group:where([data-size='xsmall']) {
|
|
25
|
+
--_between: 18px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.rio-button-group:where([data-size='medium'], [data-size='large'], [data-size='xlarge']) {
|
|
29
|
+
--_between: 8px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* Connected: 2px apart, and as wide as the space it's given, shared equally by its buttons. The
|
|
34
|
+
* ends of the row keep the group's shape; the facing corners are smaller, tighten when pressed,
|
|
35
|
+
* and open up fully when the button is selected. Each button springs its two sides separately
|
|
36
|
+
* (see base/shape-morph.css).
|
|
37
|
+
*
|
|
38
|
+
* A button may sit in a tooltip's wrapper (`tooltip.css`), which has no box, so each rule also
|
|
39
|
+
* reaches a button one level down in one.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
.rio-button-group:where([data-variant='connected']) {
|
|
43
|
+
--_between: 2px;
|
|
44
|
+
|
|
45
|
+
display: flex;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rio-button-group[data-variant='connected'] > .rio-button,
|
|
49
|
+
.rio-button-group[data-variant='connected'] > .rio-tooltip-trigger > .rio-button {
|
|
50
|
+
/* Small, medium, and extra small */
|
|
51
|
+
--_inner: var(--rio-shape-corner-small);
|
|
52
|
+
--_inner-pressed: var(--rio-shape-corner-extra-small);
|
|
53
|
+
/* Corners that face another button, and corners at the ends of the row */
|
|
54
|
+
--_inner-now: var(--_inner);
|
|
55
|
+
--_outer: var(--_round);
|
|
56
|
+
--_start: var(--_inner-now);
|
|
57
|
+
--_end: var(--_inner-now);
|
|
58
|
+
|
|
59
|
+
flex: 1 1 0;
|
|
60
|
+
/* The corners show these targets (button.css). */
|
|
61
|
+
--rio-shape-morph-start-target: var(--_start);
|
|
62
|
+
--rio-shape-morph-end-target: var(--_end);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.rio-button-group[data-variant='connected'] > .rio-button:where([data-size='large']),
|
|
66
|
+
.rio-button-group[data-variant='connected']
|
|
67
|
+
> .rio-tooltip-trigger
|
|
68
|
+
> .rio-button:where([data-size='large']) {
|
|
69
|
+
--_inner: var(--rio-shape-corner-large);
|
|
70
|
+
--_inner-pressed: var(--rio-shape-corner-medium);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.rio-button-group[data-variant='connected'] > .rio-button:where([data-size='xlarge']),
|
|
74
|
+
.rio-button-group[data-variant='connected']
|
|
75
|
+
> .rio-tooltip-trigger
|
|
76
|
+
> .rio-button:where([data-size='xlarge']) {
|
|
77
|
+
--_inner: var(--rio-shape-corner-large-increased);
|
|
78
|
+
--_inner-pressed: var(--rio-shape-corner-large);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* A square group's ends use the same radius as its facing corners. */
|
|
82
|
+
.rio-button-group[data-variant='connected'] > .rio-button[data-shape='square'],
|
|
83
|
+
.rio-button-group[data-variant='connected']
|
|
84
|
+
> .rio-tooltip-trigger
|
|
85
|
+
> .rio-button[data-shape='square'] {
|
|
86
|
+
--_outer: var(--_inner);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.rio-button-group[data-variant='connected'] > .rio-button:first-child,
|
|
90
|
+
.rio-button-group[data-variant='connected'] > .rio-tooltip-trigger:first-child > .rio-button {
|
|
91
|
+
--_start: var(--_outer);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.rio-button-group[data-variant='connected'] > .rio-button:last-child,
|
|
95
|
+
.rio-button-group[data-variant='connected'] > .rio-tooltip-trigger:last-child > .rio-button {
|
|
96
|
+
--_end: var(--_outer);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.rio-button-group[data-variant='connected'] > .rio-button[data-selected='true'],
|
|
100
|
+
.rio-button-group[data-variant='connected']
|
|
101
|
+
> .rio-tooltip-trigger
|
|
102
|
+
> .rio-button[data-selected='true'] {
|
|
103
|
+
--_inner-now: var(--_round);
|
|
104
|
+
--_outer: var(--_round);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* After selected, so a pressed selected button still tightens its facing corners */
|
|
108
|
+
.rio-button-group[data-variant='connected'] > .rio-button[data-pressed],
|
|
109
|
+
.rio-button-group[data-variant='connected'] > .rio-tooltip-trigger > .rio-button[data-pressed] {
|
|
110
|
+
--_inner-now: var(--_inner-pressed);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Extra small and small icon buttons keep a 48px minimum width, matching their target. */
|
|
114
|
+
.rio-button-group[data-variant='connected']
|
|
115
|
+
> .rio-icon-button:where([data-size='xsmall'], [data-size='small']),
|
|
116
|
+
.rio-button-group[data-variant='connected']
|
|
117
|
+
> .rio-tooltip-trigger
|
|
118
|
+
> .rio-icon-button:where([data-size='xsmall'], [data-size='small']) {
|
|
119
|
+
min-inline-size: 48px;
|
|
120
|
+
}
|
package/src/button.css
CHANGED
|
@@ -42,9 +42,19 @@
|
|
|
42
42
|
block-size: var(--_height);
|
|
43
43
|
padding-inline: var(--_padding);
|
|
44
44
|
border: var(--_outline-width) solid var(--_outline);
|
|
45
|
-
/*
|
|
45
|
+
/*
|
|
46
|
+
* The shape-morph primitive springs the inline radii toward these targets. The corners show the
|
|
47
|
+
* targets, not `--_radius`: when the state changes, a target holds its old value until the
|
|
48
|
+
* spring takes over (see `transition` below), so the new shape doesn't flash first. Button
|
|
49
|
+
* groups and split buttons set the per-side targets.
|
|
50
|
+
*/
|
|
46
51
|
--rio-shape-morph-target: var(--_radius);
|
|
47
|
-
|
|
52
|
+
--rio-shape-morph-start-target: var(--rio-shape-morph-target);
|
|
53
|
+
--rio-shape-morph-end-target: var(--rio-shape-morph-target);
|
|
54
|
+
border-start-start-radius: var(--rio-shape-morph-start-target);
|
|
55
|
+
border-end-start-radius: var(--rio-shape-morph-start-target);
|
|
56
|
+
border-start-end-radius: var(--rio-shape-morph-end-target);
|
|
57
|
+
border-end-end-radius: var(--rio-shape-morph-end-target);
|
|
48
58
|
background-color: var(--_container);
|
|
49
59
|
box-shadow: var(--_elevation);
|
|
50
60
|
color: var(--_content);
|
|
@@ -54,11 +64,20 @@
|
|
|
54
64
|
vertical-align: middle;
|
|
55
65
|
cursor: pointer;
|
|
56
66
|
user-select: none;
|
|
57
|
-
/*
|
|
67
|
+
/*
|
|
68
|
+
* No border-radius transition: corners move with the fast-spatial spring (see
|
|
69
|
+
* base/shape-morph.css). The spring targets get an instant transition instead, which is how the
|
|
70
|
+
* spring sees them change (core's spring-property). `--rio-spring-targets` is public: an app
|
|
71
|
+
* that sets its own `transition` on a button keeps the morphing by including it, and
|
|
72
|
+
* stylesheets that spring more targets on a button extend it.
|
|
73
|
+
*/
|
|
74
|
+
--rio-spring-targets:
|
|
75
|
+
--rio-shape-morph-start-target 1ms step-end, --rio-shape-morph-end-target 1ms step-end;
|
|
58
76
|
transition:
|
|
59
77
|
background-color var(--rio-motion-fast-effects),
|
|
60
78
|
color var(--rio-motion-fast-effects),
|
|
61
|
-
box-shadow var(--rio-motion-fast-effects)
|
|
79
|
+
box-shadow var(--rio-motion-fast-effects),
|
|
80
|
+
var(--rio-spring-targets);
|
|
62
81
|
}
|
|
63
82
|
|
|
64
83
|
/* Sizes */
|
|
@@ -189,7 +208,6 @@
|
|
|
189
208
|
|
|
190
209
|
.rio-button[data-pressed] {
|
|
191
210
|
--rio-shape-morph-target: var(--_pressed);
|
|
192
|
-
border-radius: var(--_pressed);
|
|
193
211
|
box-shadow: var(--_elevation);
|
|
194
212
|
}
|
|
195
213
|
|
|
@@ -237,8 +255,10 @@
|
|
|
237
255
|
/* Extra small and small buttons are shorter than the spec's 48px minimum target. */
|
|
238
256
|
.rio-button__touch {
|
|
239
257
|
position: absolute;
|
|
240
|
-
|
|
241
|
-
|
|
258
|
+
/* Physical, like `translate`: a logical start would flip in right-to-left layouts and push the
|
|
259
|
+
target off the button. */
|
|
260
|
+
top: 50%;
|
|
261
|
+
left: 50%;
|
|
242
262
|
inline-size: max(100%, 48px);
|
|
243
263
|
block-size: max(100%, 48px);
|
|
244
264
|
translate: -50% -50%;
|
package/src/dialog.css
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* M3 basic dialogs. `attachDialog` from `@rio-m3e/core` shows the `<dialog>` with `showModal()`,
|
|
3
|
+
* in the top layer above its scrim, centered in the window.
|
|
4
|
+
*
|
|
5
|
+
* Values come from Material Web's component tokens (`_md-comp-dialog.scss`, Material 3 v34.0.21)
|
|
6
|
+
* and the spec page's measurements: 280px to 560px wide, 24px padding, 16px from the icon to the
|
|
7
|
+
* headline and from the headline to the content, 24px from the content to the buttons, 8px
|
|
8
|
+
* between buttons. The scrim's color and 32% opacity come from `_md-comp-scrim.scss`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* How much of the container shows, from the top: it expands as the dialog opens. */
|
|
12
|
+
@property --_dialog-reveal {
|
|
13
|
+
syntax: '<percentage>';
|
|
14
|
+
inherits: true;
|
|
15
|
+
initial-value: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.rio-dialog {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
min-inline-size: 280px;
|
|
22
|
+
max-inline-size: min(560px, 100% - 48px);
|
|
23
|
+
max-block-size: calc(100% - 48px);
|
|
24
|
+
margin: auto;
|
|
25
|
+
padding: 24px 0;
|
|
26
|
+
border: 0;
|
|
27
|
+
border-radius: var(--rio-shape-corner-extra-large);
|
|
28
|
+
outline: none;
|
|
29
|
+
/* The container is drawn behind the content, so it can expand; its shadow reaches past the box. */
|
|
30
|
+
isolation: isolate;
|
|
31
|
+
overflow: visible;
|
|
32
|
+
background: transparent;
|
|
33
|
+
color: var(--rio-color-on-surface-variant);
|
|
34
|
+
font: var(--rio-typescale-body-medium);
|
|
35
|
+
letter-spacing: var(--rio-typescale-body-medium-tracking);
|
|
36
|
+
overflow-wrap: break-word;
|
|
37
|
+
/* Closing */
|
|
38
|
+
transition:
|
|
39
|
+
opacity var(--rio-motion-fast-effects),
|
|
40
|
+
--_dialog-reveal var(--rio-motion-fast-spatial),
|
|
41
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
42
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.rio-dialog::before {
|
|
46
|
+
content: '';
|
|
47
|
+
position: absolute;
|
|
48
|
+
z-index: -1;
|
|
49
|
+
inset: 0 0 auto;
|
|
50
|
+
block-size: var(--_dialog-reveal);
|
|
51
|
+
border-radius: inherit;
|
|
52
|
+
background-color: var(--rio-color-surface-container-high);
|
|
53
|
+
box-shadow: var(--rio-elevation-level3);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Opening: the container expands downward from the top, and its content fades in once the
|
|
58
|
+
* container is under it. Closing fades it all out as the container collapses.
|
|
59
|
+
*/
|
|
60
|
+
.rio-dialog[open] {
|
|
61
|
+
display: flex;
|
|
62
|
+
transition:
|
|
63
|
+
opacity var(--rio-motion-fast-effects),
|
|
64
|
+
--_dialog-reveal var(--rio-motion-default-spatial),
|
|
65
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
66
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.rio-dialog:not([open]) {
|
|
70
|
+
--_dialog-reveal: 35%;
|
|
71
|
+
|
|
72
|
+
opacity: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.rio-dialog > * {
|
|
76
|
+
transition: opacity var(--rio-motion-fast-effects);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.rio-dialog[open] > * {
|
|
80
|
+
transition-delay: var(--rio-motion-fast-effects-duration);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@starting-style {
|
|
84
|
+
.rio-dialog[open] {
|
|
85
|
+
--_dialog-reveal: 35%;
|
|
86
|
+
|
|
87
|
+
opacity: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.rio-dialog[open] > * {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Without spatial motion the container doesn't collapse at once as it starts fading out. */
|
|
96
|
+
@media (prefers-reduced-motion: reduce) {
|
|
97
|
+
.rio-dialog:not([open]) {
|
|
98
|
+
--_dialog-reveal: 100%;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.rio-dialog::backdrop {
|
|
103
|
+
background-color: color-mix(in srgb, var(--rio-color-scrim) 32%, transparent);
|
|
104
|
+
transition:
|
|
105
|
+
opacity var(--rio-motion-fast-effects),
|
|
106
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
107
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.rio-dialog[open]::backdrop {
|
|
111
|
+
transition:
|
|
112
|
+
opacity var(--rio-motion-default-effects),
|
|
113
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
114
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.rio-dialog:not([open])::backdrop {
|
|
118
|
+
opacity: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@starting-style {
|
|
122
|
+
.rio-dialog[open]::backdrop {
|
|
123
|
+
opacity: 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Parts */
|
|
128
|
+
|
|
129
|
+
.rio-dialog__icon {
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
flex: none;
|
|
132
|
+
align-self: center;
|
|
133
|
+
inline-size: 24px;
|
|
134
|
+
block-size: 24px;
|
|
135
|
+
margin-block-end: 16px;
|
|
136
|
+
color: var(--rio-color-secondary);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.rio-dialog__icon > :is(svg, img) {
|
|
140
|
+
inline-size: 100%;
|
|
141
|
+
block-size: 100%;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.rio-dialog__headline {
|
|
145
|
+
flex: none;
|
|
146
|
+
margin: 0 0 16px;
|
|
147
|
+
padding-inline: 24px;
|
|
148
|
+
color: var(--rio-color-on-surface);
|
|
149
|
+
font: var(--rio-typescale-headline-small);
|
|
150
|
+
letter-spacing: var(--rio-typescale-headline-small-tracking);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.rio-dialog[data-icon] .rio-dialog__headline {
|
|
154
|
+
text-align: center;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* The headline and the buttons stay put while the content scrolls. A divider shows at the edge
|
|
159
|
+
* the content scrolls past, where it meets the headline or the buttons.
|
|
160
|
+
*/
|
|
161
|
+
.rio-dialog__content {
|
|
162
|
+
--_divider-top: transparent;
|
|
163
|
+
--_divider-bottom: transparent;
|
|
164
|
+
|
|
165
|
+
flex: 1 1 auto;
|
|
166
|
+
min-block-size: 0;
|
|
167
|
+
padding-inline: 24px;
|
|
168
|
+
overflow-y: auto;
|
|
169
|
+
overscroll-behavior: contain;
|
|
170
|
+
box-shadow:
|
|
171
|
+
0 -1px var(--_divider-top),
|
|
172
|
+
0 1px var(--_divider-bottom);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.rio-dialog__content[data-overflow-top]:not(:first-child) {
|
|
176
|
+
--_divider-top: var(--rio-color-outline);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.rio-dialog__content[data-overflow-bottom]:not(:last-child) {
|
|
180
|
+
--_divider-bottom: var(--rio-color-outline);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* Buttons sit at the end, the confirming one last, nearest the edge. When they don't fit side by
|
|
185
|
+
* side, they stack with the confirming one on top.
|
|
186
|
+
*/
|
|
187
|
+
.rio-dialog__actions {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex: none;
|
|
190
|
+
flex-wrap: wrap-reverse;
|
|
191
|
+
justify-content: flex-end;
|
|
192
|
+
gap: 8px;
|
|
193
|
+
margin-block-start: 24px;
|
|
194
|
+
padding-inline: 24px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@media (forced-colors: active) {
|
|
198
|
+
.rio-dialog {
|
|
199
|
+
outline: 2px solid CanvasText;
|
|
200
|
+
}
|
|
201
|
+
}
|
package/src/menu.css
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* M3E vertical menu. `attachMenu` from `@rio-m3e/core` shows the menu in the top layer as a
|
|
3
|
+
* popover and places it next to its anchor (`base/anchored-position.css`), which also says where
|
|
4
|
+
* it ended up (`data-side`, `data-align`) and how much room there is.
|
|
5
|
+
*
|
|
6
|
+
* Values come from Material Web's component tokens (`_md-comp-menus.scss`, `-standard`,
|
|
7
|
+
* `-vibrant`, Material 3 v34.0.21). The spacing around the items comes from the M3 Design Kit: a
|
|
8
|
+
* 48px row per item, with the 44px item inset 2px above and below and 4px at the sides, so items
|
|
9
|
+
* sit 4px apart and 4px from the container's edges.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.rio-menu {
|
|
13
|
+
--_container: var(--rio-color-surface-container-low);
|
|
14
|
+
--_content: var(--rio-color-on-surface);
|
|
15
|
+
--_icon: var(--rio-color-on-surface-variant);
|
|
16
|
+
--_supporting: var(--rio-color-on-surface-variant);
|
|
17
|
+
--_selected-container: var(--rio-color-tertiary-container);
|
|
18
|
+
--_selected-content: var(--rio-color-on-tertiary-container);
|
|
19
|
+
|
|
20
|
+
/* Only while open: a `display` here would override the browser's hiding of closed popovers. */
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
/* Not in the tokens: the baseline menu's limits */
|
|
24
|
+
min-inline-size: 112px;
|
|
25
|
+
max-inline-size: min(280px, var(--rio-anchored-available-width, 280px));
|
|
26
|
+
max-block-size: var(--rio-anchored-available-height, none);
|
|
27
|
+
padding: 2px 4px;
|
|
28
|
+
overflow-x: hidden;
|
|
29
|
+
overflow-y: auto;
|
|
30
|
+
border: 0;
|
|
31
|
+
border-radius: var(--rio-shape-corner-large);
|
|
32
|
+
background-color: var(--_container);
|
|
33
|
+
color: var(--_content);
|
|
34
|
+
box-shadow: var(--rio-elevation-level2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.rio-menu:where([data-variant='vibrant']) {
|
|
38
|
+
--_container: var(--rio-color-tertiary-container);
|
|
39
|
+
--_content: var(--rio-color-on-tertiary-container);
|
|
40
|
+
--_icon: var(--rio-color-on-tertiary-container);
|
|
41
|
+
--_supporting: var(--rio-color-on-tertiary-container);
|
|
42
|
+
--_selected-container: var(--rio-color-tertiary);
|
|
43
|
+
--_selected-content: var(--rio-color-on-tertiary);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Opening: the menu expands from the side facing its anchor and fades in; closing reverses it.
|
|
48
|
+
* Without `transition-behavior: allow-discrete` support for `overlay`, closing is instant.
|
|
49
|
+
*/
|
|
50
|
+
.rio-menu {
|
|
51
|
+
--_origin-block: top;
|
|
52
|
+
--_origin-inline: left;
|
|
53
|
+
|
|
54
|
+
transform-origin: var(--_origin-inline) var(--_origin-block);
|
|
55
|
+
transition:
|
|
56
|
+
opacity var(--rio-motion-fast-effects),
|
|
57
|
+
scale var(--rio-motion-fast-spatial),
|
|
58
|
+
border-radius var(--rio-motion-fast-spatial),
|
|
59
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
60
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* With a submenu open, the menu the pointer or the focus is in keeps the active container shape
|
|
65
|
+
* (corner large), and the others take the inactive one (corner small), on the `fast-spatial` curve.
|
|
66
|
+
* `attachMenu` marks them with `data-inactive`.
|
|
67
|
+
*/
|
|
68
|
+
.rio-menu[data-inactive] {
|
|
69
|
+
border-radius: var(--rio-shape-corner-small);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.rio-menu:popover-open {
|
|
73
|
+
display: flex;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.rio-menu:not(:popover-open) {
|
|
77
|
+
opacity: 0;
|
|
78
|
+
scale: 1 0.8;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@starting-style {
|
|
82
|
+
.rio-menu:popover-open {
|
|
83
|
+
opacity: 0;
|
|
84
|
+
scale: 1 0.8;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.rio-menu[data-side='top'] {
|
|
89
|
+
--_origin-block: bottom;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.rio-menu:is([data-side='start'], [data-side='end']) {
|
|
93
|
+
--_origin-block: top;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.rio-menu:is([data-align='end'], [data-side='start']):dir(ltr),
|
|
97
|
+
.rio-menu:not([data-align='end'], [data-side='start']):dir(rtl) {
|
|
98
|
+
--_origin-inline: right;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.rio-menu[data-instant] {
|
|
102
|
+
transition: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Items */
|
|
106
|
+
|
|
107
|
+
.rio-menu-item {
|
|
108
|
+
--_item-content: var(--_content);
|
|
109
|
+
--_item-icon: var(--_icon);
|
|
110
|
+
--_item-supporting: var(--_supporting);
|
|
111
|
+
--_item-container: transparent;
|
|
112
|
+
--_corner-block-start: var(--rio-shape-corner-extra-small);
|
|
113
|
+
--_corner-block-end: var(--rio-shape-corner-extra-small);
|
|
114
|
+
|
|
115
|
+
display: flex;
|
|
116
|
+
flex: none;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 12px;
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
inline-size: 100%;
|
|
121
|
+
min-block-size: 44px;
|
|
122
|
+
margin: 2px 0;
|
|
123
|
+
padding: 8px 12px;
|
|
124
|
+
border: 0;
|
|
125
|
+
border-start-start-radius: var(--_corner-block-start);
|
|
126
|
+
border-start-end-radius: var(--_corner-block-start);
|
|
127
|
+
border-end-start-radius: var(--_corner-block-end);
|
|
128
|
+
border-end-end-radius: var(--_corner-block-end);
|
|
129
|
+
background-color: var(--_item-container);
|
|
130
|
+
color: var(--_item-content);
|
|
131
|
+
font: var(--rio-typescale-label-large);
|
|
132
|
+
letter-spacing: var(--rio-typescale-label-large-tracking);
|
|
133
|
+
text-align: start;
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
user-select: none;
|
|
136
|
+
transition:
|
|
137
|
+
background-color var(--rio-motion-fast-effects),
|
|
138
|
+
color var(--rio-motion-fast-effects),
|
|
139
|
+
border-radius var(--rio-motion-fast-spatial);
|
|
140
|
+
--rio-focus-ring-color: var(--rio-color-secondary);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* The first and last items of a menu or group round their outer corners to sit inside the container. */
|
|
144
|
+
.rio-menu-item:nth-child(1 of .rio-menu-item) {
|
|
145
|
+
--_corner-block-start: var(--rio-shape-corner-medium);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.rio-menu-item:nth-last-child(1 of .rio-menu-item) {
|
|
149
|
+
--_corner-block-end: var(--rio-shape-corner-medium);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.rio-menu-item[data-selected='true'] {
|
|
153
|
+
--_item-content: var(--_selected-content);
|
|
154
|
+
--_item-icon: var(--_selected-content);
|
|
155
|
+
--_item-supporting: var(--_selected-content);
|
|
156
|
+
--_item-container: var(--_selected-container);
|
|
157
|
+
--_corner-block-start: var(--rio-shape-corner-medium);
|
|
158
|
+
--_corner-block-end: var(--rio-shape-corner-medium);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* The item whose submenu is open is active, with the hovered state layer
|
|
163
|
+
* (`menu-item.active.state-layer`), where `base/state-layer.css` would show a menu button's open
|
|
164
|
+
* menu as pressed. With the focus on it, that pressed layer stays, of the same strength as focus.
|
|
165
|
+
*/
|
|
166
|
+
.rio-menu-item.rio-state-layer[aria-haspopup][aria-expanded='true']:not(:focus-visible)::before {
|
|
167
|
+
opacity: var(--rio-state-hover-state-layer-opacity);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.rio-menu-item[aria-disabled='true'] {
|
|
171
|
+
cursor: default;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.rio-menu-item[aria-disabled='true'] > :not(.rio-menu-item__touch) {
|
|
175
|
+
opacity: 0.38;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.rio-menu-item[aria-disabled='true'][data-selected='true'] {
|
|
179
|
+
--_item-container: color-mix(in srgb, var(--_selected-container) 38%, transparent);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.rio-menu-item__touch {
|
|
183
|
+
position: absolute;
|
|
184
|
+
inset: -2px 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.rio-menu-item__icon,
|
|
188
|
+
.rio-menu-item__trailing-icon {
|
|
189
|
+
display: inline-flex;
|
|
190
|
+
flex: none;
|
|
191
|
+
inline-size: 20px;
|
|
192
|
+
block-size: 20px;
|
|
193
|
+
color: var(--_item-icon);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.rio-menu-item__icon > :is(svg, img),
|
|
197
|
+
.rio-menu-item__trailing-icon > :is(svg, img) {
|
|
198
|
+
inline-size: 100%;
|
|
199
|
+
block-size: 100%;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.rio-menu-item__text {
|
|
203
|
+
display: flex;
|
|
204
|
+
flex: 1;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
min-inline-size: 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.rio-menu-item__label {
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
text-overflow: ellipsis;
|
|
212
|
+
white-space: nowrap;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.rio-menu-item__supporting-text {
|
|
216
|
+
color: var(--_item-supporting);
|
|
217
|
+
font: var(--rio-typescale-body-small);
|
|
218
|
+
letter-spacing: var(--rio-typescale-body-small-tracking);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.rio-menu-item__trailing-text {
|
|
222
|
+
flex: none;
|
|
223
|
+
color: var(--_item-supporting);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Groups and dividers */
|
|
227
|
+
|
|
228
|
+
.rio-menu-group__items {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.rio-menu-group__label {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
min-block-size: 32px;
|
|
237
|
+
padding-inline: 12px;
|
|
238
|
+
color: var(--_supporting);
|
|
239
|
+
font: var(--rio-typescale-label-large);
|
|
240
|
+
letter-spacing: var(--rio-typescale-label-large-tracking);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Not in the vertical menu's tokens: the baseline menu's divider, 1px of surface variant */
|
|
244
|
+
.rio-menu-divider {
|
|
245
|
+
flex: none;
|
|
246
|
+
block-size: 1px;
|
|
247
|
+
margin: 4px 12px;
|
|
248
|
+
background-color: var(--rio-color-surface-variant);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@media (forced-colors: active) {
|
|
252
|
+
.rio-menu {
|
|
253
|
+
border: 1px solid CanvasText;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* M3E split button: a leading button and a trailing menu button, 2px apart. Both are buttons
|
|
3
|
+
* (`button.css`), so they keep the button's sizes, colors, elevation, disabled rules, state layer,
|
|
4
|
+
* ripple, and touch target. This file adds what differs: the paddings, the trailing button's
|
|
5
|
+
* width, the corners, and the menu icon. `createSplitButton` from `@rio-m3e/core` sets the
|
|
6
|
+
* attributes, and the buttons carry `data-size`, `data-variant`, and, on the trailing button,
|
|
7
|
+
* `data-open`.
|
|
8
|
+
*
|
|
9
|
+
* Values come from Material Web's component tokens (`_md-comp-split-button-*.scss`, Material 3
|
|
10
|
+
* v34.0.21). The menu icon's offset comes from the spec page's measurements, and its direction
|
|
11
|
+
* (toward the start) from the M3 Design Kit.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
@property --rio-split-button-menu-turn-target {
|
|
15
|
+
syntax: '<angle>';
|
|
16
|
+
inherits: false;
|
|
17
|
+
initial-value: 0deg;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@property --rio-split-button-menu-shift-target {
|
|
21
|
+
syntax: '<length>';
|
|
22
|
+
inherits: false;
|
|
23
|
+
initial-value: 0px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.rio-split-button {
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 2px;
|
|
30
|
+
vertical-align: middle;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Both buttons: the corners at the ends of the split button are round, and the facing ones are
|
|
35
|
+
* small. Hovering, focusing, or pressing a button rounds its facing corners more, and opening the
|
|
36
|
+
* menu rounds the trailing button fully. Each side springs on its own (see base/shape-morph.css).
|
|
37
|
+
*/
|
|
38
|
+
.rio-split-button > .rio-button {
|
|
39
|
+
/* Small */
|
|
40
|
+
--_lead-start: 16px;
|
|
41
|
+
--_lead-end: 12px;
|
|
42
|
+
--_menu-size: 22px;
|
|
43
|
+
--_menu-padding: 13px;
|
|
44
|
+
--_menu-offset: 1px;
|
|
45
|
+
--_inner: var(--rio-shape-corner-extra-small);
|
|
46
|
+
--_inner-active: var(--rio-shape-corner-medium);
|
|
47
|
+
|
|
48
|
+
--_inner-now: var(--_inner);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* `[data-variant]` is always there; it lifts this above button.css's per-side defaults in any
|
|
53
|
+
* order. The corners show these targets (button.css).
|
|
54
|
+
*/
|
|
55
|
+
.rio-split-button > .rio-button[data-variant] {
|
|
56
|
+
--rio-shape-morph-start-target: var(--_start);
|
|
57
|
+
--rio-shape-morph-end-target: var(--_end);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.rio-split-button > .rio-button:where([data-size='xsmall']) {
|
|
61
|
+
--_lead-start: 12px;
|
|
62
|
+
--_lead-end: 10px;
|
|
63
|
+
--_inner-active: var(--rio-shape-corner-small);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.rio-split-button > .rio-button:where([data-size='medium']) {
|
|
67
|
+
--_lead-start: 24px;
|
|
68
|
+
--_lead-end: 24px;
|
|
69
|
+
--_menu-size: 26px;
|
|
70
|
+
--_menu-padding: 15px;
|
|
71
|
+
--_menu-offset: 2px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.rio-split-button > .rio-button:where([data-size='large']) {
|
|
75
|
+
--_lead-start: 48px;
|
|
76
|
+
--_lead-end: 48px;
|
|
77
|
+
--_menu-size: 38px;
|
|
78
|
+
--_menu-padding: 29px;
|
|
79
|
+
--_menu-offset: 3px;
|
|
80
|
+
--_inner: var(--rio-shape-corner-small);
|
|
81
|
+
--_inner-active: var(--rio-shape-corner-large-increased);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.rio-split-button > .rio-button:where([data-size='xlarge']) {
|
|
85
|
+
--_lead-start: 64px;
|
|
86
|
+
--_lead-end: 64px;
|
|
87
|
+
--_menu-size: 50px;
|
|
88
|
+
--_menu-padding: 43px;
|
|
89
|
+
--_menu-offset: 6px;
|
|
90
|
+
--_inner: var(--rio-shape-corner-medium);
|
|
91
|
+
--_inner-active: var(--rio-shape-corner-large-increased);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (hover: hover) {
|
|
95
|
+
.rio-split-button > .rio-button:hover {
|
|
96
|
+
--_inner-now: var(--_inner-active);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.rio-split-button > .rio-button:is(:focus-visible, [data-pressed]) {
|
|
101
|
+
--_inner-now: var(--_inner-active);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* Disabled buttons don't react, so their corners stay put. Being open still shows (that rule comes
|
|
106
|
+
* later): a disabled toggle button keeps its selected shape too.
|
|
107
|
+
*/
|
|
108
|
+
.rio-split-button > .rio-button:is(:disabled, [aria-disabled='true']) {
|
|
109
|
+
--_inner-now: var(--_inner);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Leading button */
|
|
113
|
+
|
|
114
|
+
.rio-split-button > .rio-split-button__leading {
|
|
115
|
+
--_start: var(--_round);
|
|
116
|
+
--_end: var(--_inner-now);
|
|
117
|
+
|
|
118
|
+
padding-inline: var(--_lead-start) var(--_lead-end);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Trailing button: a square, or 48px wide when shorter, with the menu icon in it */
|
|
122
|
+
|
|
123
|
+
.rio-split-button > .rio-split-button__trailing {
|
|
124
|
+
--_start: var(--_inner-now);
|
|
125
|
+
--_end: var(--_round);
|
|
126
|
+
--rio-split-button-menu-shift-target: var(--_menu-offset);
|
|
127
|
+
/* Toward the start: left, or right in right-to-left layouts */
|
|
128
|
+
--_start-sign: -1;
|
|
129
|
+
|
|
130
|
+
inline-size: calc(var(--_menu-size) + 2 * var(--_menu-padding));
|
|
131
|
+
padding: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.rio-split-button > .rio-split-button__trailing:dir(rtl) {
|
|
135
|
+
--_start-sign: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* Open: fully round, with the icon centered and turned half a circle, its point swinging through
|
|
140
|
+
* the start side, toward the leading button. The color doesn't change; a state layer shows
|
|
141
|
+
* instead, at the pressed opacity.
|
|
142
|
+
*/
|
|
143
|
+
.rio-split-button > .rio-split-button__trailing[data-open] {
|
|
144
|
+
--_inner-now: var(--_round);
|
|
145
|
+
--rio-split-button-menu-turn-target: 180deg;
|
|
146
|
+
--rio-split-button-menu-shift-target: 0px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.rio-split-button > .rio-split-button__trailing[data-open]:dir(rtl) {
|
|
150
|
+
--rio-split-button-menu-turn-target: -180deg;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.rio-split-button
|
|
154
|
+
> .rio-split-button__trailing[data-open]:not(:disabled, [aria-disabled='true'])::before {
|
|
155
|
+
opacity: var(--rio-state-pressed-state-layer-opacity);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/*
|
|
159
|
+
* The springs in `attachSplitButtonMenuIcon` write `--_menu-turn` and `--_menu-shift` while they
|
|
160
|
+
* move; at rest the targets apply. The targets are registered and don't inherit, so they are
|
|
161
|
+
* passed down through these.
|
|
162
|
+
*/
|
|
163
|
+
.rio-split-button > .rio-split-button__trailing {
|
|
164
|
+
--_menu-turn-rest: var(--rio-split-button-menu-turn-target);
|
|
165
|
+
--_menu-shift-rest: var(--rio-split-button-menu-shift-target);
|
|
166
|
+
/* The button's spring targets (button.css), and the menu icon's. */
|
|
167
|
+
--rio-spring-targets:
|
|
168
|
+
--rio-shape-morph-start-target 1ms step-end, --rio-shape-morph-end-target 1ms step-end,
|
|
169
|
+
--rio-split-button-menu-turn-target 1ms step-end,
|
|
170
|
+
--rio-split-button-menu-shift-target 1ms step-end;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.rio-split-button__menu-icon {
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
flex: none;
|
|
176
|
+
inline-size: var(--_menu-size);
|
|
177
|
+
block-size: var(--_menu-size);
|
|
178
|
+
rotate: var(--_menu-turn, var(--_menu-turn-rest));
|
|
179
|
+
translate: calc(var(--_menu-shift, var(--_menu-shift-rest)) * var(--_start-sign)) 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.rio-split-button__menu-icon > :is(svg, img) {
|
|
183
|
+
inline-size: 100%;
|
|
184
|
+
block-size: 100%;
|
|
185
|
+
}
|
package/src/switch.css
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Values come from Material Web's component tokens (`_md-comp-switch.scss`, Material 3 v34.0.21),
|
|
5
5
|
* which match the spec's measurements table. The handle's center and size are declared as targets
|
|
6
6
|
* for each state; `attachSwitchHandle` from `@rio-m3e/core` springs `--_handle-x` and
|
|
7
|
-
* `--_handle-size` toward them
|
|
7
|
+
* `--_handle-size` toward them, which show the targets at rest. The targets get an instant
|
|
8
|
+
* transition so the springs see them change. Without JavaScript the handle simply jumps.
|
|
8
9
|
*
|
|
9
10
|
* The root carries `data-selected`, plus `data-pressed` (from the ripple), `data-dragging` (from
|
|
10
11
|
* the drag), and `data-icon` when the handle shows an icon. State rules sit inside `:where()` so
|
|
@@ -72,9 +73,14 @@
|
|
|
72
73
|
/* Horizontal drags belong to the handle; vertical ones still scroll the page. */
|
|
73
74
|
touch-action: pan-y;
|
|
74
75
|
-webkit-tap-highlight-color: transparent;
|
|
76
|
+
/* The handle's spring targets get an instant transition, which is how the springs see them
|
|
77
|
+
change (core's spring-property). Public, as on the button (button.css). */
|
|
78
|
+
--rio-spring-targets:
|
|
79
|
+
--rio-switch-handle-x-target 1ms step-end, --rio-switch-handle-size-target 1ms step-end;
|
|
75
80
|
transition:
|
|
76
81
|
background-color var(--rio-motion-fast-effects),
|
|
77
|
-
border-color var(--rio-motion-fast-effects)
|
|
82
|
+
border-color var(--rio-motion-fast-effects),
|
|
83
|
+
var(--rio-spring-targets);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/* States */
|
|
@@ -196,8 +202,10 @@
|
|
|
196
202
|
/* The track is 32px tall, below the spec's 48px minimum target. */
|
|
197
203
|
.rio-switch__touch {
|
|
198
204
|
position: absolute;
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
/* Physical, like `translate`: a logical start would flip in right-to-left layouts and push the
|
|
206
|
+
target off the switch. */
|
|
207
|
+
top: 50%;
|
|
208
|
+
left: 50%;
|
|
201
209
|
inline-size: max(100%, 48px);
|
|
202
210
|
block-size: max(100%, 48px);
|
|
203
211
|
translate: -50% -50%;
|
package/src/tooltip.css
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* M3 tooltips, plain and rich. `attachTooltip` from `@rio-m3e/core` shows the tooltip in the top
|
|
3
|
+
* layer as a popover and places it next to its element (`base/anchored-position.css`).
|
|
4
|
+
*
|
|
5
|
+
* Values come from Material Web's component tokens (`_md-comp-plain-tooltip.scss`,
|
|
6
|
+
* `_md-comp-rich-tooltip.scss`, Material 3 v34.0.21) and the spec page. Sizes the tokens leave out
|
|
7
|
+
* come from the M3 Design Kit: a plain tooltip at most 200px wide, a rich one at most 312px, with
|
|
8
|
+
* 4px between subhead and text and 12px from the text to the actions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* Wraps the element and the tooltip without a box, so the element lays out as if unwrapped. */
|
|
12
|
+
.rio-tooltip-trigger {
|
|
13
|
+
display: contents;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.rio-tooltip {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
max-inline-size: var(--rio-anchored-available-width, none);
|
|
19
|
+
border: 0;
|
|
20
|
+
overflow-wrap: break-word;
|
|
21
|
+
transition:
|
|
22
|
+
opacity var(--rio-motion-fast-effects),
|
|
23
|
+
overlay var(--rio-motion-fast-effects) allow-discrete,
|
|
24
|
+
display var(--rio-motion-fast-effects) allow-discrete;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Only while open: a `display` here would override the browser's hiding of closed popovers. */
|
|
28
|
+
.rio-tooltip:popover-open {
|
|
29
|
+
display: block;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.rio-tooltip:not(:popover-open) {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@starting-style {
|
|
37
|
+
.rio-tooltip:popover-open {
|
|
38
|
+
opacity: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Plain: a brief label on the inverse surface, one 24px line high */
|
|
43
|
+
|
|
44
|
+
.rio-tooltip[data-variant='plain'] {
|
|
45
|
+
max-inline-size: min(200px, var(--rio-anchored-available-width, 200px));
|
|
46
|
+
min-block-size: 24px;
|
|
47
|
+
padding: 4px 8px;
|
|
48
|
+
border-radius: var(--rio-shape-corner-extra-small);
|
|
49
|
+
background-color: var(--rio-color-inverse-surface);
|
|
50
|
+
color: var(--rio-color-inverse-on-surface);
|
|
51
|
+
font: var(--rio-typescale-body-small);
|
|
52
|
+
letter-spacing: var(--rio-typescale-body-small-tracking);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Rich: a card with an optional subhead, the text, and up to two text buttons */
|
|
56
|
+
|
|
57
|
+
.rio-tooltip[data-variant='rich'] {
|
|
58
|
+
max-inline-size: min(312px, var(--rio-anchored-available-width, 312px));
|
|
59
|
+
padding: 12px 16px 8px;
|
|
60
|
+
border-radius: var(--rio-shape-corner-medium);
|
|
61
|
+
background-color: var(--rio-color-surface-container);
|
|
62
|
+
color: var(--rio-color-on-surface-variant);
|
|
63
|
+
box-shadow: var(--rio-elevation-level2);
|
|
64
|
+
font: var(--rio-typescale-body-medium);
|
|
65
|
+
letter-spacing: var(--rio-typescale-body-medium-tracking);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.rio-tooltip__subhead {
|
|
69
|
+
display: block;
|
|
70
|
+
margin-block-end: 4px;
|
|
71
|
+
font: var(--rio-typescale-title-small);
|
|
72
|
+
letter-spacing: var(--rio-typescale-title-small-tracking);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.rio-tooltip[data-variant='rich'] .rio-tooltip__supporting-text {
|
|
76
|
+
display: block;
|
|
77
|
+
padding-block-end: 4px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* The buttons sit 4px in from the container's edge. */
|
|
81
|
+
.rio-tooltip__actions {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-wrap: wrap;
|
|
84
|
+
gap: 8px;
|
|
85
|
+
margin-block-start: 8px;
|
|
86
|
+
margin-inline: -12px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@media (forced-colors: active) {
|
|
90
|
+
.rio-tooltip {
|
|
91
|
+
border: 1px solid CanvasText;
|
|
92
|
+
}
|
|
93
|
+
}
|