@expressivecss/expressive 0.7.0 → 0.9.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.
Files changed (122) hide show
  1. package/CHANGELOG.md +1313 -1
  2. package/README.md +76 -30
  3. package/dist/css/expressive.css +4896 -1777
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/LICENSE-material-symbols +93 -0
  8. package/dist/fonts/LICENSE-noto-sans +93 -0
  9. package/dist/fonts/LICENSE-roboto +93 -0
  10. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  11. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  12. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  13. package/dist/fonts/noto-sans-latin-400.woff2 +0 -0
  14. package/dist/fonts/noto-sans-latin-500.woff2 +0 -0
  15. package/dist/fonts/roboto-latin-400.woff2 +0 -0
  16. package/dist/fonts/roboto-latin-500.woff2 +0 -0
  17. package/dist/js/expressive.cjs +1673 -1338
  18. package/dist/js/expressive.cjs.map +4 -4
  19. package/dist/js/expressive.js +1673 -1338
  20. package/dist/js/expressive.js.map +4 -4
  21. package/dist/js/expressive.min.js +13 -13
  22. package/dist/js/expressive.min.js.map +4 -4
  23. package/dist/js/expressive.mjs +1673 -1338
  24. package/dist/js/expressive.mjs.map +4 -4
  25. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  26. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  27. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  28. package/dist/types/components/appBar.d.ts +26 -0
  29. package/dist/types/components/autocomplete.d.ts +15 -3
  30. package/dist/types/components/buttonGroup.d.ts +39 -0
  31. package/dist/types/components/buttons.d.ts +5 -1
  32. package/dist/types/components/cards.d.ts +11 -13
  33. package/dist/types/components/carousel.d.ts +87 -53
  34. package/dist/types/components/chips.d.ts +21 -1
  35. package/dist/types/components/expandingCard.d.ts +31 -0
  36. package/dist/types/components/index.d.ts +23 -20
  37. package/dist/types/components/lightbox.d.ts +1 -0
  38. package/dist/types/components/menu.d.ts +11 -1
  39. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  40. package/dist/types/components/registry.d.ts +18 -8
  41. package/dist/types/components/slider.d.ts +26 -85
  42. package/dist/types/components/snackbar.d.ts +20 -5
  43. package/dist/types/components/tabs.d.ts +12 -0
  44. package/dist/types/components/timepicker.d.ts +12 -10
  45. package/dist/types/components/tooltip.d.ts +1 -0
  46. package/dist/types/core/utils.d.ts +64 -53
  47. package/dist/types/index.d.ts +8 -9
  48. package/package.json +39 -3
  49. package/src/sass/README.md +61 -11
  50. package/src/sass/abstracts/_breakpoints.scss +17 -19
  51. package/src/sass/abstracts/_mixins.scss +155 -21
  52. package/src/sass/abstracts/_variables.scss +69 -5
  53. package/src/sass/base/_fonts.scss +63 -0
  54. package/src/sass/base/_global.scss +6 -24
  55. package/src/sass/base/_grid.scss +15 -12
  56. package/src/sass/base/_normalize.scss +34 -38
  57. package/src/sass/base/_typography.scss +4 -18
  58. package/src/sass/components/_badges.scss +3 -5
  59. package/src/sass/components/_banners.scss +298 -0
  60. package/src/sass/components/_bottom-app-bar.scss +180 -0
  61. package/src/sass/components/_bottom-sheet.scss +12 -4
  62. package/src/sass/components/_button-groups.scss +358 -0
  63. package/src/sass/components/_buttons.scss +245 -92
  64. package/src/sass/components/_cards.scss +514 -78
  65. package/src/sass/components/_carousel.scss +562 -68
  66. package/src/sass/components/_chips.scss +64 -5
  67. package/src/sass/components/_datepicker.scss +24 -24
  68. package/src/sass/components/_dialog.scss +17 -11
  69. package/src/sass/components/_drag-handle.scss +130 -0
  70. package/src/sass/components/_expanding-card.scss +226 -0
  71. package/src/sass/components/_fab-menu.scss +232 -0
  72. package/src/sass/components/_floating-sheet.scss +86 -0
  73. package/src/sass/components/_icon-buttons.scss +172 -0
  74. package/src/sass/components/_icons-material-design.scss +22 -10
  75. package/src/sass/components/_index.scss +22 -6
  76. package/src/sass/components/_list.scss +16 -13
  77. package/src/sass/components/_loading-indicator.scss +106 -0
  78. package/src/sass/components/_menu.scss +335 -61
  79. package/src/sass/components/_navbar.scss +245 -45
  80. package/src/sass/components/_navigation-bar.scss +22 -11
  81. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  82. package/src/sass/components/_navigation-rail.scss +31 -23
  83. package/src/sass/components/_page-footer.scss +19 -16
  84. package/src/sass/components/_pagination.scss +24 -6
  85. package/src/sass/components/_panes.scss +52 -11
  86. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  87. package/src/sass/components/_scrim.scss +37 -0
  88. package/src/sass/components/_search.scss +259 -0
  89. package/src/sass/components/_segmented-buttons.scss +188 -0
  90. package/src/sass/components/_side-sheet.scss +2 -2
  91. package/src/sass/components/_snackbar.scss +20 -15
  92. package/src/sass/components/_split-button.scss +196 -0
  93. package/src/sass/components/_tabs.scss +7 -8
  94. package/src/sass/components/_timepicker.scss +67 -58
  95. package/src/sass/components/_toolbar.scss +206 -80
  96. package/src/sass/components/_transitions.scss +8 -1
  97. package/src/sass/components/forms/_checkboxes.scss +49 -12
  98. package/src/sass/components/forms/_fieldset.scss +4 -4
  99. package/src/sass/components/forms/_file-input.scss +8 -1
  100. package/src/sass/components/forms/_index.scss +1 -1
  101. package/src/sass/components/forms/_input-fields.scss +50 -44
  102. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  103. package/src/sass/components/forms/_select.scss +38 -1
  104. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  105. package/src/sass/components/forms/_switches.scss +6 -7
  106. package/src/sass/expressive.scss +5 -0
  107. package/src/sass/tokens/_index.scss +4 -1
  108. package/src/sass/tokens/_reference.scss +70 -61
  109. package/src/sass/tokens/_state.scss +24 -0
  110. package/src/sass/tokens/_theme.scss +6 -3
  111. package/src/sass/tokens/_vibrant.scss +76 -0
  112. package/src/sass/utilities/_colors.scss +14 -1
  113. package/src/sass/utilities/_helpers.scss +5 -5
  114. package/src/sass/utilities/_visibility.scss +48 -26
  115. package/dist/types/behaviors/waves.d.ts +0 -15
  116. package/dist/types/components/parallax.d.ts +0 -16
  117. package/dist/types/components/range.d.ts +0 -43
  118. package/dist/types/core/bounding.d.ts +0 -6
  119. package/dist/types/core/edges.d.ts +0 -6
  120. package/src/sass/components/_parallax.scss +0 -104
  121. package/src/sass/components/_pulse.scss +0 -37
  122. package/src/sass/components/_slider.scss +0 -129
@@ -2,63 +2,102 @@
2
2
 
3
3
  // Toolbars (M3 Expressive).
4
4
  //
5
- // Anatomy is the HTML. A <nav class="toolbar"> is the bar. Direct
5
+ // Anatomy is the HTML. A <div class="toolbar"> is the bar. Direct
6
6
  // <button> / <a> children are the actions. An <i> is the icon; wrap a
7
7
  // label in <span>. .active marks the selected action. A .filled action
8
8
  // is the emphasized control (the in-bar FAB). Helpers are only for
9
- // variants markup cannot express: .docked, .vibrant / .filled (on the
10
- // nav), .vertical, .fixed, .top, .bottom.
9
+ // variants markup cannot express: .docked, .vibrant, .vertical, .fixed,
10
+ // .top, .bottom.
11
11
  //
12
- // Two variants:
13
- // floating default. Hugs its actions, 64dp, 32dp stadium,
14
- // surface-container, elevation 2. Horizontal or .vertical.
15
- // docked .docked (or .max). Full width, 64dp, square, no
16
- // elevation. Replaces the bottom app bar.
12
+ // Four variants, from two independent axes - shape and color:
17
13
  //
18
- // Color: standard (surface-container) or .vibrant (primary-container).
19
- // Pair a floating bar with a FAB via .toolbar-group. Do not show a
20
- // toolbar at the same time as nav.navigation-bar.
14
+ // standard vibrant
15
+ // floating surface-container, 32dp, primary-container, 32dp,
16
+ // elevation 3, hugs its elevation 3
17
+ // actions
18
+ // docked surface-container, square, primary-container, square,
19
+ // full width, no lift full width, no lift
21
20
  //
22
- // Tokens follow https://m3.material.io/components/toolbars/specs
23
- // action 48dp target, 24dp icon, transparent at rest
24
- // selected secondary-container (standard) / surface (vibrant)
25
- // emphasized 48dp, 16dp corners, primary / on-primary
21
+ // Neither default needs a class. Each default arm is the *absence* of the
22
+ // other side, so a bar that names nothing still lands on it - and each also
23
+ // names its own class, so `.floating` and `.standard` are spellings a grep of
24
+ // the compiled sheet finds rather than prose the docs assert about nothing.
25
+ // All four variants can therefore be written out in full, and the two
26
+ // non-default arms are declared after their defaults so that markup naming
27
+ // both sides of an axis resolves to the more specific one on source order
28
+ // rather than on a specificity accident.
26
29
  //
27
- // nav.toolbar only — do not style every <nav>. div.fixed-action-btn.toolbar
30
+ // Tokens follow the 34.0.21 sets md.comp.toolbar.{standard, vibrant, docked,
31
+ // floating, floating.fab}; https://m3.material.io/components/toolbars/specs
32
+ // is the prose behind them. M3 splits them into a shape/size pair (docked,
33
+ // floating) and a color pair (standard, vibrant), which is the same two axes
34
+ // as above; floating.fab is the companion FAB, not the in-bar .filled action.
35
+ //
36
+ // Color: `.vibrant` is M3's own toolbar-vibrant set - primary-container - and
37
+ // it is deliberately *not* also spelled `[vibrant]`. That attribute is the
38
+ // emphasis foundation, whose one ramp is tertiary; a bar carrying it is
39
+ // repainted by the foundation alone, with no toolbar-specific code, because
40
+ // every colour below is a --md-comp-toolbar-* token pointed at a
41
+ // --md-sys-color-* role. Matching the attribute here as well would give one
42
+ // variant two renderings - the class's primary chip against the foundation's
43
+ // tertiary one - and would contradict tokens/_vibrant.scss, which states that
44
+ // a component carrying its own .vibrant class keeps the colours its own M3
45
+ // token set gives it. Nothing collides either way: selection is
46
+ // secondary-container, which the foundation does not remap.
47
+ //
48
+ // Do not show a toolbar at the same time as nav.navigation-bar.
49
+ //
50
+ // #{$_toolbar} only - do not style every <nav>. div.fixed-action-btn.toolbar
28
51
  // is the FAB-to-toolbar transition, not this component.
29
52
 
30
- nav.toolbar {
31
- --md-comp-toolbar-container-color: var(--md-sys-color-surface-container);
32
- --md-comp-toolbar-container-shape: 32px;
53
+ // The host. A toolbar holds commands, not destinations, so it is not a <nav>
54
+ // (rule 4) - and not role="toolbar" either, because that is a composite role
55
+ // promising arrow-key navigation nothing here implements (rule 2). The <nav>
56
+ // form is the older markup and still works.
57
+ //
58
+ // Wrapped in :is() so it stays one compound selector: interpolating a bare
59
+ // comma-separated list breaks out of its context. That mistake shipped once
60
+ // already, in $_bar.
61
+ $_toolbar: ":is(nav.toolbar, .toolbar:not(.fixed-action-btn):not(.fab))";
62
+
63
+ // The two axes. `.max` is BeerCSS's name for docked; `.filled` is the older
64
+ // name for vibrant. Both stay in the :is() lists.
65
+ $_floating: "#{$_toolbar}:is(.floating, :not(.docked, .max))";
66
+ $_docked: "#{$_toolbar}:is(.docked, .max)";
67
+ $_standard: "#{$_toolbar}:is(.standard, :not(.vibrant, .filled))";
68
+ $_vibrant: "#{$_toolbar}:is(.vibrant, .filled)";
69
+
70
+ #{$_toolbar} {
71
+ // Shared across all four variants. --md-comp-toolbar-color stands for
72
+ // md.comp.toolbar.*.icon.color and .label-text.color, which M3 gives the
73
+ // same value in both color sets.
33
74
  --md-comp-toolbar-container-height: 64px;
34
- --md-comp-toolbar-leading-space: 12px;
75
+ --md-comp-toolbar-between-space: 4px;
35
76
  --md-comp-toolbar-action-size: 48px;
36
77
  --md-comp-toolbar-icon-size: 24px;
37
- --md-comp-toolbar-selected-container-color: var(--md-sys-color-secondary-container);
38
- --md-comp-toolbar-selected-color: var(--md-sys-color-on-secondary-container);
39
78
 
40
79
  box-sizing: border-box;
41
80
  display: inline-flex;
42
81
  align-items: center;
43
82
  justify-content: center;
44
- gap: 4px;
83
+ gap: var(--md-comp-toolbar-between-space);
45
84
  min-height: var(--md-comp-toolbar-container-height);
46
85
  min-width: var(--md-comp-toolbar-container-height);
47
86
  margin: 0;
48
- padding: 8px var(--md-comp-toolbar-leading-space);
87
+ padding-block: 8px;
88
+ padding-inline: var(--md-comp-toolbar-leading-space) var(--md-comp-toolbar-trailing-space);
49
89
  border: none;
50
90
  border-radius: var(--md-comp-toolbar-container-shape);
51
91
  background-color: var(--md-comp-toolbar-container-color);
52
- color: var(--md-sys-color-on-surface);
53
- @include z-depth("2");
92
+ color: var(--md-comp-toolbar-color);
54
93
 
55
94
  & + & {
56
95
  margin-block-start: 16px;
57
96
  }
58
97
 
59
- // Actions — transparent at rest so they sit on the container, not filled.
98
+ // Actions - transparent at rest so they sit on the container, not filled.
60
99
  // .extra is the 56dp companion FAB; leave it alone.
61
- > :is(a, button):not(.extra) {
100
+ > :is(a, button):not(.extra, .icon-button) {
62
101
  box-sizing: border-box;
63
102
  display: inline-flex;
64
103
  flex: 0 0 auto;
@@ -79,10 +118,9 @@ nav.toolbar {
79
118
  cursor: pointer;
80
119
  appearance: none;
81
120
  @include typescale("label-large");
82
- font-family: $font-stack;
83
121
  @include focus-visible;
84
122
 
85
- > i {
123
+ > #{$icon} {
86
124
  flex: 0 0 auto;
87
125
  font-size: var(--md-comp-toolbar-icon-size);
88
126
  width: var(--md-comp-toolbar-icon-size);
@@ -90,12 +128,14 @@ nav.toolbar {
90
128
  line-height: var(--md-comp-toolbar-icon-size);
91
129
  }
92
130
 
131
+ // The state layer is the content color in both color sets, which is
132
+ // what currentColor already is on an action that has not been filled.
93
133
  &:hover:not(.active, .filled) {
94
- background-color: color-mix(in oklab, currentColor 8%, transparent);
134
+ background-color: color-mix(in oklab, currentColor state-opacity("hover"), transparent);
95
135
  }
96
136
 
97
137
  &:active:not(.active, .filled) {
98
- background-color: color-mix(in oklab, currentColor 10%, transparent);
138
+ background-color: color-mix(in oklab, currentColor state-opacity("pressed"), transparent);
99
139
  }
100
140
 
101
141
  &.active {
@@ -104,6 +144,8 @@ nav.toolbar {
104
144
  }
105
145
 
106
146
  &:disabled {
147
+ // 0.38, md.comp.toolbar.*.disabled.{icon,label-text}.opacity. Left as a
148
+ // literal because it is the global disabled figure, not a toolbar knob.
107
149
  color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
108
150
  background-color: transparent;
109
151
  box-shadow: none;
@@ -111,15 +153,15 @@ nav.toolbar {
111
153
  }
112
154
  }
113
155
 
114
- // Icon-only — .circle, or an <i> with no <span> sibling.
115
- > :is(a, button):not(.extra):is(.circle, :has(> i):not(:has(> span))) {
156
+ // Icon-only - .circle, or an icon with no label beside it.
157
+ > :is(a, button):not(.extra, .icon-button):is(.circle, :has(> #{$icon}):not(:has(> #{$icon-label}))) {
116
158
  width: var(--md-comp-toolbar-action-size);
117
159
  padding: 0;
118
160
  border-radius: 50%;
119
161
  }
120
162
 
121
163
  // Emphasized action (the in-bar FAB). 48dp, 16dp corners, primary.
122
- > :is(a, button).filled:not(.extra) {
164
+ > :is(a, button).filled:not(.extra, .icon-button) {
123
165
  background-color: var(--md-sys-color-primary);
124
166
  color: var(--md-sys-color-on-primary);
125
167
  border-radius: 16px;
@@ -128,7 +170,7 @@ nav.toolbar {
128
170
  &:hover {
129
171
  background-color: color-mix(
130
172
  in oklab,
131
- var(--md-sys-color-on-primary) 8%,
173
+ var(--md-sys-color-on-primary) state-opacity("hover"),
132
174
  var(--md-sys-color-primary)
133
175
  );
134
176
  }
@@ -136,13 +178,13 @@ nav.toolbar {
136
178
  &:active {
137
179
  background-color: color-mix(
138
180
  in oklab,
139
- var(--md-sys-color-on-primary) 10%,
181
+ var(--md-sys-color-on-primary) state-opacity("pressed"),
140
182
  var(--md-sys-color-primary)
141
183
  );
142
184
  }
143
185
  }
144
186
 
145
- > :is(a, button).filled:not(.extra):has(> span) {
187
+ > :is(a, button).filled:not(.extra):has(> #{$icon-label}) {
146
188
  height: 40px;
147
189
  min-height: 40px;
148
190
  padding: 0 24px;
@@ -158,7 +200,8 @@ nav.toolbar {
158
200
  margin: 0;
159
201
  }
160
202
 
161
- // Spacer between leading and trailing actions.
203
+ // Spacer between leading and trailing actions. `.max` on a *child* is the
204
+ // spacer; on the bar itself it is the docked alias.
162
205
  > .max {
163
206
  flex: 1 1 auto;
164
207
  align-self: stretch;
@@ -167,21 +210,69 @@ nav.toolbar {
167
210
  }
168
211
  }
169
212
 
170
- // Vibrant color style. Pair .active with surface so it still contrasts.
171
- nav.toolbar.vibrant,
172
- nav.toolbar.filled {
213
+ // Floating shape. Hugs its actions, stadium, lifted. 32px rather than a
214
+ // corner-full keyword because it has to be half the *short* side: on a
215
+ // vertical bar the container is 64dp wide and arbitrarily tall, where 9999px
216
+ // would round the long edge into an oval.
217
+ #{$_floating} {
218
+ --md-comp-toolbar-container-shape: 32px;
219
+ --md-comp-toolbar-leading-space: 8px;
220
+ --md-comp-toolbar-trailing-space: 8px;
221
+ --md-comp-toolbar-external-space: 16px;
222
+
223
+ @include z-depth("3");
224
+ }
225
+
226
+ // Docked shape. Full width, square, no lift. M3's replacement for the bottom
227
+ // app bar, and shorter than it (64dp, not 80).
228
+ //
229
+ // min-spacing and max-spacing bound how far apart the actions sit as the bar
230
+ // widens, which is a clamped gap on a centered row - space-around would add
231
+ // its own distribution on top of both bounds and honour neither. A child
232
+ // .max spacer still takes the slack.
233
+ #{$_docked} {
234
+ --md-comp-toolbar-container-shape: 0;
235
+ --md-comp-toolbar-leading-space: 16px;
236
+ --md-comp-toolbar-trailing-space: 16px;
237
+ --md-comp-toolbar-min-spacing: 4px;
238
+ --md-comp-toolbar-max-spacing: 32px;
239
+
240
+ display: flex;
241
+ width: 100%;
242
+ // 4vw is the growth rate between the two bounds, and has no token behind it
243
+ // because M3 states the bounds and not the ramp. The viewport unit is honest
244
+ // here and only here: a docked bar is full width by definition.
245
+ gap: clamp(var(--md-comp-toolbar-min-spacing), 4vw, var(--md-comp-toolbar-max-spacing));
246
+ @include z-depth("0");
247
+ }
248
+
249
+ // Standard color style, the same in both shapes.
250
+ #{$_standard} {
251
+ --md-comp-toolbar-container-color: var(--md-sys-color-surface-container);
252
+ --md-comp-toolbar-color: var(--md-sys-color-on-surface-variant);
253
+ --md-comp-toolbar-selected-container-color: var(--md-sys-color-secondary-container);
254
+ --md-comp-toolbar-selected-color: var(--md-sys-color-on-secondary-container);
255
+ }
256
+
257
+ // Vibrant color style, the same in both shapes. Selection moves to a neutral
258
+ // chip so it still reads against the accent container.
259
+ #{$_vibrant} {
173
260
  --md-comp-toolbar-container-color: var(--md-sys-color-primary-container);
174
- --md-comp-toolbar-selected-container-color: var(--md-sys-color-surface);
261
+ --md-comp-toolbar-color: var(--md-sys-color-on-primary-container);
262
+ --md-comp-toolbar-selected-container-color: var(--md-sys-color-surface-container);
175
263
  --md-comp-toolbar-selected-color: var(--md-sys-color-on-surface);
176
- color: var(--md-sys-color-on-primary-container);
177
264
  }
178
265
 
179
- // Vertical floating bar.
180
- nav.toolbar.vertical {
266
+ // Vertical floating bar. Only floating goes vertical - a docked bar is a
267
+ // full-width strip by definition.
268
+ #{$_floating}.vertical {
269
+ --md-comp-toolbar-external-space: 24px;
270
+
181
271
  flex-direction: column;
182
272
  min-width: var(--md-comp-toolbar-container-height);
183
273
  min-height: var(--md-comp-toolbar-container-height);
184
- padding: var(--md-comp-toolbar-leading-space) 8px;
274
+ padding-block: var(--md-comp-toolbar-leading-space) var(--md-comp-toolbar-trailing-space);
275
+ padding-inline: 8px;
185
276
 
186
277
  > .max {
187
278
  min-width: 0;
@@ -190,68 +281,103 @@ nav.toolbar.vertical {
190
281
  }
191
282
  }
192
283
 
193
- // Docked — full width, square, no lift. M3's replacement for the bottom
194
- // app bar. Shorter than that bar (64dp, not 80). .max is the BeerCSS
195
- // name; .docked is the M3 name.
196
- nav.toolbar.max,
197
- nav.toolbar.docked {
198
- display: flex;
199
- width: 100%;
200
- --md-comp-toolbar-container-shape: 0;
201
- justify-content: space-around;
202
- @include z-depth("0");
203
- }
204
-
205
- // Pair a floating toolbar with a companion FAB. The FAB is a sibling,
206
- // not a child — typically .extra.circle.
284
+ // The companion FAB. It is a sibling of the bar, not a child, and it stays a
285
+ // FAB: md.comp.toolbar.floating.fab restates md.comp.fab's 56dp container,
286
+ // 24dp icon and 16dp corner, which .extra.circle already draws. Only three
287
+ // values are its own - the 8dp gap, the level-1 lift (the bar beside it is
288
+ // level 3), and a color pair that follows the bar's color style.
207
289
  .toolbar-group {
290
+ --md-comp-toolbar-floating-fab-between-space: 8px;
291
+ --md-comp-toolbar-floating-fab-container-color: var(--md-sys-color-secondary-container);
292
+ --md-comp-toolbar-floating-fab-icon-color: var(--md-sys-color-on-secondary-container);
293
+
208
294
  display: inline-flex;
209
295
  align-items: center;
210
- gap: 8px;
296
+ gap: var(--md-comp-toolbar-floating-fab-between-space);
211
297
 
212
298
  &.vertical {
213
299
  flex-direction: column;
214
300
  }
215
301
 
216
- > nav.toolbar {
302
+ > #{$_toolbar} {
217
303
  margin: 0;
218
304
  }
305
+
306
+ > .extra {
307
+ background-color: var(--md-comp-toolbar-floating-fab-container-color);
308
+ color: var(--md-comp-toolbar-floating-fab-icon-color);
309
+ @include z-depth("1");
310
+
311
+ // One opacity per state. All three arms are needed, bare `:focus`
312
+ // included: .extra.circle draws its own `:active, :focus` tint over
313
+ // primary-container, and leaving either uncovered would flash the FAB's
314
+ // default colour through a click on this one.
315
+ &:hover {
316
+ background-color: color-mix(
317
+ in oklab,
318
+ var(--md-comp-toolbar-floating-fab-icon-color) state-opacity("hover"),
319
+ var(--md-comp-toolbar-floating-fab-container-color)
320
+ );
321
+ @include z-depth("2");
322
+ }
323
+
324
+ &:focus {
325
+ background-color: color-mix(
326
+ in oklab,
327
+ var(--md-comp-toolbar-floating-fab-icon-color) state-opacity("focus"),
328
+ var(--md-comp-toolbar-floating-fab-container-color)
329
+ );
330
+ }
331
+
332
+ &:active {
333
+ background-color: color-mix(
334
+ in oklab,
335
+ var(--md-comp-toolbar-floating-fab-icon-color) state-opacity("pressed"),
336
+ var(--md-comp-toolbar-floating-fab-container-color)
337
+ );
338
+ }
339
+ }
219
340
  }
220
341
 
221
- // Pin to the viewport. Floating defaults to bottom-center; docked
222
- // defaults to the bottom edge.
223
- nav.toolbar.fixed {
342
+ // A vibrant bar takes a vibrant FAB. The author marks the bar, not the group,
343
+ // so the group reads it - the same one class drives both.
344
+ .toolbar-group:has(> #{$_vibrant}) > .extra {
345
+ --md-comp-toolbar-floating-fab-container-color: var(--md-sys-color-tertiary-container);
346
+ --md-comp-toolbar-floating-fab-icon-color: var(--md-sys-color-on-tertiary-container);
347
+ }
348
+
349
+ // Pin to the viewport. The inset is the variant's own external space:
350
+ // 16dp for a horizontal floating bar, 24dp for a vertical one, 0 for a
351
+ // docked bar, which states its own and so declares no external space at all.
352
+ // Bottom is the default, so `.bottom`
353
+ // has no rule - a bar naming it falls through to the one above.
354
+ #{$_toolbar}.fixed {
224
355
  position: fixed;
225
356
  z-index: 996;
226
- inset: auto auto 16px 50%;
357
+ inset: auto auto var(--md-comp-toolbar-external-space) 50%;
227
358
  transform: translateX(-50%);
228
359
  }
229
360
 
230
- nav.toolbar.fixed.top {
231
- inset: 16px auto auto 50%;
232
- }
233
-
234
- nav.toolbar.fixed.bottom {
235
- inset: auto auto 16px 50%;
361
+ #{$_toolbar}.fixed.top {
362
+ inset: var(--md-comp-toolbar-external-space) auto auto 50%;
236
363
  }
237
364
 
238
- nav.toolbar.vertical.fixed {
239
- inset: 50% auto auto 16px;
365
+ #{$_floating}.vertical.fixed {
366
+ inset: 50% auto auto var(--md-comp-toolbar-external-space);
240
367
  transform: translateY(-50%);
241
368
  }
242
369
 
243
- nav.toolbar.vertical.fixed.right {
244
- inset: 50% 16px auto auto;
370
+ #{$_floating}.vertical.fixed.right {
371
+ inset: 50% var(--md-comp-toolbar-external-space) auto auto;
245
372
  }
246
373
 
247
- nav.toolbar:is(.max, .docked).fixed,
248
- nav.toolbar:is(.max, .docked).fixed.bottom {
374
+ #{$_docked}.fixed {
249
375
  inset: auto 0 0 0;
250
376
  transform: none;
251
377
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
252
378
  }
253
379
 
254
- nav.toolbar:is(.max, .docked).fixed.top {
380
+ #{$_docked}.fixed.top {
255
381
  inset: 0 0 auto 0;
256
382
  transform: none;
257
383
  padding-bottom: 8px;
@@ -11,4 +11,11 @@
11
11
  &.scale-in {
12
12
  transform: scale(1);
13
13
  }
14
- }
14
+ }
15
+
16
+ @media (prefers-reduced-motion: reduce) {
17
+ .scale-transition,
18
+ .scale-transition.scale-out {
19
+ transition: none !important;
20
+ }
21
+ }
@@ -39,19 +39,21 @@ label:has(> [type="checkbox"]):not(.switch),
39
39
  -webkit-tap-highlight-color: transparent;
40
40
  color: var(--md-sys-color-on-surface);
41
41
  @include typescale("body-large");
42
- font-family: $font-stack;
43
42
  }
44
43
 
45
- nav:has(> label > [type="checkbox"]:not(.switch *)),
46
- nav:has(> .checkbox) {
44
+ // A row of checkboxes is a group of controls, not navigation - see rule 4 in
45
+ // SEMANTICS.md. `.inline` is the documented hook; the bare <nav> is the older
46
+ // markup and still works. _radio-buttons.scss carries the same pair.
47
+ :is(nav, .inline):has(> label > [type="checkbox"]:not(.switch *)),
48
+ :is(nav, .inline):has(> .checkbox) {
47
49
  display: flex;
48
50
  flex-wrap: wrap;
49
51
  align-items: center;
50
52
  gap: 8px 24px;
51
53
  }
52
54
 
53
- nav > label:has(> [type="checkbox"]):not(.switch),
54
- nav > .checkbox {
55
+ :is(nav, .inline) > label:has(> [type="checkbox"]):not(.switch),
56
+ :is(nav, .inline) > .checkbox {
55
57
  display: inline-flex;
56
58
  }
57
59
 
@@ -80,17 +82,32 @@ label:has(> [type="checkbox"]):not(.switch) > [type="checkbox"],
80
82
  }
81
83
 
82
84
  &:hover {
83
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
85
+ @include state-layer-ring(
86
+ var(--md-comp-checkbox-state-layer-size),
87
+ var(--md-comp-checkbox-container-size),
88
+ var(--md-sys-color-on-surface),
89
+ "hover"
90
+ );
84
91
  }
85
92
 
86
93
  &:checked:hover,
87
94
  &:indeterminate:hover {
88
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
95
+ @include state-layer-ring(
96
+ var(--md-comp-checkbox-state-layer-size),
97
+ var(--md-comp-checkbox-container-size),
98
+ var(--md-sys-color-primary),
99
+ "hover"
100
+ );
89
101
  }
90
102
 
91
103
  &:focus-visible,
92
104
  &.tabbed:focus {
93
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
105
+ @include state-layer-ring(
106
+ var(--md-comp-checkbox-state-layer-size),
107
+ var(--md-comp-checkbox-container-size),
108
+ var(--md-sys-color-primary),
109
+ "focus"
110
+ );
94
111
  outline: none;
95
112
  }
96
113
 
@@ -109,7 +126,12 @@ label:has(> [type="checkbox"]):not(.switch) > [type="checkbox"],
109
126
 
110
127
  &.invalid:hover,
111
128
  &[aria-invalid="true"]:hover {
112
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-error) 8%, transparent);
129
+ @include state-layer-ring(
130
+ var(--md-comp-checkbox-state-layer-size),
131
+ var(--md-comp-checkbox-container-size),
132
+ var(--md-sys-color-error),
133
+ "hover"
134
+ );
113
135
  }
114
136
 
115
137
  &:disabled {
@@ -239,16 +261,31 @@ $_cb-span: "*:not(.switch) > [type='checkbox'] + span:not(.lever, .supporting-te
239
261
  }
240
262
 
241
263
  *:not(.switch) > [type="checkbox"]:hover + span:not(.lever, .supporting-text, .character-counter, .file-path)::before {
242
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
264
+ @include state-layer-ring(
265
+ var(--md-comp-checkbox-state-layer-size),
266
+ var(--md-comp-checkbox-container-size),
267
+ var(--md-sys-color-on-surface),
268
+ "hover"
269
+ );
243
270
  }
244
271
 
245
272
  *:not(.switch) > [type="checkbox"]:is(:checked, :indeterminate):hover + span:not(.lever, .supporting-text, .character-counter, .file-path)::before {
246
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
273
+ @include state-layer-ring(
274
+ var(--md-comp-checkbox-state-layer-size),
275
+ var(--md-comp-checkbox-container-size),
276
+ var(--md-sys-color-primary),
277
+ "hover"
278
+ );
247
279
  }
248
280
 
249
281
  *:not(.switch) > [type="checkbox"]:focus-visible + span:not(.lever, .supporting-text, .character-counter, .file-path)::before,
250
282
  *:not(.switch) > [type="checkbox"].tabbed:focus + span:not(.lever, .supporting-text, .character-counter, .file-path)::before {
251
- box-shadow: 0 0 0 11px color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
283
+ @include state-layer-ring(
284
+ var(--md-comp-checkbox-state-layer-size),
285
+ var(--md-comp-checkbox-container-size),
286
+ var(--md-sys-color-primary),
287
+ "focus"
288
+ );
252
289
  }
253
290
 
254
291
  *:not(.switch) > [type="checkbox"].invalid + span:not(.lever, .supporting-text, .character-counter, .file-path)::before,
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Anatomy is the HTML. A <fieldset> is the group. A <legend> is the
6
6
  // headline. Direct children are the fields — .field, radios, switches,
7
- // a <nav> of radios, a trailing <small> as supporting text. No wrapper
7
+ // a .inline row of radios, a trailing <small> as supporting text. No wrapper
8
8
  // class. .filled / .border / .rounded are helpers markup cannot express.
9
9
  //
10
10
  // M3 has no fieldset component. Tokens follow the outlined grouping
@@ -49,7 +49,7 @@ fieldset {
49
49
  background-color: transparent;
50
50
  @include typescale("title-small");
51
51
 
52
- > i {
52
+ > #{$icon} {
53
53
  font-size: 18px;
54
54
  width: 18px;
55
55
  height: 18px;
@@ -64,7 +64,7 @@ fieldset {
64
64
  margin-block-start: 16px;
65
65
  }
66
66
 
67
- // Switches are inline-flex. Make a bare group stack; a <nav> keeps
67
+ // Switches are inline-flex. Make a bare group stack; .inline keeps
68
68
  // radios on one line. Consecutive 48dp rows don't need the 16dp gap.
69
69
  > label,
70
70
  > .switch {
@@ -112,7 +112,7 @@ fieldset[disabled] {
112
112
  transparent
113
113
  );
114
114
 
115
- > legend > i {
115
+ > legend > #{$icon} {
116
116
  color: var(--md-comp-fieldset-legend-color);
117
117
  }
118
118
  }
@@ -12,6 +12,10 @@
12
12
 
13
13
  input.file-path { width: 100%; }
14
14
 
15
+ // The trigger. `label.button` is the documented form - it wraps the file
16
+ // input, which is how clicking it opens the picker with no script. <button>
17
+ // and .btn are the older markup and still work.
18
+ > label.button,
15
19
  > button,
16
20
  > .btn {
17
21
  position: relative;
@@ -24,7 +28,10 @@
24
28
  cursor: pointer;
25
29
  }
26
30
 
27
- input[type=file] {
31
+ // Scoped to the trigger, not to .file-field. `inset: 0` against the field
32
+ // stretched the invisible input across the whole row, so clicking the path
33
+ // display - or any empty space - opened the file picker.
34
+ :is(label.button, button, .btn) input[type=file] {
28
35
  position: absolute;
29
36
  inset: 0;
30
37
  cursor: pointer;
@@ -4,7 +4,7 @@
4
4
  @forward 'switches';
5
5
  @forward 'select';
6
6
  @forward 'file-input';
7
- @forward 'range';
7
+ @forward 'slider';
8
8
  @forward 'fieldset';
9
9
 
10
10
  label {