@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,298 @@
|
|
|
1
|
+
@use "../abstracts" as *;
|
|
2
|
+
|
|
3
|
+
// Banners.
|
|
4
|
+
//
|
|
5
|
+
// Anatomy is the HTML. A .banner is the container. A <p> is the message. An
|
|
6
|
+
// optional leading #{$icon} leads it, an optional .actions holds the buttons,
|
|
7
|
+
// and an optional trailing .icon-button dismisses it. .rich adds a heading
|
|
8
|
+
// above the message and takes an <img> instead of an icon.
|
|
9
|
+
//
|
|
10
|
+
// A banner is persistent and in flow: it stays until the user acts on it, and
|
|
11
|
+
// it pushes the page down rather than covering it. That is the whole
|
|
12
|
+
// difference from a snackbar, which is transient, floats over the page and
|
|
13
|
+
// dismisses itself. Nothing here is scripted - dismissing a banner is removing
|
|
14
|
+
// it, which is the page's business, not the framework's.
|
|
15
|
+
//
|
|
16
|
+
// The action row is .actions and never a <nav> (SEMANTICS rule 4): these are
|
|
17
|
+
// commands, not destinations. No composite role is declared, so none is
|
|
18
|
+
// withheld. And a .banner is not `role="banner"` - that landmark is the page
|
|
19
|
+
// header, and semantics.json blocks the confusion in both spellings.
|
|
20
|
+
//
|
|
21
|
+
// Tokens follow M3 Expressive, md.comp.banners.* (DSP 34.0.21), which is three
|
|
22
|
+
// families: `banners` for the colours both variants share, `banners.basic` and
|
|
23
|
+
// `banners.rich` for their geometry.
|
|
24
|
+
//
|
|
25
|
+
// standard surface-container container, on-surface text and icon
|
|
26
|
+
// vibrant primary-container container, on-primary-container text
|
|
27
|
+
// close primary, whichever variant it sits in
|
|
28
|
+
// basic 56dp tall, 4dp insets, 28dp corners (0 when .square)
|
|
29
|
+
// 48dp icon container around a 24dp icon
|
|
30
|
+
// body-medium text, 14dp above and below
|
|
31
|
+
// actions 8dp apart with 8dp on either side of the row
|
|
32
|
+
// basic mobile 112dp tall, 8dp top inset, actions on their own line
|
|
33
|
+
// rich 28dp corners, 12dp insets, 24dp icon or an 80dp square image
|
|
34
|
+
// title body-medium at weight 500, 14dp above and below
|
|
35
|
+
// actions 12dp under the message
|
|
36
|
+
//
|
|
37
|
+
// Several token values go unspent, and every one of them is unspendable rather
|
|
38
|
+
// than forgotten.
|
|
39
|
+
//
|
|
40
|
+
// Three describe something this does not draw. `basic.two-lines.*` (a 64dp
|
|
41
|
+
// container with 8dp text insets) needs a line count, which CSS cannot see, so
|
|
42
|
+
// the container grows past 56dp on its own instead. `basic.centered.body-text
|
|
43
|
+
// .trailing-space` belongs to a centered basic variant that is not shipped. And
|
|
44
|
+
// both `body-text.trailing-space: 48px` values reserve room for the close
|
|
45
|
+
// button; here the button occupies that room itself, in flow in the basic row
|
|
46
|
+
// and in its own grid column in the rich one, so the reserve tracks the button
|
|
47
|
+
// rather than a constant.
|
|
48
|
+
//
|
|
49
|
+
// The rest are one gap M3 names twice, from either side, where spending both
|
|
50
|
+
// would double it. `rich.leading-element.trailing-space` restates
|
|
51
|
+
// `rich.icon-text-space` / `rich.image-text-space`; the *-text-space one is
|
|
52
|
+
// written. `basic.actions-close-button-space` (8dp, 4dp on mobile) restates
|
|
53
|
+
// `basic.actions.trailing-space` - the same two numbers - and the padding is
|
|
54
|
+
// written. `rich.body-text.text-actions-space: 8px` and
|
|
55
|
+
// `rich.actions.top-space: 12px` both name the gap under the message; the
|
|
56
|
+
// larger is written, since actions 8dp under body text read as part of it.
|
|
57
|
+
// `rich.actions.{leading,trailing,bottom}-space` and
|
|
58
|
+
// `rich.leading-element.bottom-space` are all 12dp, which is the container's
|
|
59
|
+
// own inset, already spent by its padding.
|
|
60
|
+
//
|
|
61
|
+
// One token is renamed on the way in. M3's `basic.body-text.leading-space` is
|
|
62
|
+
// 0dp - the icon container carries the inset - and the 16dp / 12dp pair lives
|
|
63
|
+
// under `basic.{round,square}.no-icon.body-text.leading-space`. The custom
|
|
64
|
+
// property is named for the case it serves, not for the 0dp token it is not.
|
|
65
|
+
.banner {
|
|
66
|
+
--md-comp-banners-color: var(--md-sys-color-surface-container);
|
|
67
|
+
--md-comp-banners-body-text-color: var(--md-sys-color-on-surface);
|
|
68
|
+
--md-comp-banners-title-text-color: var(--md-sys-color-on-surface);
|
|
69
|
+
--md-comp-banners-icon-color: var(--md-sys-color-on-surface);
|
|
70
|
+
--md-comp-banners-close-button-color: var(--md-sys-color-primary);
|
|
71
|
+
|
|
72
|
+
box-sizing: border-box;
|
|
73
|
+
color: var(--md-comp-banners-body-text-color);
|
|
74
|
+
background-color: var(--md-comp-banners-color);
|
|
75
|
+
@include typescale("body-medium");
|
|
76
|
+
|
|
77
|
+
// The close button is the one control M3 colours from the banner family
|
|
78
|
+
// rather than from its own. The action buttons are ordinary text buttons and
|
|
79
|
+
// are left alone - `primary` clears 5:1 on both container colours.
|
|
80
|
+
//
|
|
81
|
+
// The `:not()` is load-bearing, not defensive. `_icon-buttons.scss` sets this
|
|
82
|
+
// very token for the disabled state at the same (0,2,0) specificity, and
|
|
83
|
+
// banners is forwarded after it, so an unscoped override wins the tie and
|
|
84
|
+
// repaints a disabled close button at full-strength primary. Scoped, the rule
|
|
85
|
+
// simply stops matching and the disabled colour stands.
|
|
86
|
+
> .icon-button:not(:disabled, [disabled], [aria-disabled="true"], .disabled) {
|
|
87
|
+
--md-comp-icon-button-color: var(--md-comp-banners-close-button-color);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The two colour variants. Standard is the default, so only vibrant is a class.
|
|
92
|
+
.banner.vibrant {
|
|
93
|
+
--md-comp-banners-color: var(--md-sys-color-primary-container);
|
|
94
|
+
--md-comp-banners-body-text-color: var(--md-sys-color-on-primary-container);
|
|
95
|
+
--md-comp-banners-title-text-color: var(--md-sys-color-on-primary-container);
|
|
96
|
+
--md-comp-banners-icon-color: var(--md-sys-color-on-primary-container);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Basic: one row of icon, message, actions and close. Every layout rule is
|
|
100
|
+
// scoped away from .rich rather than left to the cascade, so the two variants
|
|
101
|
+
// tie on specificity and neither can quietly reach into the other.
|
|
102
|
+
.banner:not(.rich) {
|
|
103
|
+
--md-comp-banners-basic-height: 56px;
|
|
104
|
+
--md-comp-banners-basic-shape: 28px;
|
|
105
|
+
--md-comp-banners-basic-leading-space: 4px;
|
|
106
|
+
--md-comp-banners-basic-trailing-space: 4px;
|
|
107
|
+
--md-comp-banners-basic-top-space: 4px;
|
|
108
|
+
--md-comp-banners-basic-bottom-space: 4px;
|
|
109
|
+
--md-comp-banners-basic-icon-container-size: 48px;
|
|
110
|
+
--md-comp-banners-basic-icon-size: 24px;
|
|
111
|
+
--md-comp-banners-basic-body-text-top-space: 14px;
|
|
112
|
+
--md-comp-banners-basic-body-text-bottom-space: 14px;
|
|
113
|
+
--md-comp-banners-basic-no-icon-body-text-leading-space: 16px;
|
|
114
|
+
--md-comp-banners-basic-actions-between-space: 8px;
|
|
115
|
+
--md-comp-banners-basic-actions-leading-space: 8px;
|
|
116
|
+
--md-comp-banners-basic-actions-trailing-space: 8px;
|
|
117
|
+
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
min-block-size: var(--md-comp-banners-basic-height);
|
|
121
|
+
padding-block: var(--md-comp-banners-basic-top-space)
|
|
122
|
+
var(--md-comp-banners-basic-bottom-space);
|
|
123
|
+
padding-inline: var(--md-comp-banners-basic-leading-space)
|
|
124
|
+
var(--md-comp-banners-basic-trailing-space);
|
|
125
|
+
border-radius: var(--md-comp-banners-basic-shape);
|
|
126
|
+
|
|
127
|
+
> #{$icon} {
|
|
128
|
+
flex: 0 0 auto;
|
|
129
|
+
display: inline-flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
inline-size: var(--md-comp-banners-basic-icon-container-size);
|
|
133
|
+
block-size: var(--md-comp-banners-basic-icon-container-size);
|
|
134
|
+
font-size: var(--md-comp-banners-basic-icon-size);
|
|
135
|
+
line-height: var(--md-comp-banners-basic-icon-size);
|
|
136
|
+
color: var(--md-comp-banners-icon-color);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// `flex-basis: 0`, not `auto`. A wrapped flex container breaks lines on an
|
|
140
|
+
// item's *hypothetical* size - what it would be before shrinking - so a
|
|
141
|
+
// message long enough to fill the row takes a line of its own and leaves the
|
|
142
|
+
// icon stranded on the one above it. From zero it contributes nothing to that
|
|
143
|
+
// decision and grows into whatever the row has left, which is also what puts
|
|
144
|
+
// the actions hard against the trailing edge.
|
|
145
|
+
> p {
|
|
146
|
+
flex: 1 1 0;
|
|
147
|
+
min-inline-size: 0;
|
|
148
|
+
margin: 0;
|
|
149
|
+
padding-block: var(--md-comp-banners-basic-body-text-top-space)
|
|
150
|
+
var(--md-comp-banners-basic-body-text-bottom-space);
|
|
151
|
+
color: inherit;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The message pays for its own leading inset only when nothing leads it: the
|
|
155
|
+
// icon's 48dp container already carries one.
|
|
156
|
+
> p:first-child {
|
|
157
|
+
padding-inline-start: var(--md-comp-banners-basic-no-icon-body-text-leading-space);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
> .icon-button {
|
|
161
|
+
flex: 0 0 auto;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
> .actions {
|
|
165
|
+
display: flex;
|
|
166
|
+
flex: 0 0 auto;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: var(--md-comp-banners-basic-actions-between-space);
|
|
169
|
+
padding-inline: var(--md-comp-banners-basic-actions-leading-space)
|
|
170
|
+
var(--md-comp-banners-basic-actions-trailing-space);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Compact: the row is too narrow to hold the actions beside the message, so
|
|
174
|
+
// they take a line of their own under it. Wrapping is switched on here and
|
|
175
|
+
// nowhere else, and `order` puts the actions after the close button, which is
|
|
176
|
+
// what makes them the only thing that can wrap.
|
|
177
|
+
@include bp-down("medium") {
|
|
178
|
+
--md-comp-banners-basic-height: 112px;
|
|
179
|
+
--md-comp-banners-basic-top-space: 8px;
|
|
180
|
+
--md-comp-banners-basic-actions-trailing-space: 4px;
|
|
181
|
+
|
|
182
|
+
flex-wrap: wrap;
|
|
183
|
+
|
|
184
|
+
> .actions {
|
|
185
|
+
order: 1;
|
|
186
|
+
flex-basis: 100%;
|
|
187
|
+
justify-content: flex-end;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// The 112dp mobile height is the two-row arrangement's, so it is owed only
|
|
191
|
+
// to a banner that has a second row. Without actions the row stays 56dp and
|
|
192
|
+
// a one-line message does not float in the middle of an empty half.
|
|
193
|
+
&:not(:has(> .actions)) {
|
|
194
|
+
--md-comp-banners-basic-height: 56px;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Square corners, which M3 uses for a banner sitting flush under an app bar.
|
|
200
|
+
// The message's leading inset shrinks with the corner it is clearing.
|
|
201
|
+
.banner:not(.rich).square {
|
|
202
|
+
--md-comp-banners-basic-shape: 0;
|
|
203
|
+
--md-comp-banners-basic-no-icon-body-text-leading-space: 12px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Rich: a leading element beside a heading, a message and an action row. Named
|
|
207
|
+
// areas rather than auto-placement, so the markup can omit any of the four and
|
|
208
|
+
// order the rest however it reads best - a missing area collapses its track.
|
|
209
|
+
.banner.rich {
|
|
210
|
+
--md-comp-banners-rich-shape: 28px;
|
|
211
|
+
--md-comp-banners-rich-leading-space: 12px;
|
|
212
|
+
--md-comp-banners-rich-trailing-space: 12px;
|
|
213
|
+
--md-comp-banners-rich-top-space: 12px;
|
|
214
|
+
--md-comp-banners-rich-bottom-space: 12px;
|
|
215
|
+
--md-comp-banners-rich-icon-size: 24px;
|
|
216
|
+
--md-comp-banners-rich-icon-text-space: 4px;
|
|
217
|
+
--md-comp-banners-rich-image-size: 80px;
|
|
218
|
+
--md-comp-banners-rich-image-text-space: 8px;
|
|
219
|
+
--md-comp-banners-rich-leading-element-leading-space: 4px;
|
|
220
|
+
--md-comp-banners-rich-with-image-leading-space: 20px;
|
|
221
|
+
--md-comp-banners-rich-title-text-top-space: 14px;
|
|
222
|
+
--md-comp-banners-rich-title-text-bottom-space: 14px;
|
|
223
|
+
--md-comp-banners-rich-actions-between-space: 8px;
|
|
224
|
+
--md-comp-banners-rich-actions-top-space: 12px;
|
|
225
|
+
|
|
226
|
+
display: grid;
|
|
227
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
228
|
+
grid-template-areas:
|
|
229
|
+
"lead title close"
|
|
230
|
+
"lead body close"
|
|
231
|
+
"lead actions actions";
|
|
232
|
+
padding-block: var(--md-comp-banners-rich-top-space)
|
|
233
|
+
var(--md-comp-banners-rich-bottom-space);
|
|
234
|
+
padding-inline: var(--md-comp-banners-rich-leading-space)
|
|
235
|
+
var(--md-comp-banners-rich-trailing-space);
|
|
236
|
+
border-radius: var(--md-comp-banners-rich-shape);
|
|
237
|
+
|
|
238
|
+
> #{$icon} {
|
|
239
|
+
grid-area: lead;
|
|
240
|
+
align-self: start;
|
|
241
|
+
margin-inline: var(--md-comp-banners-rich-leading-element-leading-space)
|
|
242
|
+
var(--md-comp-banners-rich-icon-text-space);
|
|
243
|
+
font-size: var(--md-comp-banners-rich-icon-size);
|
|
244
|
+
inline-size: var(--md-comp-banners-rich-icon-size);
|
|
245
|
+
block-size: var(--md-comp-banners-rich-icon-size);
|
|
246
|
+
line-height: var(--md-comp-banners-rich-icon-size);
|
|
247
|
+
color: var(--md-comp-banners-icon-color);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// md.comp.banners.rich.image.shape is corner.none, so the square is square.
|
|
251
|
+
> img {
|
|
252
|
+
grid-area: lead;
|
|
253
|
+
align-self: start;
|
|
254
|
+
// The token is measured from the container edge, and the container has
|
|
255
|
+
// already spent its own leading space, so the margin is the difference.
|
|
256
|
+
margin-inline: calc(
|
|
257
|
+
var(--md-comp-banners-rich-with-image-leading-space) -
|
|
258
|
+
var(--md-comp-banners-rich-leading-space)
|
|
259
|
+
)
|
|
260
|
+
var(--md-comp-banners-rich-image-text-space);
|
|
261
|
+
inline-size: var(--md-comp-banners-rich-image-size);
|
|
262
|
+
block-size: var(--md-comp-banners-rich-image-size);
|
|
263
|
+
border-radius: 0;
|
|
264
|
+
object-fit: cover;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
> :is(h1, h2, h3, h4, h5, h6) {
|
|
268
|
+
grid-area: title;
|
|
269
|
+
margin: 0;
|
|
270
|
+
padding-block: var(--md-comp-banners-rich-title-text-top-space)
|
|
271
|
+
var(--md-comp-banners-rich-title-text-bottom-space);
|
|
272
|
+
color: var(--md-comp-banners-title-text-color);
|
|
273
|
+
// md.sys.typescale.emphasized.body-medium: body-medium at weight 500. The
|
|
274
|
+
// emphasized set is not in the token layer, so the weight is written here.
|
|
275
|
+
@include typescale("body-medium");
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
> p {
|
|
280
|
+
grid-area: body;
|
|
281
|
+
margin: 0;
|
|
282
|
+
color: inherit;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
> .actions {
|
|
286
|
+
grid-area: actions;
|
|
287
|
+
display: flex;
|
|
288
|
+
align-items: center;
|
|
289
|
+
justify-content: flex-end;
|
|
290
|
+
gap: var(--md-comp-banners-rich-actions-between-space);
|
|
291
|
+
margin-block-start: var(--md-comp-banners-rich-actions-top-space);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
> .icon-button {
|
|
295
|
+
grid-area: close;
|
|
296
|
+
align-self: start;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
@use "../abstracts" as *;
|
|
2
|
+
|
|
3
|
+
// Bottom app bar. A row of *commands* at the bottom edge, with an optional
|
|
4
|
+
// FAB — the counterpart of the top app bar, not of the navigation bar.
|
|
5
|
+
//
|
|
6
|
+
// It is not `nav.navigation-bar`, and the difference is the whole reason this
|
|
7
|
+
// component exists twice over. A navigation bar holds 3–5 destinations that do
|
|
8
|
+
// not change per screen; a bottom app bar holds the actions of the screen you
|
|
9
|
+
// are already on. So this is not a <nav> and takes no landmark (rule 4), and
|
|
10
|
+
// M3 says never show the two at once. The retired version of this component
|
|
11
|
+
// keyed on `footer:has(> nav:only-child)` — an element, which both claimed the
|
|
12
|
+
// landmark and put the two one markup slip apart. The host is a class now, and
|
|
13
|
+
// `semantics.json` forbids writing it on a <nav>.
|
|
14
|
+
//
|
|
15
|
+
// Anatomy is the HTML. Direct <a> / <button> children with a single icon are
|
|
16
|
+
// the actions; a `.button.extra.circle` child is the FAB and is pushed to the
|
|
17
|
+
// end. `.icon-button` styles itself and opts out, as it does in every host.
|
|
18
|
+
//
|
|
19
|
+
// M3 Expressive's toolbar (`.toolbar.docked`) is the newer answer to the same
|
|
20
|
+
// problem and holds the same commands in a 64dp bar. Both ship; this one is
|
|
21
|
+
// the app-bar-shaped 80dp version the spec still publishes tokens for.
|
|
22
|
+
//
|
|
23
|
+
// Tokens (md.comp.bottom-app-bar.*, DSP 34.0.21 — the container is all of it;
|
|
24
|
+
// the actions are icon buttons and the FAB is a FAB):
|
|
25
|
+
// --md-comp-bottom-app-bar-container-color surface-container
|
|
26
|
+
// --md-comp-bottom-app-bar-container-height 80dp, one height with or
|
|
27
|
+
// without a FAB (with-fab's 72dp
|
|
28
|
+
// is deprecated), content centred
|
|
29
|
+
// --md-comp-bottom-app-bar-container-shape 0
|
|
30
|
+
// --md-comp-bottom-app-bar-icon-color on-surface-variant
|
|
31
|
+
// --md-comp-bottom-app-bar-icon-size 24dp
|
|
32
|
+
// --md-comp-bottom-app-bar-action-size 48dp target
|
|
33
|
+
// --md-comp-bottom-app-bar-leading-space 4dp — 16dp to the glyph, less
|
|
34
|
+
// --md-comp-bottom-app-bar-trailing-space the 12dp the target insets
|
|
35
|
+
// --md-comp-bottom-app-bar-fab-container-color secondary-container, per
|
|
36
|
+
// BottomAppBarDefaults, not the
|
|
37
|
+
// free-standing FAB's primary
|
|
38
|
+
// --md-comp-bottom-app-bar-fab-trailing-space 16dp from the container edge,
|
|
39
|
+
// measured from the edge and not
|
|
40
|
+
// added to the container's own
|
|
41
|
+
// trailing space
|
|
42
|
+
// --md-comp-bottom-app-bar-z 997, the navigation bar's rung
|
|
43
|
+
.bottom-app-bar {
|
|
44
|
+
--md-comp-bottom-app-bar-container-color: var(--md-sys-color-surface-container);
|
|
45
|
+
--md-comp-bottom-app-bar-container-height: 80px;
|
|
46
|
+
--md-comp-bottom-app-bar-container-shape: 0;
|
|
47
|
+
--md-comp-bottom-app-bar-icon-color: var(--md-sys-color-on-surface-variant);
|
|
48
|
+
--md-comp-bottom-app-bar-icon-size: 24px;
|
|
49
|
+
--md-comp-bottom-app-bar-action-size: 48px;
|
|
50
|
+
--md-comp-bottom-app-bar-leading-space: 4px;
|
|
51
|
+
--md-comp-bottom-app-bar-trailing-space: 4px;
|
|
52
|
+
--md-comp-bottom-app-bar-fab-container-color: var(--md-sys-color-secondary-container);
|
|
53
|
+
--md-comp-bottom-app-bar-fab-trailing-space: 16px;
|
|
54
|
+
--md-comp-bottom-app-bar-z: 997;
|
|
55
|
+
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
display: flex;
|
|
58
|
+
// No gap: the actions are 48dp targets and abut, which is Compose's
|
|
59
|
+
// `Arrangement.Start` and puts their glyph centres 48dp apart. The top app
|
|
60
|
+
// bar spaces its actions the same way.
|
|
61
|
+
align-items: center;
|
|
62
|
+
width: 100%;
|
|
63
|
+
min-height: var(--md-comp-bottom-app-bar-container-height);
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding-inline: var(--md-comp-bottom-app-bar-leading-space)
|
|
66
|
+
var(--md-comp-bottom-app-bar-trailing-space);
|
|
67
|
+
border-radius: var(--md-comp-bottom-app-bar-container-shape);
|
|
68
|
+
background-color: var(--md-comp-bottom-app-bar-container-color);
|
|
69
|
+
color: var(--md-sys-color-on-surface);
|
|
70
|
+
@include z-depth("2");
|
|
71
|
+
|
|
72
|
+
// Icon-only actions. Same shape as the top app bar's: a bare control with a
|
|
73
|
+
// single icon becomes a 48dp target. Anything that styles itself — the
|
|
74
|
+
// common button, the icon button, the FAB — is left alone.
|
|
75
|
+
> :is(a, button):not(.button, .icon-button):not(.circle):not(.extra):has(> #{$icon}:only-child) {
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
flex: 0 0 auto;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
width: var(--md-comp-bottom-app-bar-action-size);
|
|
81
|
+
height: var(--md-comp-bottom-app-bar-action-size);
|
|
82
|
+
margin: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
border: 0;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
background: transparent;
|
|
87
|
+
color: var(--md-comp-bottom-app-bar-icon-color);
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
appearance: none;
|
|
90
|
+
font: inherit;
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
@include focus-visible;
|
|
93
|
+
|
|
94
|
+
> #{$icon} {
|
|
95
|
+
display: block;
|
|
96
|
+
width: var(--md-comp-bottom-app-bar-icon-size);
|
|
97
|
+
height: var(--md-comp-bottom-app-bar-icon-size);
|
|
98
|
+
font-size: var(--md-comp-bottom-app-bar-icon-size);
|
|
99
|
+
line-height: var(--md-comp-bottom-app-bar-icon-size);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&:hover {
|
|
103
|
+
background-color: color-mix(in oklab, currentColor state-opacity("hover"), transparent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&:active {
|
|
107
|
+
background-color: color-mix(in oklab, currentColor state-opacity("pressed"), transparent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&:disabled,
|
|
111
|
+
&.disabled {
|
|
112
|
+
pointer-events: none;
|
|
113
|
+
color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
|
|
114
|
+
background: transparent;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The FAB. `margin-inline-start: auto` is the spacer, so the bar needs no
|
|
119
|
+
// filler element between the actions and it. Flat and secondary-container:
|
|
120
|
+
// the bar is already at level 2, and a second shadow inside it reads as a
|
|
121
|
+
// sticker. Specificity beats the FAB's own `:where()`-scoped block.
|
|
122
|
+
> :is(a, button).extra.circle,
|
|
123
|
+
> :is(a, button).large.circle {
|
|
124
|
+
margin-inline-start: auto;
|
|
125
|
+
// 16dp from the container edge. The bar's own trailing space is already
|
|
126
|
+
// spent, so this is the remainder - Compose's `16.dp - HorizontalPadding`.
|
|
127
|
+
margin-inline-end: calc(
|
|
128
|
+
var(--md-comp-bottom-app-bar-fab-trailing-space) -
|
|
129
|
+
var(--md-comp-bottom-app-bar-trailing-space)
|
|
130
|
+
);
|
|
131
|
+
background-color: var(--md-comp-bottom-app-bar-fab-container-color);
|
|
132
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
133
|
+
box-shadow: none;
|
|
134
|
+
|
|
135
|
+
&:hover {
|
|
136
|
+
box-shadow: none;
|
|
137
|
+
background-color: color-mix(
|
|
138
|
+
in oklab,
|
|
139
|
+
var(--md-sys-color-on-secondary-container) state-opacity("hover"),
|
|
140
|
+
var(--md-comp-bottom-app-bar-fab-container-color)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Focus and pressed are separate states with separate opacities, the way
|
|
145
|
+
// `abstracts/_mixins` keeps them. `:focus`, not `:focus-visible`: the FAB's
|
|
146
|
+
// own block in `_buttons.scss` paints a `primary-container` tint on plain
|
|
147
|
+
// `:focus`, and leaving that uncovered would flash the wrong colour family
|
|
148
|
+
// at a mouse click. The ring is `focus-visible`'s job and the base rule
|
|
149
|
+
// already carries it.
|
|
150
|
+
&:focus {
|
|
151
|
+
background-color: color-mix(
|
|
152
|
+
in oklab,
|
|
153
|
+
var(--md-sys-color-on-secondary-container) state-opacity("focus"),
|
|
154
|
+
var(--md-comp-bottom-app-bar-fab-container-color)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&:active {
|
|
159
|
+
background-color: color-mix(
|
|
160
|
+
in oklab,
|
|
161
|
+
var(--md-sys-color-on-secondary-container) state-opacity("pressed"),
|
|
162
|
+
var(--md-comp-bottom-app-bar-fab-container-color)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Pinned to the bottom edge. Same z rung as nav.navigation-bar.fixed: the two
|
|
169
|
+
// are alternatives, never neighbours, so neither needs to sit above the other.
|
|
170
|
+
.bottom-app-bar.fixed {
|
|
171
|
+
position: fixed;
|
|
172
|
+
inset: auto 0 0 0;
|
|
173
|
+
z-index: var(--md-comp-bottom-app-bar-z);
|
|
174
|
+
// The bar grows by the inset rather than spending its own height on it.
|
|
175
|
+
// Under `border-box` the padding would otherwise come out of the 80dp the
|
|
176
|
+
// token block and the docs both promise, and the actions would sit in
|
|
177
|
+
// whatever was left.
|
|
178
|
+
min-height: calc(var(--md-comp-bottom-app-bar-container-height) + env(safe-area-inset-bottom, 0px));
|
|
179
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
180
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Tokens follow https://m3.material.io/components/bottom-sheets
|
|
12
12
|
// container surface-container-low, 28dp top corners, elevation 1
|
|
13
|
-
// max width 640dp; 56dp inset from the
|
|
13
|
+
// max width 640dp; 56dp inset from the Medium breakpoint
|
|
14
14
|
// max height viewport minus 72dp
|
|
15
15
|
// handle 32×4dp bar in a 48dp hit target, on-surface-variant
|
|
16
16
|
// scrim modal only (UA ::backdrop + the dialog scrim token)
|
|
@@ -60,7 +60,7 @@ dialog.bottom-sheet {
|
|
|
60
60
|
z-index: 1100;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
@include bp-up("
|
|
63
|
+
@include bp-up("medium") {
|
|
64
64
|
width: min(
|
|
65
65
|
var(--md-comp-bottom-sheet-container-max-width),
|
|
66
66
|
calc(100% - 2 * var(--md-comp-bottom-sheet-margin-inline))
|
|
@@ -90,12 +90,12 @@ dialog.bottom-sheet {
|
|
|
90
90
|
pointer-events: none;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
&:has(> .handle)::before {
|
|
93
|
+
&:has(> :is(.handle, .drag-handle))::before {
|
|
94
94
|
content: none;
|
|
95
95
|
display: none;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
> .handle {
|
|
98
|
+
> :is(.handle, .drag-handle) {
|
|
99
99
|
box-sizing: border-box;
|
|
100
100
|
flex: 0 0 48px;
|
|
101
101
|
align-self: center;
|
|
@@ -111,6 +111,14 @@ dialog.bottom-sheet {
|
|
|
111
111
|
appearance: none;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// The sheet paints its own bar with a mask on the element, so the vertical
|
|
115
|
+
// bar _drag-handle.scss draws in ::before would print across it. This is the
|
|
116
|
+
// whole cost of one class serving both hosts, and it is cheaper than a
|
|
117
|
+
// `horizontal` modifier whose values M3 does not publish.
|
|
118
|
+
> .drag-handle::before {
|
|
119
|
+
content: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
114
122
|
> :is(h1, h2, h3, h4, h5, h6) {
|
|
115
123
|
padding-top: 8px;
|
|
116
124
|
}
|