@expressivecss/expressive 0.7.0-rc.0 → 0.8.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 (105) hide show
  1. package/CHANGELOG.md +1311 -1
  2. package/README.md +14 -11
  3. package/dist/css/expressive.css +4215 -1668
  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/js/expressive.cjs +1231 -1253
  8. package/dist/js/expressive.cjs.map +4 -4
  9. package/dist/js/expressive.js +1231 -1253
  10. package/dist/js/expressive.js.map +4 -4
  11. package/dist/js/expressive.min.js +12 -12
  12. package/dist/js/expressive.min.js.map +4 -4
  13. package/dist/js/expressive.mjs +1231 -1253
  14. package/dist/js/expressive.mjs.map +4 -4
  15. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  16. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  17. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  18. package/dist/types/components/autocomplete.d.ts +15 -3
  19. package/dist/types/components/buttons.d.ts +5 -1
  20. package/dist/types/components/carousel.d.ts +87 -53
  21. package/dist/types/components/chips.d.ts +21 -1
  22. package/dist/types/components/expandingCard.d.ts +31 -0
  23. package/dist/types/components/index.d.ts +21 -20
  24. package/dist/types/components/lightbox.d.ts +1 -0
  25. package/dist/types/components/menu.d.ts +7 -1
  26. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  27. package/dist/types/components/registry.d.ts +8 -8
  28. package/dist/types/components/slider.d.ts +26 -85
  29. package/dist/types/components/snackbar.d.ts +20 -5
  30. package/dist/types/components/tabs.d.ts +12 -0
  31. package/dist/types/components/timepicker.d.ts +12 -9
  32. package/dist/types/core/utils.d.ts +64 -53
  33. package/dist/types/index.d.ts +8 -9
  34. package/package.json +15 -1
  35. package/src/sass/README.md +61 -11
  36. package/src/sass/abstracts/_breakpoints.scss +17 -19
  37. package/src/sass/abstracts/_mixins.scss +103 -21
  38. package/src/sass/abstracts/_variables.scss +50 -5
  39. package/src/sass/base/_global.scss +6 -24
  40. package/src/sass/base/_grid.scss +15 -12
  41. package/src/sass/base/_normalize.scss +34 -38
  42. package/src/sass/base/_typography.scss +4 -18
  43. package/src/sass/components/_badges.scss +2 -3
  44. package/src/sass/components/_banners.scss +298 -0
  45. package/src/sass/components/_bottom-app-bar.scss +180 -0
  46. package/src/sass/components/_bottom-sheet.scss +12 -4
  47. package/src/sass/components/_button-groups.scss +186 -0
  48. package/src/sass/components/_buttons.scss +245 -92
  49. package/src/sass/components/_cards.scss +494 -73
  50. package/src/sass/components/_carousel.scss +562 -68
  51. package/src/sass/components/_chips.scss +64 -5
  52. package/src/sass/components/_datepicker.scss +24 -24
  53. package/src/sass/components/_dialog.scss +17 -11
  54. package/src/sass/components/_drag-handle.scss +130 -0
  55. package/src/sass/components/_expanding-card.scss +260 -0
  56. package/src/sass/components/_fab-menu.scss +232 -0
  57. package/src/sass/components/_floating-sheet.scss +86 -0
  58. package/src/sass/components/_icon-buttons.scss +194 -0
  59. package/src/sass/components/_icons-material-design.scss +16 -4
  60. package/src/sass/components/_index.scss +22 -6
  61. package/src/sass/components/_list.scss +16 -13
  62. package/src/sass/components/_loading-indicator.scss +106 -0
  63. package/src/sass/components/_menu.scss +335 -61
  64. package/src/sass/components/_navbar.scss +71 -24
  65. package/src/sass/components/_navigation-bar.scss +13 -10
  66. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  67. package/src/sass/components/_navigation-rail.scss +22 -22
  68. package/src/sass/components/_page-footer.scss +19 -16
  69. package/src/sass/components/_pagination.scss +24 -6
  70. package/src/sass/components/_panes.scss +52 -11
  71. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  72. package/src/sass/components/_scrim.scss +37 -0
  73. package/src/sass/components/_search.scss +259 -0
  74. package/src/sass/components/_segmented-buttons.scss +188 -0
  75. package/src/sass/components/_side-sheet.scss +2 -2
  76. package/src/sass/components/_snackbar.scss +20 -15
  77. package/src/sass/components/_split-button.scss +196 -0
  78. package/src/sass/components/_tabs.scss +7 -8
  79. package/src/sass/components/_timepicker.scss +67 -58
  80. package/src/sass/components/_toolbar.scss +206 -80
  81. package/src/sass/components/forms/_checkboxes.scss +49 -12
  82. package/src/sass/components/forms/_fieldset.scss +4 -4
  83. package/src/sass/components/forms/_file-input.scss +8 -1
  84. package/src/sass/components/forms/_index.scss +1 -1
  85. package/src/sass/components/forms/_input-fields.scss +50 -44
  86. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  87. package/src/sass/components/forms/_select.scss +0 -1
  88. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  89. package/src/sass/components/forms/_switches.scss +6 -7
  90. package/src/sass/tokens/_index.scss +4 -1
  91. package/src/sass/tokens/_reference.scss +70 -61
  92. package/src/sass/tokens/_state.scss +24 -0
  93. package/src/sass/tokens/_theme.scss +6 -3
  94. package/src/sass/tokens/_vibrant.scss +76 -0
  95. package/src/sass/utilities/_colors.scss +14 -1
  96. package/src/sass/utilities/_helpers.scss +5 -5
  97. package/src/sass/utilities/_visibility.scss +48 -26
  98. package/dist/types/behaviors/waves.d.ts +0 -15
  99. package/dist/types/components/parallax.d.ts +0 -16
  100. package/dist/types/components/range.d.ts +0 -43
  101. package/dist/types/core/bounding.d.ts +0 -6
  102. package/dist/types/core/edges.d.ts +0 -6
  103. package/src/sass/components/_parallax.scss +0 -104
  104. package/src/sass/components/_pulse.scss +0 -37
  105. package/src/sass/components/_slider.scss +0 -129
@@ -1,39 +1,75 @@
1
+ @use "sass:map";
1
2
  @use "../abstracts" as *;
2
3
 
3
4
  // Common buttons and FABs.
4
5
  //
5
6
  // Anatomy is the HTML. A <button> (or <a class="button">) is a filled
6
7
  // common button. Put an <i> first or last for a leading/trailing icon.
7
- // Icon-only is a 40dp icon button. Helpers switch the M3 variant
8
- // (tonal, outlined, elevated, text) or the size.
8
+ // Icon-only is a 40dp icon button. Two independent modifier axes dress it:
9
+ // a style (filled, tonal, outlined, elevated, text) and a size (xsmall …
10
+ // xlarge).
9
11
  //
10
- // Tokens follow https://m3.material.io/components/buttons/specs
11
- // height 40 / 32 small / 56 large, label-large, 20dp stadium
12
- // icon 18dp, 8dp gap, 24dp inset (16dp next to an icon)
12
+ // Nine classes, not twenty-five. The size rules set tokens and the style
13
+ // rules set colours, so every combination falls out of the cascade and no
14
+ // rule names both. `tests/buttons.test.js` fails if one starts to.
15
+ //
16
+ // Tokens follow M3 Expressive, md.comp.button.* (DSP 34.0.21):
17
+ // the size ladder in abstracts, corner.full at every size
13
18
  // state layer 8% hover / 10% focus+press
14
19
  // FAB 56dp (extra/large icon-only), primary-container, 16dp corners
20
+ // FAB sizes 40dp small / 56dp default / 80dp medium / 96dp large,
21
+ // 24 / 24 / 26 / 36dp icon, 12 / 16 / 20 / 28dp corners
22
+
23
+ // The size axis is `$button-sizes` in abstracts, and `button-size()` sets its
24
+ // tokens. Both moved there when button groups grew a size ladder of their own:
25
+ // a group sizes its items from the group, so it needs the same table, and a
26
+ // partial may not @use another partial.
15
27
 
16
- :root {
28
+ :root, :host {
17
29
  --md-comp-filled-button-container-color: var(--md-sys-color-primary);
18
30
  --md-comp-filled-button-label-text-color: var(--md-sys-color-on-primary);
19
- --md-comp-filled-button-container-height: 40px;
20
- --md-comp-filled-button-container-shape: 20px;
21
- --md-comp-filled-button-leading-space: 24px;
22
- --md-comp-filled-button-trailing-space: 24px;
23
- --md-comp-filled-button-with-icon-leading-space: 16px;
24
- --md-comp-filled-button-icon-size: 18px;
25
- --md-comp-filled-button-icon-label-space: 8px;
31
+ --md-comp-filled-button-container-shape: 9999px;
32
+ @include button-size("small");
26
33
  }
27
34
 
28
35
  // Widget chrome that happens to be a <button> — not a common button.
29
- $_not-btn: ".datepicker-day-button, .month-prev, .month-next, .indicator-item-btn";
30
-
31
- $_btn: "button:not(#{$_not-btn}), a.button";
36
+ //
37
+ // `.chip` earns its place here now that assist and suggestion chips are real
38
+ // buttons. The base rules were harmless (`.chip` and `:where(…):not(…)` are
39
+ // both one class, and chips is forwarded after buttons, so the chip won), but
40
+ // the state rules are not: `:where(…):not(…):hover` is *two* — a class from
41
+ // the `:not()` and the pseudo-class — so hovering, focusing or pressing a chip
42
+ // repainted it with the filled-button background while it kept the chip's own
43
+ // text colour.
44
+ //
45
+ // `.icon-button` is here because it is its own component (M3 gives it its own
46
+ // token families, size ladder and colours), not a common button in a round
47
+ // shape. Without the exclusion it would inherit 40dp geometry and a filled
48
+ // primary container before its own partial ever ran.
49
+ //
50
+ // Every entry stays a single class so the `:not()` keeps its one-class
51
+ // specificity; `.chip .close` would make it two and shift every button rule.
52
+ // The delete button needs no entry: `.chip .close` is already two classes, so
53
+ // it ties the state rules and wins on order.
54
+ $_not-btn: ".datepicker-day-button, .date-picker-day-button, .month-prev, .month-next, .chip, .icon-button";
55
+
56
+ // `label.button` is here for the file input: the control has to be a <label>
57
+ // so that clicking it reaches the <input type="file"> it wraps, but it is a
58
+ // button to look at and to use. Without it the label fell through to the
59
+ // floating-label rule in forms/_input-fields and rendered as invisible,
60
+ // unclickable field furniture.
61
+ // `.btn` is the pre-1.0 name and is deliberately NOT in this list. A
62
+ // <button class="btn"> is styled because it is a <button>; an <a class="btn">
63
+ // is not, and making it so means adding `.btn` to the twenty-odd `:not()`
64
+ // lists that let hosts (footer, drawer, app bar, rail, list, snackbar) step
65
+ // aside for a button - an invariant with no test behind it, for markup that
66
+ // has a working spelling. `a.button` is that spelling, and the docs say so.
67
+ $_btn: "button:not(#{$_not-btn}), a.button, label.button";
32
68
 
33
69
  :where(#{$_btn}) {
34
70
  @include btn;
35
71
 
36
- > i {
72
+ > #{$icon} {
37
73
  flex: 0 0 auto;
38
74
  font-size: var(--md-comp-filled-button-icon-size);
39
75
  width: var(--md-comp-filled-button-icon-size);
@@ -42,34 +78,31 @@ $_btn: "button:not(#{$_not-btn}), a.button";
42
78
  overflow: hidden;
43
79
  }
44
80
 
45
- // :only-child ignores text nodes, so "<i>add</i>Create" would look like
46
- // an icon button. Wrap the label in a <span> (BeerCSS) for the 16dp
47
- // leading inset; otherwise the icon still sits on the flex gap.
48
- &:has(> i + span) {
49
- padding-inline-start: var(--md-comp-filled-button-with-icon-leading-space);
50
- }
51
-
52
- &:has(> span + i) {
53
- padding-inline-end: var(--md-comp-filled-button-with-icon-leading-space);
54
- }
55
-
56
81
  // Icon button — opt in with .circle. Do not infer from :only-child.
82
+ //
83
+ // Shape only. The glyph is the size rule's, like every other button's: a
84
+ // `.circle` is a common button wearing a round shape, and `.icon-button` is
85
+ // the component that carries the icon *button* ladder. It used to pin 24px
86
+ // here - `md.comp.icon-button.small.icon-size`, right for the 40dp default
87
+ // and wrong for every other rung, most visibly a 24px glyph adrift in an
88
+ // `xlarge` 136dp disc. The FAB overrides both, further down.
57
89
  &.circle {
58
90
  width: var(--md-comp-filled-button-container-height);
59
91
  padding: 0;
60
92
  border-radius: 50%;
61
-
62
- > i {
63
- font-size: 24px;
64
- width: 24px;
65
- height: 24px;
66
- line-height: 24px;
67
- }
68
93
  }
69
94
  }
70
95
 
71
- // Default variant is filled. :where() keeps specificity at zero so .tonal etc. win.
72
- :where(#{$_btn}):not(.tonal, .outlined, .elevated, .text) {
96
+ // The style axis. Filled is the default, so it is both the styleless case and
97
+ // a class of its own - the two are one rule, because a second `@include
98
+ // btn-filled` would duplicate every state layer in it.
99
+ //
100
+ // They are separate selectors rather than `:is(.filled, :not(…))` on purpose:
101
+ // `:is()` takes the specificity of its heaviest argument, which would lift the
102
+ // default case to one class and let it outrank rules that legitimately beat it
103
+ // today. A selector list keeps each half its own weight.
104
+ :where(#{$_btn}):not(.tonal, .outlined, .elevated, .text),
105
+ :where(#{$_btn}).filled {
73
106
  @include btn-filled;
74
107
  }
75
108
 
@@ -89,48 +122,90 @@ $_btn: "button:not(#{$_not-btn}), a.button";
89
122
  @include btn-flat;
90
123
  }
91
124
 
92
- // Sizes. small = 32dp, large/extra = 56dp (M3 extra-large / FAB height).
93
- :where(#{$_btn}).small {
94
- --md-comp-filled-button-container-height: 32px;
95
- --md-comp-filled-button-container-shape: 16px;
96
- --md-comp-filled-button-leading-space: 16px;
97
- --md-comp-filled-button-trailing-space: 16px;
98
- --md-comp-filled-button-with-icon-leading-space: 12px;
99
- --md-comp-filled-button-icon-size: 16px;
100
- height: 32px;
101
- font-size: var(--md-sys-typescale-label-medium-font-size);
125
+ // The size classes. Every one of them sets tokens and a type role and nothing
126
+ // else - the geometry is already written once, in `btn`, against those tokens.
127
+ // A size that declared `height` itself would have to be restated by anything
128
+ // that overrides it, which is how the per-combination rules start.
129
+ @each $name, $s in $button-sizes {
130
+ :where(#{$_btn}).#{$name} {
131
+ @include button-size($name);
132
+ @include typescale(map.get($s, label));
133
+ }
102
134
  }
103
135
 
104
- :where(#{$_btn}).large,
136
+ // `.extra` is the pre-1.0 name for the 56dp button, which is now `.medium`.
137
+ // It aliases the geometry and not the type role, because it never carried
138
+ // one: a rail's FAB is `a.button.extra`, its label is label-large, and
139
+ // `.medium`'s title-medium would grow it. Size, not size-and-label.
140
+ //
141
+ // It is also the FAB's marker class - `circle extra` is a FAB, and the rules
142
+ // below reset this geometry entirely.
105
143
  :where(#{$_btn}).extra {
106
- --md-comp-filled-button-container-height: 56px;
107
- --md-comp-filled-button-container-shape: 28px;
108
- --md-comp-filled-button-leading-space: 24px;
109
- --md-comp-filled-button-trailing-space: 24px;
110
- --md-comp-filled-button-with-icon-leading-space: 16px;
111
- --md-comp-filled-button-icon-size: 24px;
112
- height: 56px;
144
+ @include button-size("medium");
113
145
  }
114
146
 
147
+ // Every size is corner.full already, so this only bites when a consumer has
148
+ // themed the shape token to something squarer and wants one button back.
115
149
  :where(#{$_btn}).rounded {
116
150
  border-radius: 9999px;
117
151
  }
118
152
 
119
- // Extended FAB: 56dp, large corners, primary-container, icon + label.
153
+ // Extended FAB: an icon plus a label, on its own size and colour axes.
154
+ //
155
+ // Tokens (M3 md.comp.extended-fab.*, DSP 34.0.21). The sizeless case is the
156
+ // base family; the three named sizes are `md.comp.extended-fab.{size}.*`:
157
+ //
158
+ // size height shape icon lead gap trail label
159
+ // (none) 56 16 24 16 8 20 label-large
160
+ // small 56 16 24 16 8 16 title-medium
161
+ // medium 80 20 28 26 12 26 title-large
162
+ // large 96 28 36 28 16 28 headline-small
163
+ //
164
+ // Leading and trailing space are equal at every named size, so the map states
165
+ // one `space`; only the base family keeps M3's older 16/20 asymmetry.
166
+ //
167
+ // The colour axis is M3's three container roles, and it runs through two
168
+ // tokens rather than a background per variant: the state layers mix the label
169
+ // colour into the container colour, so a role that only sets the two tokens
170
+ // gets its own hover and focus for free.
171
+ //
172
+ // `font-size` is restated because the size classes are the same one class
173
+ // this rule is: `.small` sets label-large from the common-button ladder and
174
+ // would otherwise decide the label of an `extend small`.
175
+ $_extend-sizes: (
176
+ "small": (height: 56px, shape: 16px, icon: 24px, space: 16px, gap: 8px, label: "title-medium"),
177
+ "medium": (height: 80px, shape: 20px, icon: 28px, space: 26px, gap: 12px, label: "title-large"),
178
+ "large": (height: 96px, shape: 28px, icon: 36px, space: 28px, gap: 16px, label: "headline-small"),
179
+ );
180
+
120
181
  :where(#{$_btn}).extend {
121
- height: 56px;
122
- padding-inline: 16px 20px;
123
- border-radius: 16px;
124
- color: var(--md-sys-color-on-primary-container);
125
- background-color: var(--md-sys-color-primary-container);
182
+ --md-comp-extended-fab-container-height: 56px;
183
+ --md-comp-extended-fab-container-shape: 16px;
184
+ --md-comp-extended-fab-icon-size: 24px;
185
+ --md-comp-extended-fab-leading-space: 16px;
186
+ --md-comp-extended-fab-icon-label-space: 8px;
187
+ --md-comp-extended-fab-trailing-space: 20px;
188
+ --md-comp-extended-fab-container-color: var(--md-sys-color-primary-container);
189
+ --md-comp-extended-fab-label-text-color: var(
190
+ --md-sys-color-on-primary-container
191
+ );
192
+
193
+ gap: var(--md-comp-extended-fab-icon-label-space);
194
+ height: var(--md-comp-extended-fab-container-height);
195
+ padding-inline: var(--md-comp-extended-fab-leading-space)
196
+ var(--md-comp-extended-fab-trailing-space);
197
+ border-radius: var(--md-comp-extended-fab-container-shape);
198
+ font-size: var(--md-sys-typescale-label-large-font-size);
199
+ color: var(--md-comp-extended-fab-label-text-color);
200
+ background-color: var(--md-comp-extended-fab-container-color);
126
201
  @include z-depth("3");
127
202
 
128
203
  &:hover {
129
204
  @include z-depth("4");
130
205
  background-color: color-mix(
131
206
  in oklab,
132
- var(--md-sys-color-on-primary-container) 8%,
133
- var(--md-sys-color-primary-container)
207
+ var(--md-comp-extended-fab-label-text-color) state-opacity("hover"),
208
+ var(--md-comp-extended-fab-container-color)
134
209
  );
135
210
  }
136
211
 
@@ -138,33 +213,74 @@ $_btn: "button:not(#{$_not-btn}), a.button";
138
213
  &:focus {
139
214
  background-color: color-mix(
140
215
  in oklab,
141
- var(--md-sys-color-on-primary-container) 10%,
142
- var(--md-sys-color-primary-container)
216
+ var(--md-comp-extended-fab-label-text-color) state-opacity("focus"),
217
+ var(--md-comp-extended-fab-container-color)
143
218
  );
144
219
  }
145
220
 
146
221
  @include focus-visible;
147
222
 
148
- > i {
149
- font-size: 24px;
150
- width: 24px;
151
- height: 24px;
152
- line-height: 24px;
223
+ > #{$icon} {
224
+ font-size: var(--md-comp-extended-fab-icon-size);
225
+ width: var(--md-comp-extended-fab-icon-size);
226
+ height: var(--md-comp-extended-fab-icon-size);
227
+ line-height: var(--md-comp-extended-fab-icon-size);
228
+ }
229
+
230
+ // The size axis. Every size sets the same six tokens and its type role -
231
+ // the geometry is written once, above, against those tokens.
232
+ @each $name, $s in $_extend-sizes {
233
+ &.#{$name} {
234
+ --md-comp-extended-fab-container-height: #{map.get($s, height)};
235
+ --md-comp-extended-fab-container-shape: #{map.get($s, shape)};
236
+ --md-comp-extended-fab-icon-size: #{map.get($s, icon)};
237
+ --md-comp-extended-fab-leading-space: #{map.get($s, space)};
238
+ --md-comp-extended-fab-icon-label-space: #{map.get($s, gap)};
239
+ --md-comp-extended-fab-trailing-space: #{map.get($s, space)};
240
+ @include typescale(map.get($s, label));
241
+ }
242
+ }
243
+
244
+ // The colour axis. `.primary-container` is the default and is named anyway,
245
+ // so all three roles are a class a page can be written with.
246
+ @each $role in ("primary", "secondary", "tertiary") {
247
+ &.#{$role}-container {
248
+ --md-comp-extended-fab-container-color: var(
249
+ --md-sys-color-#{$role}-container
250
+ );
251
+ --md-comp-extended-fab-label-text-color: var(
252
+ --md-sys-color-on-#{$role}-container
253
+ );
254
+ }
153
255
  }
154
256
  }
155
257
 
156
258
  // FAB. Icon-only .extra / .large is the 56dp regular FAB. Add .small for
157
- // the 40dp small FAB. The speed-dial host is .fixed-action-btn.
259
+ // the 40dp small FAB. The speed-dial host is .fab.
260
+ //
261
+ // Tokens (M3 md.comp.fab.*):
262
+ // --md-comp-fab-container-height 56dp
263
+ // --md-comp-fab-container-width 56dp
264
+ // --md-comp-fab-container-shape 16dp, corner.large - rounded, not a pill
265
+ // --md-comp-fab-icon-size 24dp
266
+ // .small, .medium and .extra.large reset those four; nothing else varies
267
+ // by size.
158
268
  :where(#{$_btn}).extra.circle,
159
269
  :where(#{$_btn}).large.circle {
270
+ --md-comp-fab-container-height: 56px;
271
+ --md-comp-fab-container-width: 56px;
272
+ --md-comp-fab-container-shape: 16px;
273
+ --md-comp-fab-icon-size: 24px;
274
+
160
275
  display: inline-flex;
276
+ flex: none;
161
277
  align-items: center;
162
278
  justify-content: center;
163
- width: 56px;
164
- height: 56px;
279
+ width: var(--md-comp-fab-container-width);
280
+ height: var(--md-comp-fab-container-height);
165
281
  padding: 0;
166
282
  border: none;
167
- border-radius: 16px;
283
+ border-radius: var(--md-comp-fab-container-shape);
168
284
  color: var(--md-sys-color-on-primary-container);
169
285
  background-color: var(--md-sys-color-primary-container);
170
286
  text-decoration: none;
@@ -174,11 +290,11 @@ $_btn: "button:not(#{$_not-btn}), a.button";
174
290
  @include z-depth("3");
175
291
  @include focus-visible;
176
292
 
177
- > i {
178
- font-size: 24px;
179
- width: 24px;
180
- height: 24px;
181
- line-height: 24px;
293
+ > #{$icon} {
294
+ font-size: var(--md-comp-fab-icon-size);
295
+ width: var(--md-comp-fab-icon-size);
296
+ height: var(--md-comp-fab-icon-size);
297
+ line-height: var(--md-comp-fab-icon-size);
182
298
  color: inherit;
183
299
  }
184
300
 
@@ -186,7 +302,7 @@ $_btn: "button:not(#{$_not-btn}), a.button";
186
302
  @include z-depth("4");
187
303
  background-color: color-mix(
188
304
  in oklab,
189
- var(--md-sys-color-on-primary-container) 8%,
305
+ var(--md-sys-color-on-primary-container) state-opacity("hover"),
190
306
  var(--md-sys-color-primary-container)
191
307
  );
192
308
  }
@@ -195,19 +311,25 @@ $_btn: "button:not(#{$_not-btn}), a.button";
195
311
  &:focus {
196
312
  background-color: color-mix(
197
313
  in oklab,
198
- var(--md-sys-color-on-primary-container) 10%,
314
+ var(--md-sys-color-on-primary-container) state-opacity("focus"),
199
315
  var(--md-sys-color-primary-container)
200
316
  );
201
317
  }
202
318
 
319
+ // Small FAB: 40dp container, corner.medium, icon stays 24dp.
203
320
  &.small {
204
- width: 40px;
205
- height: 40px;
206
- border-radius: 12px;
321
+ --md-comp-fab-container-height: 40px;
322
+ --md-comp-fab-container-width: 40px;
323
+ --md-comp-fab-container-shape: 12px;
324
+ }
207
325
 
208
- > i {
209
- font-size: 24px;
210
- }
326
+ // Medium FAB: 80dp container, 26dp icon. The corner continues the size
327
+ // ladder - 12dp small, 16dp default, 20dp here (corner.large-increased).
328
+ &.medium {
329
+ --md-comp-fab-container-height: 80px;
330
+ --md-comp-fab-container-width: 80px;
331
+ --md-comp-fab-container-shape: 20px;
332
+ --md-comp-fab-icon-size: 26px;
211
333
  }
212
334
 
213
335
  &.halfway-fab {
@@ -222,15 +344,34 @@ $_btn: "button:not(#{$_not-btn}), a.button";
222
344
  }
223
345
  }
224
346
 
347
+ // Large FAB: 96dp container, corner.extra-large, 36dp icon.
348
+ //
349
+ // `.large` is *also* the alias for the default FAB - `circle large` is
350
+ // `circle extra` - so the size only applies next to `.extra`, and the markup
351
+ // is `circle extra large`. Nesting it as `&.large` inside the block above
352
+ // would compile `.large.circle.large` as well, and every aliased 56dp FAB on
353
+ // a page would silently grow to 96dp.
354
+ :where(#{$_btn}).extra.circle.large {
355
+ --md-comp-fab-container-height: 96px;
356
+ --md-comp-fab-container-width: 96px;
357
+ --md-comp-fab-container-shape: 28px;
358
+ --md-comp-fab-icon-size: 36px;
359
+ }
360
+
225
361
  :where(#{$_btn}):disabled,
226
362
  :where(#{$_btn})[disabled],
227
363
  :where(#{$_btn}).disabled {
228
364
  @include btn-disabled;
229
365
  }
230
366
 
367
+ // The FAB host. M3 names this component "FAB"; `.fixed-action-btn` is the
368
+ // Materialize name and stays as an alias. Interpolate inside :is() - a bare
369
+ // comma list breaks out of its context (see $_bar in _navbar).
370
+ $_fab: ":is(.fab, .fixed-action-btn)";
371
+
231
372
  // Speed dial. .active (and :hover when the pointer can hover) opens the
232
373
  // menu. Motion is CSS. .click-to-toggle is the markup switch for click
233
- // mode. .fixed-action-btn stays the public class.
374
+ // mode. `.fab` is the public class; .fixed-action-btn is the old alias.
234
375
  //
235
376
  // Tokens:
236
377
  // --md-comp-fab-offset 16px from the corner
@@ -241,7 +382,7 @@ $_btn: "button:not(#{$_not-btn}), a.button";
241
382
  // --md-comp-fab-easing soft ease-in-out
242
383
  // --md-comp-fab-travel center-to-center step into the main disc
243
384
  // --md-comp-fab-hidden-transform toward / into the main button
244
- .fixed-action-btn {
385
+ #{$_fab} {
245
386
  --md-comp-fab-offset: 16px;
246
387
  --md-comp-fab-z: 997;
247
388
  --md-comp-fab-menu-gap: 16px;
@@ -409,7 +550,7 @@ $_btn: "button:not(#{$_not-btn}), a.button";
409
550
  width: 100%;
410
551
  height: 56px;
411
552
 
412
- > :is(a, button) > i {
553
+ > :is(a, button) > #{$icon} {
413
554
  opacity: 0;
414
555
  }
415
556
 
@@ -437,6 +578,18 @@ $_btn: "button:not(#{$_not-btn}), a.button";
437
578
  background: transparent;
438
579
  box-shadow: none;
439
580
  color: var(--md-sys-color-on-primary-container);
581
+
582
+ &:hover {
583
+ background-color: color-mix(in oklab, var(--md-sys-color-on-primary-container) state-opacity("hover"), transparent);
584
+ }
585
+
586
+ &:focus-visible {
587
+ background-color: color-mix(in oklab, var(--md-sys-color-on-primary-container) state-opacity("focus"), transparent);
588
+ }
589
+
590
+ &:active {
591
+ background-color: color-mix(in oklab, var(--md-sys-color-on-primary-container) state-opacity("pressed"), transparent);
592
+ }
440
593
  }
441
594
  }
442
595
  }
@@ -454,7 +607,7 @@ $_btn: "button:not(#{$_not-btn}), a.button";
454
607
  }
455
608
 
456
609
  @media (hover: hover) {
457
- .fixed-action-btn:not(.click-to-toggle):hover :is(ul, menu) {
610
+ #{$_fab}:not(.click-to-toggle):hover :is(ul, menu) {
458
611
  visibility: visible;
459
612
  transition-delay: 0s;
460
613
 
@@ -467,7 +620,7 @@ $_btn: "button:not(#{$_not-btn}), a.button";
467
620
  }
468
621
 
469
622
  @media (prefers-reduced-motion: reduce) {
470
- .fixed-action-btn {
623
+ #{$_fab} {
471
624
  --md-comp-fab-duration: 0s;
472
625
  --md-comp-fab-stagger: 0s;
473
626
  }