@expressivecss/expressive 0.7.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.
- package/CHANGELOG.md +1266 -1
- package/README.md +14 -11
- package/dist/css/expressive.css +4215 -1668
- package/dist/css/expressive.css.map +1 -1
- package/dist/css/expressive.min.css +1 -1
- package/dist/css/expressive.min.css.map +1 -1
- package/dist/js/expressive.cjs +1231 -1253
- package/dist/js/expressive.cjs.map +4 -4
- package/dist/js/expressive.js +1231 -1253
- package/dist/js/expressive.js.map +4 -4
- package/dist/js/expressive.min.js +12 -12
- package/dist/js/expressive.min.js.map +4 -4
- package/dist/js/expressive.mjs +1231 -1253
- package/dist/js/expressive.mjs.map +4 -4
- package/dist/types/behaviors/bottomSheets.d.ts +19 -11
- package/dist/types/behaviors/sheetDrag.d.ts +40 -0
- package/dist/types/behaviors/sideSheets.d.ts +2 -9
- package/dist/types/components/autocomplete.d.ts +15 -3
- package/dist/types/components/buttons.d.ts +5 -1
- package/dist/types/components/carousel.d.ts +87 -53
- package/dist/types/components/chips.d.ts +21 -1
- package/dist/types/components/expandingCard.d.ts +31 -0
- package/dist/types/components/index.d.ts +21 -20
- package/dist/types/components/lightbox.d.ts +1 -0
- package/dist/types/components/menu.d.ts +7 -1
- package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
- package/dist/types/components/registry.d.ts +8 -8
- package/dist/types/components/slider.d.ts +26 -85
- package/dist/types/components/snackbar.d.ts +20 -5
- package/dist/types/components/tabs.d.ts +12 -0
- package/dist/types/components/timepicker.d.ts +12 -9
- package/dist/types/core/utils.d.ts +64 -53
- package/dist/types/index.d.ts +8 -9
- package/package.json +15 -1
- package/src/sass/README.md +61 -11
- package/src/sass/abstracts/_breakpoints.scss +17 -19
- package/src/sass/abstracts/_mixins.scss +103 -21
- package/src/sass/abstracts/_variables.scss +50 -5
- package/src/sass/base/_global.scss +6 -24
- package/src/sass/base/_grid.scss +15 -12
- package/src/sass/base/_normalize.scss +34 -38
- package/src/sass/base/_typography.scss +4 -18
- package/src/sass/components/_badges.scss +2 -3
- package/src/sass/components/_banners.scss +298 -0
- package/src/sass/components/_bottom-app-bar.scss +180 -0
- package/src/sass/components/_bottom-sheet.scss +12 -4
- package/src/sass/components/_button-groups.scss +186 -0
- package/src/sass/components/_buttons.scss +245 -92
- package/src/sass/components/_cards.scss +494 -73
- package/src/sass/components/_carousel.scss +562 -68
- package/src/sass/components/_chips.scss +64 -5
- package/src/sass/components/_datepicker.scss +24 -24
- package/src/sass/components/_dialog.scss +17 -11
- package/src/sass/components/_drag-handle.scss +130 -0
- package/src/sass/components/_expanding-card.scss +260 -0
- package/src/sass/components/_fab-menu.scss +232 -0
- package/src/sass/components/_floating-sheet.scss +86 -0
- package/src/sass/components/_icon-buttons.scss +194 -0
- package/src/sass/components/_icons-material-design.scss +16 -4
- package/src/sass/components/_index.scss +22 -6
- package/src/sass/components/_list.scss +16 -13
- package/src/sass/components/_loading-indicator.scss +106 -0
- package/src/sass/components/_menu.scss +335 -61
- package/src/sass/components/_navbar.scss +71 -24
- package/src/sass/components/_navigation-bar.scss +13 -10
- package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
- package/src/sass/components/_navigation-rail.scss +22 -22
- package/src/sass/components/_page-footer.scss +19 -16
- package/src/sass/components/_pagination.scss +24 -6
- package/src/sass/components/_panes.scss +52 -11
- package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
- package/src/sass/components/_scrim.scss +37 -0
- package/src/sass/components/_search.scss +259 -0
- package/src/sass/components/_segmented-buttons.scss +188 -0
- package/src/sass/components/_side-sheet.scss +2 -2
- package/src/sass/components/_snackbar.scss +20 -15
- package/src/sass/components/_split-button.scss +196 -0
- package/src/sass/components/_tabs.scss +7 -8
- package/src/sass/components/_timepicker.scss +67 -58
- package/src/sass/components/_toolbar.scss +206 -80
- package/src/sass/components/forms/_checkboxes.scss +49 -12
- package/src/sass/components/forms/_fieldset.scss +4 -4
- package/src/sass/components/forms/_file-input.scss +8 -1
- package/src/sass/components/forms/_index.scss +1 -1
- package/src/sass/components/forms/_input-fields.scss +50 -44
- package/src/sass/components/forms/_radio-buttons.scss +42 -10
- package/src/sass/components/forms/_select.scss +0 -1
- package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
- package/src/sass/components/forms/_switches.scss +6 -7
- package/src/sass/tokens/_index.scss +4 -1
- package/src/sass/tokens/_reference.scss +70 -61
- package/src/sass/tokens/_state.scss +24 -0
- package/src/sass/tokens/_theme.scss +6 -3
- package/src/sass/tokens/_vibrant.scss +76 -0
- package/src/sass/utilities/_colors.scss +14 -1
- package/src/sass/utilities/_helpers.scss +5 -5
- package/src/sass/utilities/_visibility.scss +48 -26
- package/dist/types/behaviors/waves.d.ts +0 -15
- package/dist/types/components/parallax.d.ts +0 -16
- package/dist/types/components/range.d.ts +0 -43
- package/dist/types/core/bounding.d.ts +0 -6
- package/dist/types/core/edges.d.ts +0 -6
- package/src/sass/components/_parallax.scss +0 -104
- package/src/sass/components/_pulse.scss +0 -37
- 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
|
-
>
|
|
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)
|
|
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
|
|
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("
|
|
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("
|
|
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
|
-
>
|
|
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)
|
|
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)
|
|
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
|
-
>
|
|
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)
|
|
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)
|
|
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
|
|
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("
|
|
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("
|
|
259
|
+
@include bp-down("medium") {
|
|
255
260
|
top: 8px;
|
|
256
261
|
left: 8px;
|
|
257
262
|
right: 8px;
|