@rio-m3e/styles 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rio-m3e/styles",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-agnostic component styles for Rio",
5
5
  "license": "Apache-2.0",
6
6
  "author": "LegenLee",
@@ -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, and sets
4
- * `border-radius` to the same value, so it still works without JavaScript. The primitive reads
5
- * the target in px, which needs the registration below, and animates the inline radius with a
6
- * spring. It marks the element with `data-shape-morph`.
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
+ }
@@ -41,7 +41,8 @@
41
41
  opacity: var(--rio-state-pressed-state-layer-opacity);
42
42
  }
43
43
 
44
- .rio-state-layer[data-dragged]::before {
44
+ /* `attachDrag` from `@rio-m3e/core` marks an element with `data-dragging` while it moves. */
45
+ .rio-state-layer[data-dragging]::before {
45
46
  opacity: var(--rio-state-dragged-state-layer-opacity);
46
47
  }
47
48
 
@@ -0,0 +1,98 @@
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
+
39
+ .rio-button-group:where([data-variant='connected']) {
40
+ --_between: 2px;
41
+
42
+ display: flex;
43
+ }
44
+
45
+ .rio-button-group[data-variant='connected'] > .rio-button {
46
+ /* Small, medium, and extra small */
47
+ --_inner: var(--rio-shape-corner-small);
48
+ --_inner-pressed: var(--rio-shape-corner-extra-small);
49
+ /* Corners that face another button, and corners at the ends of the row */
50
+ --_inner-now: var(--_inner);
51
+ --_outer: var(--_round);
52
+ --_start: var(--_inner-now);
53
+ --_end: var(--_inner-now);
54
+
55
+ flex: 1 1 0;
56
+ /* The corners show these targets (button.css). */
57
+ --rio-shape-morph-start-target: var(--_start);
58
+ --rio-shape-morph-end-target: var(--_end);
59
+ }
60
+
61
+ .rio-button-group[data-variant='connected'] > .rio-button:where([data-size='large']) {
62
+ --_inner: var(--rio-shape-corner-large);
63
+ --_inner-pressed: var(--rio-shape-corner-medium);
64
+ }
65
+
66
+ .rio-button-group[data-variant='connected'] > .rio-button:where([data-size='xlarge']) {
67
+ --_inner: var(--rio-shape-corner-large-increased);
68
+ --_inner-pressed: var(--rio-shape-corner-large);
69
+ }
70
+
71
+ /* A square group's ends use the same radius as its facing corners. */
72
+ .rio-button-group[data-variant='connected'] > .rio-button[data-shape='square'] {
73
+ --_outer: var(--_inner);
74
+ }
75
+
76
+ .rio-button-group[data-variant='connected'] > .rio-button:first-child {
77
+ --_start: var(--_outer);
78
+ }
79
+
80
+ .rio-button-group[data-variant='connected'] > .rio-button:last-child {
81
+ --_end: var(--_outer);
82
+ }
83
+
84
+ .rio-button-group[data-variant='connected'] > .rio-button[data-selected='true'] {
85
+ --_inner-now: var(--_round);
86
+ --_outer: var(--_round);
87
+ }
88
+
89
+ /* After selected, so a pressed selected button still tightens its facing corners */
90
+ .rio-button-group[data-variant='connected'] > .rio-button[data-pressed] {
91
+ --_inner-now: var(--_inner-pressed);
92
+ }
93
+
94
+ /* Extra small and small icon buttons keep a 48px minimum width, matching their target. */
95
+ .rio-button-group[data-variant='connected']
96
+ > .rio-icon-button:where([data-size='xsmall'], [data-size='small']) {
97
+ min-inline-size: 48px;
98
+ }
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
- /* The shape-morph primitive springs the inline radius toward this target. */
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
- border-radius: var(--_radius);
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
- /* No border-radius transition: corners move with the fast-spatial spring (see base/shape-morph.css). */
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
- inset-block-start: 50%;
241
- inset-inline-start: 50%;
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%;
@@ -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. Without JavaScript the handle simply jumps.
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
- inset-block-start: 50%;
200
- inset-inline-start: 50%;
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%;