@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
@@ -0,0 +1,259 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // Search: the M3 search bar and the view it expands into.
4
+ //
5
+ // Anatomy is the HTML. A `<search class="search-bar">` is the bar - an
6
+ // optional leading icon or icon button, an `<input type="search">`, and
7
+ // optional trailing actions. `.searchbar` is the pre-1.0 alias.
8
+ //
9
+ // `.search-view` is the surface the bar expands into, and it is two different
10
+ // native elements because the two variants want two different behaviours:
11
+ //
12
+ // docked a plain element inside the bar, shown by dropping `hidden`.
13
+ // The bar keeps focus, which is the whole point - a modeless
14
+ // `<dialog>.show()` moves focus into the dialog and would take
15
+ // the caret out of the input the user is typing in.
16
+ // full-screen a `<dialog class="search-view full-screen">` opened with
17
+ // `showModal()`, so Escape and light dismiss come from the
18
+ // platform and `behaviors/dialogs.ts` rather than from here.
19
+ //
20
+ // Suggestions are Autocomplete's, not this component's. Put `autocomplete` on
21
+ // the bar's input and the combobox, its listbox and the keyboard contract
22
+ // behind them are the ones already implemented and tested. The search view
23
+ // holds content - recent searches, results, filters - and takes no composite
24
+ // role of its own; see SEMANTICS.md.
25
+ //
26
+ // Tokens follow md.comp.search-bar.* / md.comp.search-view.* at DSP 34.0.21
27
+ // bar 56dp, corner-full, surface-container-high, elevation 3
28
+ // input body-large / on-surface; the placeholder is M3's supporting
29
+ // text, hence on-surface-variant
30
+ // icons 24dp; leading on-surface, trailing on-surface-variant
31
+ // avatar 30dp, corner-full
32
+ // spacing 16dp at an end with no action, 4dp at one with an icon
33
+ // button, which carries its own 12dp inset
34
+ // view surface-container-high, corner-medium, elevation 3, 2dp
35
+ // below the bar
36
+ // full-screen fills the viewport, no corners, 72dp header
37
+ // divider outline
38
+ //
39
+ // The bar and the docked view are both the *contained* (Expressive) variant,
40
+ // which is what the 4dp/16dp spacing pair and the 2dp gap belong to; taking
41
+ // the shape from the divided/baseline set instead would mix two anatomies.
42
+ // `--md-comp-search-view-max-height` is a framework knob, not a DSP token -
43
+ // M3 sizes the docked view by its content and leaves the cap to the platform.
44
+ //
45
+ // Nest descendants inside these blocks, and keep `$_bar` wrapped in `:is()` -
46
+ // a bare list interpolated into `.search-view > #{$_bar}` breaks out of its
47
+ // context and emits a top-level rule.
48
+
49
+ $_bar: ":is(.search-bar, .searchbar)";
50
+
51
+ #{$_bar} {
52
+ --md-comp-search-bar-container-color: var(--md-sys-color-surface-container-high);
53
+ --md-comp-search-bar-container-height: 56px;
54
+ --md-comp-search-bar-container-shape: 9999px;
55
+ --md-comp-search-bar-leading-space: 16px;
56
+ --md-comp-search-bar-trailing-space: 16px;
57
+ --md-comp-search-bar-icon-label-space: 16px;
58
+ --md-comp-search-bar-icon-size: 24px;
59
+ --md-comp-search-bar-avatar-size: 30px;
60
+
61
+ position: relative;
62
+ box-sizing: border-box;
63
+ display: flex;
64
+ align-items: center;
65
+ gap: var(--md-comp-search-bar-icon-label-space);
66
+ width: 100%;
67
+ min-height: var(--md-comp-search-bar-container-height);
68
+ padding-inline: var(--md-comp-search-bar-leading-space)
69
+ var(--md-comp-search-bar-trailing-space);
70
+ border-radius: var(--md-comp-search-bar-container-shape);
71
+ background-color: var(--md-comp-search-bar-container-color);
72
+ color: var(--md-sys-color-on-surface);
73
+ @include z-depth("3");
74
+
75
+ // An icon *button* already insets its glyph by 12dp, so M3 pulls the end
76
+ // space in to 4dp beside one. A bare glyph keeps the full 16dp.
77
+ // A `form[method="dialog"]` wrapper is spelled out rather than folded into
78
+ // the compound: `:has()` may not be nested inside `:has()`, and because the
79
+ // argument list is forgiving, `form:has(> .icon-button)` in there would be
80
+ // dropped silently rather than reported.
81
+ &:has(> .icon-button:first-child),
82
+ &:has(> form:first-child > .icon-button) {
83
+ --md-comp-search-bar-leading-space: 4px;
84
+ }
85
+
86
+ // The docked view is a child of the bar but out of flow, so the last
87
+ // *control* is the last child that is not it - `:last-child` alone reads the
88
+ // view as the trailing element and leaves the icon button on 16dp.
89
+ &:has(> .icon-button:nth-last-child(1 of :not(.search-view))),
90
+ &:has(> form:nth-last-child(1 of :not(.search-view)) > .icon-button) {
91
+ --md-comp-search-bar-trailing-space: 4px;
92
+ }
93
+
94
+ // A leading or trailing action wrapped in `form[method="dialog"]` - how the
95
+ // full-screen view closes itself without script.
96
+ > form {
97
+ display: flex;
98
+ flex: 0 0 auto;
99
+ align-items: center;
100
+ margin: 0;
101
+ }
102
+
103
+ > #{$icon} {
104
+ flex: 0 0 auto;
105
+ font-size: var(--md-comp-search-bar-icon-size);
106
+ width: var(--md-comp-search-bar-icon-size);
107
+ height: var(--md-comp-search-bar-icon-size);
108
+ line-height: var(--md-comp-search-bar-icon-size);
109
+ color: var(--md-sys-color-on-surface);
110
+ }
111
+
112
+ > #{$icon}:nth-last-child(1 of :not(.search-view)) {
113
+ color: var(--md-sys-color-on-surface-variant);
114
+ }
115
+
116
+ // An account avatar, the one non-icon element M3 puts in the bar. A tappable
117
+ // one goes inside a button, which brings its own 48dp target.
118
+ > img {
119
+ flex: 0 0 auto;
120
+ width: var(--md-comp-search-bar-avatar-size);
121
+ height: var(--md-comp-search-bar-avatar-size);
122
+ border-radius: 50%;
123
+ object-fit: cover;
124
+ }
125
+
126
+ // The input carries none of the text-field chrome: no underline, no floating
127
+ // label, no 56dp block of its own. It is the bar that is the container.
128
+ > input {
129
+ flex: 1 1 auto;
130
+ min-width: 0;
131
+ height: auto;
132
+ margin: 0;
133
+ padding: 0;
134
+ border: none;
135
+ background-color: transparent;
136
+ box-shadow: none;
137
+ color: var(--md-sys-color-on-surface);
138
+ @include typescale("body-large");
139
+
140
+ &:focus {
141
+ outline: none;
142
+ }
143
+
144
+ &::placeholder {
145
+ color: var(--md-sys-color-on-surface-variant);
146
+ }
147
+
148
+ // The UA's own clear affordance duplicates the trailing icon button M3
149
+ // asks for, and cannot be styled to match it.
150
+ &::-webkit-search-cancel-button {
151
+ appearance: none;
152
+ }
153
+ }
154
+
155
+ // Pre-1.0 `.searchbar` sat in a navbar beside a fixed drawer.
156
+ &.has-sidebar {
157
+ margin-inline-start: 0;
158
+
159
+ @include bp-up("expanded") {
160
+ // `width: auto`, because the margin sits *outside* the base bar's
161
+ // `width: 100%`: the two together make the bar 100% + 300px wide and
162
+ // push its trailing edge past the viewport, on exactly the fixed-drawer
163
+ // layout this rule exists to preserve. The legacy bar had no width of
164
+ // its own, so the margin shrank it.
165
+ width: auto;
166
+ margin-inline-start: 300px;
167
+ }
168
+ }
169
+ }
170
+
171
+ .search-view {
172
+ --md-comp-search-view-container-color: var(--md-sys-color-surface-container-high);
173
+ --md-comp-search-view-container-shape: 12px;
174
+ --md-comp-search-view-header-height: 56px;
175
+ --md-comp-search-view-divider-color: var(--md-sys-color-outline);
176
+ --md-comp-search-view-bar-results-gap: 2px;
177
+ --md-comp-search-view-max-height: 60vh;
178
+
179
+ box-sizing: border-box;
180
+ position: absolute;
181
+ z-index: 1;
182
+ inset-inline: 0;
183
+ top: calc(100% + var(--md-comp-search-view-bar-results-gap));
184
+ display: flex;
185
+ flex-direction: column;
186
+ max-height: var(--md-comp-search-view-max-height);
187
+ // The surface scrolls when its content is loose (a <div> of results); when
188
+ // the content is a `.list` the list scrolls instead, so a header and its
189
+ // divider stay put. Which of the two moves depends on the composition, so
190
+ // both declare it.
191
+ overflow-y: auto;
192
+ overscroll-behavior: contain;
193
+ border-radius: var(--md-comp-search-view-container-shape);
194
+ background-color: var(--md-comp-search-view-container-color);
195
+ color: var(--md-sys-color-on-surface);
196
+ @include z-depth("3");
197
+
198
+ // Two ways this surface is closed, and an author `display` beats both of the
199
+ // UA rules that do the hiding - `[hidden] { display: none }` for the docked
200
+ // element and `dialog:not([open]) { display: none }` for the full-screen
201
+ // one. The second is the one that bites: without it a closed <dialog> paints
202
+ // an opaque panel over the whole viewport, and no visual baseline exists to
203
+ // catch it on the change that introduces the page.
204
+ &[hidden],
205
+ &:not([open]):is(dialog) {
206
+ display: none;
207
+ }
208
+
209
+ // The view's own header is a search bar with the pill taken off - the same
210
+ // markup, so nothing new to learn for the full-screen variant.
211
+ > #{$_bar} {
212
+ flex: 0 0 auto;
213
+ min-height: var(--md-comp-search-view-header-height);
214
+ border-radius: 0;
215
+ background-color: transparent;
216
+ @include z-depth("0");
217
+ }
218
+
219
+ > hr {
220
+ flex: 0 0 auto;
221
+ box-sizing: border-box;
222
+ width: 100%;
223
+ height: 1px;
224
+ margin: 0;
225
+ padding: 0;
226
+ border: 0;
227
+ background-color: var(--md-comp-search-view-divider-color);
228
+ }
229
+
230
+ > .list {
231
+ flex: 1 1 auto;
232
+ min-height: 0;
233
+ overflow-y: auto;
234
+ margin: 0;
235
+ padding-block: 8px;
236
+ }
237
+ }
238
+
239
+ // Full-screen. The UA caps a modal dialog at `calc(100% - 2em - 6px)`, so
240
+ // width: 100% alone leaves a strip of scrim down the side - the same clearing
241
+ // dialog.max and the bottom sheet already do.
242
+ .search-view.full-screen {
243
+ --md-comp-search-view-container-shape: 0;
244
+ --md-comp-search-view-header-height: 72px;
245
+ --md-comp-search-view-max-height: none;
246
+
247
+ position: fixed;
248
+ inset: 0;
249
+ width: 100%;
250
+ min-width: 0;
251
+ max-width: none;
252
+ height: 100%;
253
+ margin: 0;
254
+
255
+ &::backdrop {
256
+ background: none;
257
+ backdrop-filter: none;
258
+ }
259
+ }
@@ -0,0 +1,188 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // Segmented buttons.
4
+ //
5
+ // Anatomy is the HTML. A <fieldset class="segmented-button"> is the group and
6
+ // its <legend> names it. Each segment is an <input> and the
7
+ // <label class="segment"> beside it, tied by id: radios make the group
8
+ // single-select, checkboxes make it multi-select, and the input type is the
9
+ // whole difference.
10
+ //
11
+ // The control is the label's *sibling*, not its child, which is the same
12
+ // arrangement filter chips use and for the same reason: to
13
+ // forms/_radio-buttons and forms/_checkboxes, a <label> wrapping one of those
14
+ // inputs *is* a radio or a checkbox. They would paint a 20dp ring on the
15
+ // input, stretch the label to a 48dp row and, for a checkbox, mask a
16
+ // checkmark onto its ::before - none of which a segment wants, and all of
17
+ // which outweigh a segment rule that only names two classes.
18
+ //
19
+ // Nothing here is scripted, and that is the point. The browser owns the
20
+ // checked state and, for radios, the arrow-key model a single-select group
21
+ // promises: SEMANTICS rule 2 asks that a composite keyboard contract be
22
+ // implemented before the role is claimed, and the native one is implemented
23
+ // by the platform. No ARIA role is declared, so none is withheld.
24
+ //
25
+ // Tokens follow M3, md.comp.outlined-segmented-button.* (DSP 34.0.21):
26
+ // container 40dp, corner.full, 1dp outline, dividers between segments
27
+ // segment 12dp insets, 18dp icon, 8dp icon-label space, label-large
28
+ // unselected on-surface on a transparent container
29
+ // selected on-secondary-container on secondary-container
30
+ // state layer 8% hover, 10% press (focus is the ring, not a layer)
31
+ // disabled label and icon at 38%, outline at 12%
32
+ .segmented-button {
33
+ --md-comp-outlined-segmented-button-container-height: 40px;
34
+ --md-comp-outlined-segmented-button-container-shape: 9999px;
35
+ --md-comp-outlined-segmented-button-outline-width: 1px;
36
+ --md-comp-outlined-segmented-button-outline-color: var(--md-sys-color-outline);
37
+ --md-comp-outlined-segmented-button-leading-space: 12px;
38
+ --md-comp-outlined-segmented-button-trailing-space: 12px;
39
+ --md-comp-outlined-segmented-button-icon-size: 18px;
40
+ --md-comp-outlined-segmented-button-icon-label-space: 8px;
41
+ --md-comp-outlined-segmented-button-label-text-color: var(--md-sys-color-on-surface);
42
+ --md-comp-outlined-segmented-button-selected-container-color: var(--md-sys-color-secondary-container);
43
+ --md-comp-outlined-segmented-button-selected-label-text-color: var(--md-sys-color-on-secondary-container);
44
+
45
+ // A <fieldset> arrives with a border, 16dp of padding and a min-width; the
46
+ // group's own outline replaces all three.
47
+ box-sizing: border-box;
48
+ // Equal columns across whatever inline size the group is given, which is
49
+ // what M3 specifies - material-web's own set is `grid-auto-columns: 1fr` on
50
+ // a full-width grid, not a shrink-wrapped row. Constrain the group with a
51
+ // width or a wrapper when it should be narrower than its container.
52
+ display: grid;
53
+ grid-auto-flow: column;
54
+ grid-auto-columns: 1fr;
55
+ min-inline-size: 0;
56
+ margin: 0;
57
+ padding: 0;
58
+ background-color: transparent;
59
+ border: var(--md-comp-outlined-segmented-button-outline-width) solid
60
+ var(--md-comp-outlined-segmented-button-outline-color);
61
+ border-radius: var(--md-comp-outlined-segmented-button-container-shape);
62
+ // Squares the segments off against the pill, so the end segments' fill and
63
+ // their focus ring both stop at the outline.
64
+ overflow: hidden;
65
+
66
+ & + & {
67
+ margin-block-start: 24px;
68
+ }
69
+
70
+ // M3's anatomy has no visible group label. A set of radios is only a set
71
+ // because a <fieldset> says so, and a <fieldset> is only named because of
72
+ // its <legend> — so the legend is the accessible name and nothing else.
73
+ //
74
+ // There is deliberately no modifier that shows it. The root is both the
75
+ // fieldset and the grid of segments, so a legend back in flow is another
76
+ // column beside them; putting it above the pill would take an inner
77
+ // wrapper the markup does not have. A group that needs a visible label
78
+ // writes one before the fieldset and leaves the legend as the name.
79
+ > legend {
80
+ @include visually-hidden();
81
+ }
82
+
83
+ // The control carries the state and the form value; the label is the
84
+ // segment. Clipped rather than `display: none` so it stays focusable — and
85
+ // so the arrow keys still move between the radios.
86
+ > input {
87
+ @include visually-hidden();
88
+ border: 0;
89
+ appearance: none;
90
+ }
91
+
92
+ > .segment {
93
+ // Read back by the state layer below, so hover and press are one rule
94
+ // each rather than one per selected/unselected pair.
95
+ --_label-color: var(--md-comp-outlined-segmented-button-label-text-color);
96
+ --_container-color: transparent;
97
+
98
+ display: inline-flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ gap: var(--md-comp-outlined-segmented-button-icon-label-space);
102
+ margin: 0;
103
+ min-block-size: calc(
104
+ var(--md-comp-outlined-segmented-button-container-height) - 2 *
105
+ var(--md-comp-outlined-segmented-button-outline-width)
106
+ );
107
+ padding-inline: var(--md-comp-outlined-segmented-button-leading-space)
108
+ var(--md-comp-outlined-segmented-button-trailing-space);
109
+ white-space: nowrap;
110
+ cursor: pointer;
111
+ user-select: none;
112
+ color: var(--_label-color);
113
+ background-color: var(--_container-color);
114
+ transition: background-color .2s ease, color .2s ease;
115
+ @include typescale("label-large");
116
+ }
117
+
118
+ // Divider, not a border on every segment: the group's own outline is the
119
+ // outer edge, so only the joins are drawn. `:first-of-type` counts labels,
120
+ // and every segment is one — the legend and the inputs are other types.
121
+ > .segment:not(:first-of-type) {
122
+ border-inline-start: var(--md-comp-outlined-segmented-button-outline-width) solid
123
+ var(--md-comp-outlined-segmented-button-outline-color);
124
+ }
125
+
126
+ // Selected is the input's business. The adjacency is what lets the label
127
+ // read a state only the control has, with no class for the page to sync.
128
+ > input:checked + .segment {
129
+ --_label-color: var(--md-comp-outlined-segmented-button-selected-label-text-color);
130
+ --_container-color: var(--md-comp-outlined-segmented-button-selected-container-color);
131
+ }
132
+
133
+ > .segment > #{$icon} {
134
+ font-size: var(--md-comp-outlined-segmented-button-icon-size);
135
+ width: var(--md-comp-outlined-segmented-button-icon-size);
136
+ height: var(--md-comp-outlined-segmented-button-icon-size);
137
+ line-height: var(--md-comp-outlined-segmented-button-icon-size);
138
+ color: inherit;
139
+ }
140
+
141
+ > input:not(:disabled) + .segment {
142
+ &:hover {
143
+ background-color: color-mix(
144
+ in oklab,
145
+ var(--_label-color) state-opacity("hover"),
146
+ var(--_container-color)
147
+ );
148
+ }
149
+
150
+ &:active {
151
+ background-color: color-mix(
152
+ in oklab,
153
+ var(--_label-color) state-opacity("pressed"),
154
+ var(--_container-color)
155
+ );
156
+ }
157
+ }
158
+
159
+ // The ring is drawn inside the segment: `overflow: hidden` above would clip
160
+ // an outline sitting outside it, and the end segments would lose half of it
161
+ // to the pill's own corner.
162
+ > input:focus-visible + .segment {
163
+ @include focus-ring();
164
+ outline-offset: -3px;
165
+ }
166
+
167
+ > input:disabled + .segment {
168
+ --_label-color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
169
+ cursor: default;
170
+ }
171
+ }
172
+
173
+ .segmented-button:disabled {
174
+ --md-comp-outlined-segmented-button-outline-color: color-mix(
175
+ in oklab,
176
+ var(--md-sys-color-on-surface) 12%,
177
+ transparent
178
+ );
179
+ --md-comp-outlined-segmented-button-label-text-color: color-mix(
180
+ in oklab,
181
+ var(--md-sys-color-on-surface) 38%,
182
+ transparent
183
+ );
184
+
185
+ > .segment {
186
+ cursor: default;
187
+ }
188
+ }
@@ -137,7 +137,7 @@ $_sheet: "dialog.side-sheet, dialog.right, dialog.right-sheet, dialog.left, dial
137
137
  color: var(--md-sys-color-on-surface-variant);
138
138
  box-shadow: none;
139
139
 
140
- > i {
140
+ > #{$icon} {
141
141
  font-size: 24px;
142
142
  width: 24px;
143
143
  height: 24px;
@@ -145,7 +145,7 @@ $_sheet: "dialog.side-sheet, dialog.right, dialog.right-sheet, dialog.left, dial
145
145
  }
146
146
 
147
147
  &:hover {
148
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
148
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
149
149
  }
150
150
 
151
151
  &:focus-visible {
@@ -14,7 +14,7 @@
14
14
  // action label-large / inverse-primary text button
15
15
  // close 24dp icon, inverse-on-surface
16
16
  // inset 8dp compact, 24dp from the bottom on larger screens
17
- // placement bottom of the UI; centered from the small breakpoint
17
+ // placement bottom of the UI; centered from the Medium breakpoint
18
18
  //
19
19
  // Snackbars must not interrupt browsing: the container is pointer-events
20
20
  // none except on the bar, and the live region is polite. One at a time
@@ -48,7 +48,7 @@ $_bar: ".snackbar";
48
48
  pointer-events: auto;
49
49
  }
50
50
 
51
- @include bp-up("small") {
51
+ @include bp-up("medium") {
52
52
  --md-comp-snackbar-container-margin-leading: 24px;
53
53
  --md-comp-snackbar-container-margin-trailing: 24px;
54
54
  --md-comp-snackbar-container-margin-bottom: 24px;
@@ -78,6 +78,11 @@ $_bar: ".snackbar";
78
78
  --md-comp-snackbar-icon-color: var(--md-sys-color-inverse-on-surface);
79
79
 
80
80
  box-sizing: border-box;
81
+ // Swipe-to-dismiss is horizontal, so claim that axis and leave vertical
82
+ // panning to the page. Without this the browser takes a horizontal swipe for
83
+ // itself and cancels the drag mid-gesture: `preventDefault()` on a
84
+ // `pointermove` cannot stop it the way it stopped a `touchmove`.
85
+ touch-action: pan-y;
81
86
  display: flex;
82
87
  align-items: center;
83
88
  gap: 8px;
@@ -95,7 +100,7 @@ $_bar: ".snackbar";
95
100
  @include z-depth("3");
96
101
  cursor: default;
97
102
 
98
- @include bp-up("small") {
103
+ @include bp-up("medium") {
99
104
  width: max-content;
100
105
  min-width: min(344px, 100%);
101
106
  }
@@ -116,7 +121,7 @@ $_bar: ".snackbar";
116
121
  }
117
122
 
118
123
  // Optional leading icon (BeerCSS). M3 snackbars have no leading icon.
119
- > i:first-child {
124
+ > #{$icon}:first-child {
120
125
  flex: 0 0 auto;
121
126
  font-size: 24px;
122
127
  width: 24px;
@@ -126,7 +131,7 @@ $_bar: ".snackbar";
126
131
  }
127
132
 
128
133
  // Inline links in the supporting text use the action color.
129
- a:not(.button) {
134
+ a:not(.button, .icon-button) {
130
135
  color: var(--md-comp-snackbar-action-label-text-color);
131
136
  }
132
137
 
@@ -145,8 +150,8 @@ $_bar: ".snackbar";
145
150
  }
146
151
 
147
152
  // Text action — inverse-primary, not the filled common-button default.
148
- > :is(button, a.button):not(.circle),
149
- > nav > :is(button, a.button):not(.circle) {
153
+ > :is(button, a.button):not(.circle, .icon-button),
154
+ > nav > :is(button, a.button):not(.circle, .icon-button) {
150
155
  flex: 0 0 auto;
151
156
  height: 40px;
152
157
  min-width: 48px;
@@ -159,12 +164,12 @@ $_bar: ".snackbar";
159
164
  @include typescale("label-large");
160
165
 
161
166
  &:hover {
162
- background-color: color-mix(in oklab, var(--md-sys-color-inverse-primary) 8%, transparent);
167
+ background-color: color-mix(in oklab, var(--md-sys-color-inverse-primary) state-opacity("hover"), transparent);
163
168
  }
164
169
 
165
170
  &:active,
166
171
  &:focus {
167
- background-color: color-mix(in oklab, var(--md-sys-color-inverse-primary) 10%, transparent);
172
+ background-color: color-mix(in oklab, var(--md-sys-color-inverse-primary) state-opacity("focus"), transparent);
168
173
  }
169
174
 
170
175
  &:focus-visible {
@@ -186,7 +191,7 @@ $_bar: ".snackbar";
186
191
  color: var(--md-comp-snackbar-icon-color);
187
192
  box-shadow: none;
188
193
 
189
- > i {
194
+ > #{$icon} {
190
195
  font-size: 24px;
191
196
  width: 24px;
192
197
  height: 24px;
@@ -194,12 +199,12 @@ $_bar: ".snackbar";
194
199
  }
195
200
 
196
201
  &:hover {
197
- background-color: color-mix(in oklab, var(--md-sys-color-inverse-on-surface) 8%, transparent);
202
+ background-color: color-mix(in oklab, var(--md-sys-color-inverse-on-surface) state-opacity("hover"), transparent);
198
203
  }
199
204
 
200
205
  &:active,
201
206
  &:focus {
202
- background-color: color-mix(in oklab, var(--md-sys-color-inverse-on-surface) 10%, transparent);
207
+ background-color: color-mix(in oklab, var(--md-sys-color-inverse-on-surface) state-opacity("focus"), transparent);
203
208
  }
204
209
 
205
210
  &:focus-visible {
@@ -226,7 +231,7 @@ $_bar: ".snackbar";
226
231
 
227
232
  // Static overlay. In-flow without this (docs previews). Not AutoInit —
228
233
  // add and remove the class yourself, or use the constructor for timing.
229
- // Default: bottom of the UI, centered from the small breakpoint.
234
+ // Default: bottom of the UI, centered from the Medium breakpoint.
230
235
  #{$_bar}.active {
231
236
  position: fixed;
232
237
  z-index: 10000;
@@ -238,7 +243,7 @@ $_bar: ".snackbar";
238
243
  width: max-content;
239
244
  max-width: min(672px, calc(100% - 48px));
240
245
 
241
- @include bp-down("small") {
246
+ @include bp-down("medium") {
242
247
  left: 8px;
243
248
  right: 8px;
244
249
  bottom: max(8px, env(safe-area-inset-bottom, 0px));
@@ -251,7 +256,7 @@ $_bar: ".snackbar";
251
256
  top: 24px;
252
257
  bottom: auto;
253
258
 
254
- @include bp-down("small") {
259
+ @include bp-down("medium") {
255
260
  top: 8px;
256
261
  left: 8px;
257
262
  right: 8px;