@expressivecss/expressive 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +1313 -1
  2. package/README.md +76 -30
  3. package/dist/css/expressive.css +4896 -1777
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/LICENSE-material-symbols +93 -0
  8. package/dist/fonts/LICENSE-noto-sans +93 -0
  9. package/dist/fonts/LICENSE-roboto +93 -0
  10. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  11. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  12. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  13. package/dist/fonts/noto-sans-latin-400.woff2 +0 -0
  14. package/dist/fonts/noto-sans-latin-500.woff2 +0 -0
  15. package/dist/fonts/roboto-latin-400.woff2 +0 -0
  16. package/dist/fonts/roboto-latin-500.woff2 +0 -0
  17. package/dist/js/expressive.cjs +1673 -1338
  18. package/dist/js/expressive.cjs.map +4 -4
  19. package/dist/js/expressive.js +1673 -1338
  20. package/dist/js/expressive.js.map +4 -4
  21. package/dist/js/expressive.min.js +13 -13
  22. package/dist/js/expressive.min.js.map +4 -4
  23. package/dist/js/expressive.mjs +1673 -1338
  24. package/dist/js/expressive.mjs.map +4 -4
  25. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  26. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  27. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  28. package/dist/types/components/appBar.d.ts +26 -0
  29. package/dist/types/components/autocomplete.d.ts +15 -3
  30. package/dist/types/components/buttonGroup.d.ts +39 -0
  31. package/dist/types/components/buttons.d.ts +5 -1
  32. package/dist/types/components/cards.d.ts +11 -13
  33. package/dist/types/components/carousel.d.ts +87 -53
  34. package/dist/types/components/chips.d.ts +21 -1
  35. package/dist/types/components/expandingCard.d.ts +31 -0
  36. package/dist/types/components/index.d.ts +23 -20
  37. package/dist/types/components/lightbox.d.ts +1 -0
  38. package/dist/types/components/menu.d.ts +11 -1
  39. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  40. package/dist/types/components/registry.d.ts +18 -8
  41. package/dist/types/components/slider.d.ts +26 -85
  42. package/dist/types/components/snackbar.d.ts +20 -5
  43. package/dist/types/components/tabs.d.ts +12 -0
  44. package/dist/types/components/timepicker.d.ts +12 -10
  45. package/dist/types/components/tooltip.d.ts +1 -0
  46. package/dist/types/core/utils.d.ts +64 -53
  47. package/dist/types/index.d.ts +8 -9
  48. package/package.json +39 -3
  49. package/src/sass/README.md +61 -11
  50. package/src/sass/abstracts/_breakpoints.scss +17 -19
  51. package/src/sass/abstracts/_mixins.scss +155 -21
  52. package/src/sass/abstracts/_variables.scss +69 -5
  53. package/src/sass/base/_fonts.scss +63 -0
  54. package/src/sass/base/_global.scss +6 -24
  55. package/src/sass/base/_grid.scss +15 -12
  56. package/src/sass/base/_normalize.scss +34 -38
  57. package/src/sass/base/_typography.scss +4 -18
  58. package/src/sass/components/_badges.scss +3 -5
  59. package/src/sass/components/_banners.scss +298 -0
  60. package/src/sass/components/_bottom-app-bar.scss +180 -0
  61. package/src/sass/components/_bottom-sheet.scss +12 -4
  62. package/src/sass/components/_button-groups.scss +358 -0
  63. package/src/sass/components/_buttons.scss +245 -92
  64. package/src/sass/components/_cards.scss +514 -78
  65. package/src/sass/components/_carousel.scss +562 -68
  66. package/src/sass/components/_chips.scss +64 -5
  67. package/src/sass/components/_datepicker.scss +24 -24
  68. package/src/sass/components/_dialog.scss +17 -11
  69. package/src/sass/components/_drag-handle.scss +130 -0
  70. package/src/sass/components/_expanding-card.scss +226 -0
  71. package/src/sass/components/_fab-menu.scss +232 -0
  72. package/src/sass/components/_floating-sheet.scss +86 -0
  73. package/src/sass/components/_icon-buttons.scss +172 -0
  74. package/src/sass/components/_icons-material-design.scss +22 -10
  75. package/src/sass/components/_index.scss +22 -6
  76. package/src/sass/components/_list.scss +16 -13
  77. package/src/sass/components/_loading-indicator.scss +106 -0
  78. package/src/sass/components/_menu.scss +335 -61
  79. package/src/sass/components/_navbar.scss +245 -45
  80. package/src/sass/components/_navigation-bar.scss +22 -11
  81. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  82. package/src/sass/components/_navigation-rail.scss +31 -23
  83. package/src/sass/components/_page-footer.scss +19 -16
  84. package/src/sass/components/_pagination.scss +24 -6
  85. package/src/sass/components/_panes.scss +52 -11
  86. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  87. package/src/sass/components/_scrim.scss +37 -0
  88. package/src/sass/components/_search.scss +259 -0
  89. package/src/sass/components/_segmented-buttons.scss +188 -0
  90. package/src/sass/components/_side-sheet.scss +2 -2
  91. package/src/sass/components/_snackbar.scss +20 -15
  92. package/src/sass/components/_split-button.scss +196 -0
  93. package/src/sass/components/_tabs.scss +7 -8
  94. package/src/sass/components/_timepicker.scss +67 -58
  95. package/src/sass/components/_toolbar.scss +206 -80
  96. package/src/sass/components/_transitions.scss +8 -1
  97. package/src/sass/components/forms/_checkboxes.scss +49 -12
  98. package/src/sass/components/forms/_fieldset.scss +4 -4
  99. package/src/sass/components/forms/_file-input.scss +8 -1
  100. package/src/sass/components/forms/_index.scss +1 -1
  101. package/src/sass/components/forms/_input-fields.scss +50 -44
  102. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  103. package/src/sass/components/forms/_select.scss +38 -1
  104. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  105. package/src/sass/components/forms/_switches.scss +6 -7
  106. package/src/sass/expressive.scss +5 -0
  107. package/src/sass/tokens/_index.scss +4 -1
  108. package/src/sass/tokens/_reference.scss +70 -61
  109. package/src/sass/tokens/_state.scss +24 -0
  110. package/src/sass/tokens/_theme.scss +6 -3
  111. package/src/sass/tokens/_vibrant.scss +76 -0
  112. package/src/sass/utilities/_colors.scss +14 -1
  113. package/src/sass/utilities/_helpers.scss +5 -5
  114. package/src/sass/utilities/_visibility.scss +48 -26
  115. package/dist/types/behaviors/waves.d.ts +0 -15
  116. package/dist/types/components/parallax.d.ts +0 -16
  117. package/dist/types/components/range.d.ts +0 -43
  118. package/dist/types/core/bounding.d.ts +0 -6
  119. package/dist/types/core/edges.d.ts +0 -6
  120. package/src/sass/components/_parallax.scss +0 -104
  121. package/src/sass/components/_pulse.scss +0 -37
  122. package/src/sass/components/_slider.scss +0 -129
@@ -1,15 +1,16 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
- // 12-column CSS Grid. Prefixes map onto $breakpoints; s is the mobile
4
- // default (no query). xxl is M3 extra-large — the class that actually
5
- // sees a 4K or ultrawide window, since xlarge starts at 1201px.
3
+ // 12-column CSS Grid. The historical prefixes remain public markup, but each
4
+ // now maps exactly to an M3 window size class: s = Compact, m = Medium,
5
+ // l = Expanded, xl = Large, xxl = Extra-large. Compact is the default and
6
+ // therefore needs no media query.
6
7
  $grid-columns: 12;
7
8
  $grid-prefixes: (
8
9
  "s": null,
9
- "m": "small",
10
- "l": "large",
11
- "xl": "xlarge",
12
- "xxl": "xxlarge",
10
+ "m": "medium",
11
+ "l": "expanded",
12
+ "xl": "large",
13
+ "xxl": "extra-large",
13
14
  );
14
15
 
15
16
  @mixin grid-spans($prefix) {
@@ -33,13 +34,13 @@ $grid-prefixes: (
33
34
  max-width: var(--md-comp-container-max-width);
34
35
  }
35
36
 
36
- @include bp-up("small") {
37
+ @include bp-up("medium") {
37
38
  .container {
38
39
  --md-comp-container-width: 85%;
39
40
  }
40
41
  }
41
42
 
42
- @include bp-up("large") {
43
+ @include bp-up("expanded") {
43
44
  .container {
44
45
  --md-comp-container-width: 70%;
45
46
  }
@@ -49,7 +50,7 @@ $grid-prefixes: (
49
50
  // max-width snapped it back to 1280. Extra-large lifts the cap to 1920
50
51
  // (binds once 75% of the viewport exceeds it, around 2560) and opens
51
52
  // the measure a little so QHD lands on that cap instead of 1792.
52
- @include bp-up("xxlarge") {
53
+ @include bp-up("extra-large") {
53
54
  .container {
54
55
  --md-comp-container-width: 75%;
55
56
  --md-comp-container-max-width: 1920px;
@@ -70,10 +71,12 @@ $grid-prefixes: (
70
71
  padding: 1rem 0;
71
72
  }
72
73
 
73
- body {
74
+ // `:host` because a shadow tree has no `body` to hang this on, and `.row` reads
75
+ // it without a fallback - see adr/0002-shadow-only-stylesheet-adoption.md.
76
+ body, :host {
74
77
  --gap-size: #{$gutter-width};
75
78
 
76
- @include bp-up("xxlarge") {
79
+ @include bp-up("extra-large") {
77
80
  --gap-size: 2rem;
78
81
  }
79
82
  }
@@ -26,7 +26,36 @@
26
26
  //
27
27
  // Also simplified: `font-family: monospace, monospace` was a workaround for the
28
28
  // generic-family font-size quirk, which the adjacent `font-size: 1em` already
29
- // handles; and abbr's doubled text-decoration dropped its non-dotted fallback.
29
+ // handles.
30
+ //
31
+ // A second pass removed four more whose declarations both baseline engines now
32
+ // supply themselves, verbatim:
33
+ // abbr[title] { text-decoration: underline dotted } - already the UA value
34
+ // button, select { text-transform: none } - already `none` in both
35
+ // fieldset { padding: .35em .75em .625em } - restates the UA padding
36
+ // summary { display: list-item } - already list-item
37
+ //
38
+ // The rest of this file was measured against a bare page in both engines rather
39
+ // than assumed, and these are the results that keep a rule here. Do not remove
40
+ // them again without re-measuring - each one *looks* redundant and is not:
41
+ // b, strong { font-weight: bolder } UA is `bold` (700). `bolder` is relative,
42
+ // so it reaches 900 inside an already-bold
43
+ // parent where the UA value would stay 700.
44
+ // small { font-size: 80% } UA is `smaller`, which computes to
45
+ // 13.333px against a 16px parent, not 12.8.
46
+ // progress { vertical-align: baseline } UA is -3.2px, not baseline.
47
+ // legend { padding: 0 } UA has padding-left: 2px.
48
+ // [type="search"] { ... } UA is appearance:auto / outline-offset:0.
49
+ // sub, sup { ... } UA uses vertical-align: sub/super and no
50
+ // line-height reset; normalize's version is
51
+ // deliberately a different mechanism.
52
+ // code, kbd, samp, pre { font-size: 1em } UA applies the monospace font-size
53
+ // quirk (13px Chromium, 12px Firefox).
54
+ // hr { box-sizing: content-box } matches the UA, but base/_global.scss sets
55
+ // border-box globally and would override it.
56
+ // -webkit-appearance: button on the [type] selectors only - a Safari
57
+ // correction, and this file's policy is to
58
+ // keep those. `button` itself was dropped.
30
59
 
31
60
  /* Document
32
61
  ========================================================================== */
@@ -87,14 +116,6 @@ pre {
87
116
  /* Text-level semantics
88
117
  ========================================================================== */
89
118
 
90
- /**
91
- * Add the correct text decoration in Chrome, Edge, Opera, and Safari.
92
- */
93
-
94
- abbr[title] {
95
- text-decoration: underline dotted;
96
- }
97
-
98
119
  /**
99
120
  * Add the correct font weight in Chrome, Edge, and Safari.
100
121
  */
@@ -163,21 +184,15 @@ textarea {
163
184
  margin: 0; /* 2 */
164
185
  }
165
186
 
166
- /**
167
- * Remove the inheritance of text transform in Firefox.
168
- */
169
-
170
- button,
171
- select {
172
- text-transform: none;
173
- }
174
-
175
187
  /**
176
188
  * Correct the inability to style clickable types in iOS and Safari.
177
189
  * Kept prefixed: this is a WebKit correction, and Safari is what needs it.
190
+ *
191
+ * `button` is no longer in this list. The clickable *types* are what Safari
192
+ * renders unstyleable; a <button> element already renders as a button there,
193
+ * and normalize carried it out of caution.
178
194
  */
179
195
 
180
- button,
181
196
  [type="button"],
182
197
  [type="reset"],
183
198
  [type="submit"] {
@@ -193,14 +208,6 @@ legend {
193
208
  padding: 0;
194
209
  }
195
210
 
196
- /**
197
- * Correct the padding in Firefox.
198
- */
199
-
200
- fieldset {
201
- padding: 0.35em 0.75em 0.625em;
202
- }
203
-
204
211
  /**
205
212
  * Add the correct vertical alignment in Chrome, Firefox, and Opera.
206
213
  */
@@ -248,14 +255,3 @@ progress {
248
255
  -webkit-appearance: button; /* 1 */
249
256
  font: inherit; /* 2 */
250
257
  }
251
-
252
- /* Interactive
253
- ========================================================================== */
254
-
255
- /*
256
- * Add the correct display in all browsers.
257
- */
258
-
259
- summary {
260
- display: list-item;
261
- }
@@ -8,7 +8,7 @@
8
8
  // h1 display-small h2 headline-large h3 headline-medium
9
9
  // h4 headline-small h5 title-large h6 title-medium
10
10
  // p body-large small / figcaption body-small
11
- // body body-medium (matches the 14px root)
11
+ // body body-medium
12
12
  //
13
13
  // Display-large / display-medium stay as utility classes — they are too
14
14
  // large for a default <h1>. Component slots (app bar, card, dialog) set
@@ -18,23 +18,10 @@ html {
18
18
  font-family: $font-stack;
19
19
  font-weight: var(--md-sys-typescale-body-medium-font-weight);
20
20
  color: $text-color;
21
- font-size: $root-font-size;
22
-
23
- // Deliberately still a two-step ladder rather than a fluid clamp(): the root
24
- // font size is the basis of every rem in the sheet, so making it continuous
25
- // would move every computed length at every viewport width.
26
- @include bp-up("large") {
27
- font-size: 14.5px;
28
- }
29
-
30
- @include bp-up("xlarge") {
31
- font-size: 15px;
32
- }
33
21
  }
34
22
 
35
23
  body {
36
24
  @include typescale("body-medium");
37
- font-family: $font-stack;
38
25
  }
39
26
 
40
27
  h1 { @include typescale("display-small"); }
@@ -59,14 +46,12 @@ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
59
46
 
60
47
  p {
61
48
  @include typescale("body-large");
62
- font-family: $font-stack;
63
49
  margin: 0 0 1em;
64
50
  }
65
51
 
66
52
  small,
67
53
  figcaption {
68
54
  @include typescale("body-small");
69
- font-family: $font-stack;
70
55
  }
71
56
 
72
57
  em { font-style: italic; }
@@ -78,8 +63,9 @@ strong, b { font-weight: 500; }
78
63
  // 30px of viewport width from 360px to 960px. clamp() expresses the same line
79
64
  // as one declaration - and continuously, so the type no longer jumps.
80
65
  //
81
- // The whole fluid range sits below the "large" breakpoint, so 1rem is
82
- // $root-font-size throughout it and the conversion below is exact.
66
+ // M3 uses the browser's 16px default as the rem conversion basis. Do not set
67
+ // the root size here: leaving it to the browser preserves the user's preferred
68
+ // text size while the default computation remains exact.
83
69
  $_flow-min-px: math.div($flow-text-min, 1rem) * $root-font-size;
84
70
  $_flow-max-px: math.div($flow-text-max, 1rem) * $root-font-size;
85
71
  $_flow-slope: math.div($_flow-max-px - $_flow-min-px, $flow-text-to - $flow-text-from);
@@ -38,7 +38,6 @@
38
38
  white-space: nowrap;
39
39
  @include typescale("label-small");
40
40
  line-height: var(--md-comp-badge-large-size);
41
- font-family: $font-stack;
42
41
 
43
42
  // Small. No label — a 6dp dot.
44
43
  &:empty {
@@ -74,8 +73,7 @@
74
73
  inset-inline-end: calc((100% - var(--md-comp-badge-icon-size)) / 2);
75
74
  transform: translate(50%, -50%);
76
75
 
77
- &:dir(rtl),
78
- [dir="rtl"] & {
76
+ &:dir(rtl) {
79
77
  transform: translate(-50%, -50%);
80
78
  }
81
79
  }
@@ -83,7 +81,7 @@
83
81
 
84
82
  // Trailing count in a list or drawer row stays in flow.
85
83
  .list .badge,
86
- .sidenav .badge,
84
+ :is(.sidenav, .navigation-drawer) .badge,
87
85
  menu .badge {
88
86
  position: static;
89
87
  transform: none;
@@ -93,6 +91,6 @@ menu .badge {
93
91
  margin-inline-start: auto;
94
92
  }
95
93
 
96
- .sidenav summary .badge {
94
+ :is(.sidenav, .navigation-drawer) summary .badge {
97
95
  margin-inline-start: 14px;
98
96
  }
@@ -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
+ }