@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +1266 -1
  2. package/README.md +14 -11
  3. package/dist/css/expressive.css +4215 -1668
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/js/expressive.cjs +1231 -1253
  8. package/dist/js/expressive.cjs.map +4 -4
  9. package/dist/js/expressive.js +1231 -1253
  10. package/dist/js/expressive.js.map +4 -4
  11. package/dist/js/expressive.min.js +12 -12
  12. package/dist/js/expressive.min.js.map +4 -4
  13. package/dist/js/expressive.mjs +1231 -1253
  14. package/dist/js/expressive.mjs.map +4 -4
  15. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  16. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  17. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  18. package/dist/types/components/autocomplete.d.ts +15 -3
  19. package/dist/types/components/buttons.d.ts +5 -1
  20. package/dist/types/components/carousel.d.ts +87 -53
  21. package/dist/types/components/chips.d.ts +21 -1
  22. package/dist/types/components/expandingCard.d.ts +31 -0
  23. package/dist/types/components/index.d.ts +21 -20
  24. package/dist/types/components/lightbox.d.ts +1 -0
  25. package/dist/types/components/menu.d.ts +7 -1
  26. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  27. package/dist/types/components/registry.d.ts +8 -8
  28. package/dist/types/components/slider.d.ts +26 -85
  29. package/dist/types/components/snackbar.d.ts +20 -5
  30. package/dist/types/components/tabs.d.ts +12 -0
  31. package/dist/types/components/timepicker.d.ts +12 -9
  32. package/dist/types/core/utils.d.ts +64 -53
  33. package/dist/types/index.d.ts +8 -9
  34. package/package.json +15 -1
  35. package/src/sass/README.md +61 -11
  36. package/src/sass/abstracts/_breakpoints.scss +17 -19
  37. package/src/sass/abstracts/_mixins.scss +103 -21
  38. package/src/sass/abstracts/_variables.scss +50 -5
  39. package/src/sass/base/_global.scss +6 -24
  40. package/src/sass/base/_grid.scss +15 -12
  41. package/src/sass/base/_normalize.scss +34 -38
  42. package/src/sass/base/_typography.scss +4 -18
  43. package/src/sass/components/_badges.scss +2 -3
  44. package/src/sass/components/_banners.scss +298 -0
  45. package/src/sass/components/_bottom-app-bar.scss +180 -0
  46. package/src/sass/components/_bottom-sheet.scss +12 -4
  47. package/src/sass/components/_button-groups.scss +186 -0
  48. package/src/sass/components/_buttons.scss +245 -92
  49. package/src/sass/components/_cards.scss +494 -73
  50. package/src/sass/components/_carousel.scss +562 -68
  51. package/src/sass/components/_chips.scss +64 -5
  52. package/src/sass/components/_datepicker.scss +24 -24
  53. package/src/sass/components/_dialog.scss +17 -11
  54. package/src/sass/components/_drag-handle.scss +130 -0
  55. package/src/sass/components/_expanding-card.scss +260 -0
  56. package/src/sass/components/_fab-menu.scss +232 -0
  57. package/src/sass/components/_floating-sheet.scss +86 -0
  58. package/src/sass/components/_icon-buttons.scss +194 -0
  59. package/src/sass/components/_icons-material-design.scss +16 -4
  60. package/src/sass/components/_index.scss +22 -6
  61. package/src/sass/components/_list.scss +16 -13
  62. package/src/sass/components/_loading-indicator.scss +106 -0
  63. package/src/sass/components/_menu.scss +335 -61
  64. package/src/sass/components/_navbar.scss +71 -24
  65. package/src/sass/components/_navigation-bar.scss +13 -10
  66. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  67. package/src/sass/components/_navigation-rail.scss +22 -22
  68. package/src/sass/components/_page-footer.scss +19 -16
  69. package/src/sass/components/_pagination.scss +24 -6
  70. package/src/sass/components/_panes.scss +52 -11
  71. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  72. package/src/sass/components/_scrim.scss +37 -0
  73. package/src/sass/components/_search.scss +259 -0
  74. package/src/sass/components/_segmented-buttons.scss +188 -0
  75. package/src/sass/components/_side-sheet.scss +2 -2
  76. package/src/sass/components/_snackbar.scss +20 -15
  77. package/src/sass/components/_split-button.scss +196 -0
  78. package/src/sass/components/_tabs.scss +7 -8
  79. package/src/sass/components/_timepicker.scss +67 -58
  80. package/src/sass/components/_toolbar.scss +206 -80
  81. package/src/sass/components/forms/_checkboxes.scss +49 -12
  82. package/src/sass/components/forms/_fieldset.scss +4 -4
  83. package/src/sass/components/forms/_file-input.scss +8 -1
  84. package/src/sass/components/forms/_index.scss +1 -1
  85. package/src/sass/components/forms/_input-fields.scss +50 -44
  86. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  87. package/src/sass/components/forms/_select.scss +0 -1
  88. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  89. package/src/sass/components/forms/_switches.scss +6 -7
  90. package/src/sass/tokens/_index.scss +4 -1
  91. package/src/sass/tokens/_reference.scss +70 -61
  92. package/src/sass/tokens/_state.scss +24 -0
  93. package/src/sass/tokens/_theme.scss +6 -3
  94. package/src/sass/tokens/_vibrant.scss +76 -0
  95. package/src/sass/utilities/_colors.scss +14 -1
  96. package/src/sass/utilities/_helpers.scss +5 -5
  97. package/src/sass/utilities/_visibility.scss +48 -26
  98. package/dist/types/behaviors/waves.d.ts +0 -15
  99. package/dist/types/components/parallax.d.ts +0 -16
  100. package/dist/types/components/range.d.ts +0 -43
  101. package/dist/types/core/bounding.d.ts +0 -6
  102. package/dist/types/core/edges.d.ts +0 -6
  103. package/src/sass/components/_parallax.scss +0 -104
  104. package/src/sass/components/_pulse.scss +0 -37
  105. package/src/sass/components/_slider.scss +0 -129
@@ -12,8 +12,15 @@
12
12
  // (https://m3.material.io/components/app-bars/specs):
13
13
  // container surface, 64dp small / 112 medium / 152 large
14
14
  // headline on-surface, title-large (small), headline-small/medium
15
- // icons on-surface-variant, 24dp in a 48dp target, 4dp inset
16
- // on scroll surface-container fill (not elevation) so the bar
15
+ // icons 24dp in a 48dp target, 4dp inset. The spec mutes the
16
+ // trailing ones (on-surface-variant) and leaves the leading
17
+ // one on-surface; both inherit here instead, so a fill +
18
+ // on-* pair on the header stays readable. The two tokens
19
+ // below are the opt-in to the spec colors.
20
+ // medium/large headline 16dp from the inline edges, 20dp (medium) /
21
+ // 28dp (large) above the container bottom
22
+ // on scroll surface-container fill (not elevation - the spec's
23
+ // level2 elevation carries a transparent shadow) so the bar
17
24
  // separates from the body. A color utility on the
18
25
  // header still wins — it sets background-color itself.
19
26
 
@@ -29,12 +36,25 @@
29
36
  initial-value: 0;
30
37
  }
31
38
 
32
- header:has(> nav:not(.navigation-bar, .navigation-rail)) {
39
+ // The bar itself. A top app bar that holds destinations is a <nav>; one that
40
+ // holds only a title and controls - as the docs chrome does - is not
41
+ // navigation and says so with `.bar`, rather than claiming a landmark and
42
+ // then having nothing to put in it. Both look identical.
43
+ // Wrapped in :is() so it stays ONE compound selector. A bare comma-separated
44
+ // list breaks out of its context on interpolation: `header.medium > #{$_bar}`
45
+ // expanded to `header.medium > nav:not(...), .bar`, which styled every .bar on
46
+ // the page as an app bar. Same trap as $_icon in _icons-material-design.
47
+ // :is() also keeps the specificity where nav:not(...) had it.
48
+ $_bar: ":is(nav:not(.tabs, .navigation-bar, .navigation-rail), .bar)";
49
+
50
+ header:has(> :is(nav:not(.navigation-bar, .navigation-rail), .bar)) {
33
51
  --md-comp-top-app-bar-container-color: var(--md-sys-color-surface);
34
52
  --md-comp-top-app-bar-scrolled-container-color: var(--md-sys-color-surface-container);
35
53
  --md-comp-top-app-bar-headline-color: var(--md-sys-color-on-surface);
36
- // Inherit so a fill + on-* pair on the header recolors the icons.
37
- // Set this to --md-sys-color-on-surface-variant for the spec mute.
54
+ // Inherit so a fill + on-* pair on the header recolors the icons. The spec
55
+ // splits them - leading on-surface, trailing on-surface-variant - and each
56
+ // token below is the opt-in for its own side. Leading is everything before
57
+ // the headline; trailing is everything after it.
38
58
  --md-comp-top-app-bar-leading-icon-color: inherit;
39
59
  --md-comp-top-app-bar-trailing-icon-color: inherit;
40
60
  --md-comp-top-app-bar-container-height: 64px;
@@ -51,7 +71,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
51
71
  border-radius: var(--md-comp-top-app-bar-container-shape);
52
72
  width: 100%;
53
73
 
54
- > nav:not(.tabs, .navigation-bar, .navigation-rail) {
74
+ > #{$_bar} {
55
75
  display: flex;
56
76
  align-items: center;
57
77
  box-sizing: border-box;
@@ -61,7 +81,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
61
81
  margin: 0;
62
82
  }
63
83
 
64
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(h1, h2, h3, h4, h5, h6) {
84
+ > #{$_bar} > :is(h1, h2, h3, h4, h5, h6) {
65
85
  flex: 1 1 auto;
66
86
  min-width: 0;
67
87
  margin: 0;
@@ -83,7 +103,11 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
83
103
  }
84
104
 
85
105
  // Icon-only actions — 48dp target, 24dp glyph, 4dp from the container edge.
86
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
106
+ // $icon, not `i`: the canonical icon is a <span class="material-symbols">, and
107
+ // keying on the element meant every bar written the documented way lost this
108
+ // rule — the leading action fell through to the generic button pill and the
109
+ // trailing ones were picked up by the text-destination rule below.
110
+ > #{$_bar} > :is(a, button):not(.button, .icon-button):not(.circle):not(.extra):has(> #{$icon}:only-child) {
87
111
  display: inline-flex;
88
112
  flex: 0 0 auto;
89
113
  align-items: center;
@@ -105,7 +129,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
105
129
  text-decoration: none;
106
130
  @include focus-visible;
107
131
 
108
- > i {
132
+ > #{$icon} {
109
133
  display: block;
110
134
  font-size: var(--md-comp-top-app-bar-leading-icon-size);
111
135
  width: var(--md-comp-top-app-bar-leading-icon-size);
@@ -122,12 +146,20 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
122
146
  }
123
147
  }
124
148
 
149
+ // Trailing actions are the ones that follow the headline - DOM order is the
150
+ // anatomy, here as everywhere else in this file. Without this the trailing
151
+ // token was declared and consumed by nothing, so the documented way to opt
152
+ // into the spec's muted trailing icons did nothing at all.
153
+ > #{$_bar} > :is(h1, h2, h3, h4, h5, h6) ~ :is(a, button):not(.button, .icon-button):not(.circle):not(.extra):has(> #{$icon}:only-child) {
154
+ color: var(--md-comp-top-app-bar-trailing-icon-color);
155
+ }
156
+
125
157
  // Text destinations, either loose or grouped in a menu/list.
126
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > a:not(:has(> i:only-child)):not(.button):not(.circle):not(.breadcrumb) {
158
+ > #{$_bar} > a:not(:has(> #{$icon}:only-child)):not(.button, .icon-button):not(.circle):not(.breadcrumb) {
127
159
  @extend %app-bar-destination;
128
160
  }
129
161
 
130
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(menu, ul) {
162
+ > #{$_bar} > :is(menu, ul) {
131
163
  display: flex;
132
164
  align-items: stretch;
133
165
  align-self: stretch;
@@ -151,7 +183,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
151
183
  // Breadcrumb trail. Layout only — colors stay on the breadcrumb tokens
152
184
  // (which this header remaps to inherit). Do not treat the crumbs as
153
185
  // destinations.
154
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > ol {
186
+ > #{$_bar} > ol {
155
187
  display: flex;
156
188
  flex-wrap: wrap;
157
189
  align-items: center;
@@ -163,7 +195,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
163
195
  padding-inline: 12px;
164
196
  }
165
197
 
166
- > nav:not(.tabs, .navigation-bar, .navigation-rail) > form {
198
+ > #{$_bar} > form {
167
199
  display: flex;
168
200
  flex: 1 1 auto;
169
201
  align-items: center;
@@ -220,7 +252,7 @@ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
220
252
 
221
253
  // Center-aligned small bar. The headline is taken out of flow so leading and
222
254
  // trailing actions can sit on the edges without shifting the title.
223
- header.center:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
255
+ header.center:has(> #{$_bar}) > #{$_bar} {
224
256
  position: relative;
225
257
  justify-content: space-between;
226
258
 
@@ -241,17 +273,32 @@ header.center:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
241
273
  // so leading/trailing icons would otherwise pack together at the start.
242
274
  // `space-between` parks the icon row at the top and the title at the
243
275
  // bottom — that's what makes the 112/152 height readable.
244
- header.medium:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail),
245
- header.large:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
276
+ header.medium:has(> #{$_bar}) > #{$_bar},
277
+ header.large:has(> #{$_bar}) > #{$_bar} {
246
278
  flex-wrap: wrap;
247
279
  align-content: space-between;
248
280
  align-items: flex-start;
249
281
  height: auto;
250
- padding-top: 4px;
282
+ // 8dp so the 48dp icon targets are centred in the 64dp top row, the
283
+ // same row the bar collapses to.
284
+ padding-top: 8px;
285
+
286
+ // The top row is 64dp whether or not it holds icons. Without this
287
+ // zero-width spacer a title-only bar has a single flex line and
288
+ // `space-between` pins it to the *top*, so the headline sits where the
289
+ // icons belong instead of on the bottom row.
290
+ &::before {
291
+ content: "";
292
+ flex: 0 0 0;
293
+ height: 48px;
294
+ }
251
295
 
252
296
  > :is(h1, h2, h3, h4, h5, h6) {
253
297
  order: 1;
254
- flex: 1 0 100%;
298
+ // Wider than the line, so it always breaks onto its own one - a basis of
299
+ // exactly 100% still fits beside the zero-width spacer above, which put
300
+ // the headline back on the icon row. It shrinks to fit once there.
301
+ flex: 1 1 calc(100% + 1px);
255
302
  white-space: normal;
256
303
  overflow: visible;
257
304
  text-overflow: unset;
@@ -262,12 +309,12 @@ header.large:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
262
309
  }
263
310
  }
264
311
 
265
- header.medium:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
312
+ header.medium:has(> #{$_bar}) > #{$_bar} {
266
313
  min-height: 112px;
267
- padding-bottom: 16px;
314
+ padding-bottom: 20px;
268
315
 
269
316
  > :is(h1, h2, h3, h4, h5, h6) {
270
- padding: 4px 12px 0 16px;
317
+ padding: 0 12px;
271
318
  font-family: var(--md-sys-typescale-headline-small-font-family-name);
272
319
  font-size: var(--md-sys-typescale-headline-small-font-size);
273
320
  font-weight: var(--md-sys-typescale-headline-small-font-weight);
@@ -276,12 +323,12 @@ header.medium:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
276
323
  }
277
324
  }
278
325
 
279
- header.large:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
326
+ header.large:has(> #{$_bar}) > #{$_bar} {
280
327
  min-height: 152px;
281
328
  padding-bottom: 28px;
282
329
 
283
330
  > :is(h1, h2, h3, h4, h5, h6) {
284
- padding: 12px 12px 0 16px;
331
+ padding: 0 12px;
285
332
  font-family: var(--md-sys-typescale-headline-medium-font-family-name);
286
333
  font-size: var(--md-sys-typescale-headline-medium-font-size);
287
334
  font-weight: var(--md-sys-typescale-headline-medium-font-weight);
@@ -295,7 +342,7 @@ header.large:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
295
342
  // progress; a utility class on the header sets background-color itself
296
343
  // and is not overwritten. Without animation-timeline the bar stays at
297
344
  // rest (surface), the spec default.
298
- header.fixed:has(> nav:not(.navigation-bar, .navigation-rail)) {
345
+ header.fixed:has(> :is(nav:not(.navigation-bar, .navigation-rail), .bar)) {
299
346
  position: sticky;
300
347
  top: 0;
301
348
  z-index: 997;
@@ -15,7 +15,7 @@
15
15
  // --md-comp-nav-bar-container-color
16
16
  // --md-comp-nav-bar-container-height 64px
17
17
  // --md-comp-nav-bar-icon-size 24px
18
- // --md-comp-nav-bar-indicator-width 56px
18
+ // --md-comp-nav-bar-indicator-width 64px
19
19
  // --md-comp-nav-bar-indicator-height 32px
20
20
  // --md-comp-nav-bar-indicator-shape 16px / 100px horizontal
21
21
  // --md-comp-nav-bar-active-indicator-color secondary-container
@@ -27,7 +27,10 @@
27
27
  --md-comp-nav-bar-container-color: var(--md-sys-color-surface-container);
28
28
  --md-comp-nav-bar-container-height: 64px;
29
29
  --md-comp-nav-bar-icon-size: 24px;
30
- --md-comp-nav-bar-indicator-width: 56px;
30
+ // 64dp, not the rail's 56dp: md.comp.navigation-bar.active-indicator.width
31
+ // is 64 while md.comp.navigation-rail.active-indicator.width is 56. Both were
32
+ // 56 here, which is the rail's value copied onto the bar.
33
+ --md-comp-nav-bar-indicator-width: 64px;
31
34
  --md-comp-nav-bar-indicator-height: 32px;
32
35
  --md-comp-nav-bar-indicator-shape: 16px;
33
36
  --md-comp-nav-bar-active-indicator-color: var(--md-sys-color-secondary-container);
@@ -66,7 +69,7 @@
66
69
  @include typescale("label-medium");
67
70
  @include focus-visible;
68
71
 
69
- > i {
72
+ > #{$icon} {
70
73
  display: flex;
71
74
  flex-shrink: 0;
72
75
  align-items: center;
@@ -80,15 +83,15 @@
80
83
  transition: background-color 0.2s ease-out;
81
84
  }
82
85
 
83
- &:hover:not(.active, [aria-current]) > i {
84
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
86
+ &:hover:not(.active, [aria-current]) > #{$icon} {
87
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
85
88
  }
86
89
 
87
90
  &.active,
88
91
  &[aria-current] {
89
92
  color: var(--md-sys-color-on-surface);
90
93
 
91
- > i {
94
+ > #{$icon} {
92
95
  background-color: var(--md-comp-nav-bar-active-indicator-color);
93
96
  color: var(--md-comp-nav-bar-active-color);
94
97
  --md-icon-fill: 1;
@@ -123,7 +126,7 @@
123
126
  padding: 8px 16px;
124
127
  border-radius: var(--md-comp-nav-bar-indicator-shape);
125
128
 
126
- > i {
129
+ > #{$icon} {
127
130
  width: var(--md-comp-nav-bar-icon-size);
128
131
  height: var(--md-comp-nav-bar-icon-size);
129
132
  border-radius: 0;
@@ -137,9 +140,9 @@
137
140
  }
138
141
 
139
142
  &:hover:not(.active, [aria-current]) {
140
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
143
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
141
144
 
142
- > i {
145
+ > #{$icon} {
143
146
  background-color: transparent;
144
147
  }
145
148
  }
@@ -149,7 +152,7 @@
149
152
  background-color: var(--md-comp-nav-bar-active-indicator-color);
150
153
  color: var(--md-comp-nav-bar-active-color);
151
154
 
152
- > i {
155
+ > #{$icon} {
153
156
  background-color: transparent;
154
157
  color: inherit;
155
158
  }
@@ -3,7 +3,7 @@
3
3
  // Navigation drawer.
4
4
  //
5
5
  // Overlay is a modal <dialog> (JS wraps a <ul class="sidenav"> in
6
- // dialog.sidenav-overlay, or the author writes <dialog class="sidenav">).
6
+ // dialog:is(.sidenav-overlay, .navigation-drawer-overlay), or the author writes <dialog class="sidenav">).
7
7
  // showModal() puts it on the top layer; ::backdrop is the scrim; Escape
8
8
  // and a scrim tap close it. Drag writes --md-comp-nav-drawer-shift;
9
9
  // this file does the slide.
@@ -12,7 +12,7 @@
12
12
  // accordion. The summary is a destination row with an expand_more
13
13
  // chevron; there is no Collapsible plugin.
14
14
  //
15
- // .sidenav-fixed is a breakpoint, not a JS open(): at `large` the host
15
+ // :is(.sidenav-fixed, .navigation-drawer-fixed) is a breakpoint, not a JS open(): at `large` the host
16
16
  // is display: contents so the list is a docked sidebar. Below that it
17
17
  // is the same overlay drawer as any other sidenav.
18
18
  //
@@ -26,13 +26,31 @@
26
26
  // --md-comp-nav-drawer-font-size 14px
27
27
  // --md-comp-nav-drawer-padding 16px
28
28
  // --md-comp-nav-drawer-line-height item height
29
-
30
- :root {
29
+ // --md-comp-nav-drawer-user-view-shape
30
+ // 16px, bottom corners of the header
31
+ // --md-comp-nav-drawer-user-view-scrim
32
+ // gradient laid over the header image
33
+ // --md-comp-nav-drawer-user-view-ink-color
34
+ // text colour over that scrim
35
+
36
+ :root, :host {
31
37
  --md-comp-nav-drawer-width: 300px;
32
38
  }
33
39
 
34
- dialog.sidenav-overlay,
35
- dialog.sidenav {
40
+ // A drawer row is whatever element the list item holds: a destination
41
+ // <a>, the <summary> of a nested section, the <button> a command row is
42
+ // written with (.navigation-drawer-close), and the plain element a
43
+ // .subheader label is. Keyed on `li > a`, the row layout disappeared the
44
+ // moment a row stopped being an anchor - the same trap $_icon documents.
45
+ // $_row-control is the subset that is operable: it takes the hover and
46
+ // focus treatment, a label does not. Both are wrapped in :is() - a bare
47
+ // comma list breaks out of its context (see $_bar in _navbar).
48
+ $_row: ":is(li > :is(a, button, .subheader), summary)";
49
+ $_row-control: ":is(li > :is(a, button):not(.button, .icon-button, .circle, .extra), summary)";
50
+
51
+
52
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay),
53
+ dialog:is(.sidenav, .navigation-drawer) {
36
54
  --md-comp-nav-drawer-item-height: 48px;
37
55
  --md-comp-nav-drawer-duration: 250ms;
38
56
  --md-comp-nav-drawer-easing: ease;
@@ -41,6 +59,14 @@ dialog.sidenav {
41
59
  --md-comp-nav-drawer-font-size: 14px;
42
60
  --md-comp-nav-drawer-padding: 16px;
43
61
  --md-comp-nav-drawer-line-height: var(--md-comp-nav-drawer-item-height);
62
+ --md-comp-nav-drawer-user-view-shape: 16px;
63
+ --md-comp-nav-drawer-user-view-ink-color: var(--md-ref-palette-neutral100);
64
+ --md-comp-nav-drawer-user-view-scrim: linear-gradient(
65
+ to bottom,
66
+ color-mix(in oklab, var(--md-sys-color-scrim) 15%, transparent) 0%,
67
+ color-mix(in oklab, var(--md-sys-color-scrim) 50%, transparent) 55%,
68
+ color-mix(in oklab, var(--md-sys-color-scrim) 80%, transparent) 100%
69
+ );
44
70
 
45
71
  box-sizing: border-box;
46
72
  width: var(--md-comp-nav-drawer-width);
@@ -85,7 +111,7 @@ dialog.sidenav {
85
111
  }
86
112
 
87
113
  &::backdrop {
88
- background-color: color-mix(in oklab, var(--md-sys-color-scrim) 32%, transparent);
114
+ background-color: var(--md-comp-scrim-color);
89
115
  opacity: var(--md-comp-nav-drawer-open, 1);
90
116
  backdrop-filter: none;
91
117
  transition:
@@ -96,22 +122,22 @@ dialog.sidenav {
96
122
  }
97
123
 
98
124
  @starting-style {
99
- :is(dialog.sidenav-overlay, dialog.sidenav):modal {
125
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)):modal {
100
126
  transform: translateX(-100%);
101
127
  }
102
128
 
103
- :is(dialog.sidenav-overlay, dialog.sidenav).right-aligned:modal {
129
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)).right-aligned:modal {
104
130
  transform: translateX(100%);
105
131
  }
106
132
 
107
- :is(dialog.sidenav-overlay, dialog.sidenav):modal::backdrop {
133
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)):modal::backdrop {
108
134
  opacity: 0;
109
135
  }
110
136
  }
111
137
 
112
138
  @media (prefers-reduced-motion: reduce) {
113
- dialog.sidenav-overlay,
114
- dialog.sidenav {
139
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay),
140
+ dialog:is(.sidenav, .navigation-drawer) {
115
141
  transition: none;
116
142
 
117
143
  &::backdrop {
@@ -123,17 +149,25 @@ dialog.sidenav {
123
149
  // Docked sidebar: the host must not be a box, or it would steal the
124
150
  // list's position:fixed. Closed, so it is not :modal and does not
125
151
  // lock the page.
126
- @include bp-up("large") {
127
- dialog.sidenav-overlay:has(> .sidenav-fixed):not(:modal) {
152
+ @include bp-up("expanded") {
153
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay):has(> :is(.sidenav-fixed, .navigation-drawer-fixed)):not(:modal) {
128
154
  display: contents;
129
155
  }
130
156
  }
131
157
 
132
- .sidenav {
158
+ :is(.sidenav, .navigation-drawer) {
133
159
  --md-comp-nav-drawer-item-height: 48px;
134
160
  --md-comp-nav-drawer-font-size: 14px;
135
161
  --md-comp-nav-drawer-padding: 16px;
136
162
  --md-comp-nav-drawer-line-height: var(--md-comp-nav-drawer-item-height);
163
+ --md-comp-nav-drawer-user-view-shape: 16px;
164
+ --md-comp-nav-drawer-user-view-ink-color: var(--md-ref-palette-neutral100);
165
+ --md-comp-nav-drawer-user-view-scrim: linear-gradient(
166
+ to bottom,
167
+ color-mix(in oklab, var(--md-sys-color-scrim) 15%, transparent) 0%,
168
+ color-mix(in oklab, var(--md-sys-color-scrim) 50%, transparent) 55%,
169
+ color-mix(in oklab, var(--md-sys-color-scrim) 80%, transparent) 100%
170
+ );
137
171
 
138
172
  box-sizing: border-box;
139
173
  position: fixed;
@@ -162,12 +196,32 @@ dialog.sidenav {
162
196
  align-content: center;
163
197
  }
164
198
 
165
- li.active > a:not(.button, .circle, .extra) {
199
+ // The selected destination keeps its pill under the pointer: the row's own
200
+ // state layer mixes into `transparent`, so letting it reach a selected row
201
+ // would erase the fill rather than tint it. Higher specificity than the row
202
+ // rule, so order does not matter.
203
+ li.active > :is(a, button):not(.button, .icon-button, .circle, .extra) {
166
204
  background-color: var(--md-sys-color-secondary-container);
167
205
  color: var(--md-sys-color-on-secondary-container);
206
+
207
+ &:hover {
208
+ background-color: color-mix(
209
+ in oklab,
210
+ var(--md-sys-color-on-secondary-container) state-opacity("hover"),
211
+ var(--md-sys-color-secondary-container)
212
+ );
213
+ }
214
+
215
+ &:active {
216
+ background-color: color-mix(
217
+ in oklab,
218
+ var(--md-sys-color-on-secondary-container) state-opacity("pressed"),
219
+ var(--md-sys-color-secondary-container)
220
+ );
221
+ }
168
222
  }
169
223
 
170
- :is(li > a, summary) {
224
+ #{$_row} {
171
225
  display: flex;
172
226
  align-items: center;
173
227
  margin: 0 12px;
@@ -191,25 +245,32 @@ dialog.sidenav {
191
245
  }
192
246
  }
193
247
 
194
- li > a:not(.button, .circle, .extra),
195
- summary {
248
+ #{$_row-control} {
196
249
  color: var(--md-sys-color-on-surface-variant);
197
250
 
198
251
  &:hover {
199
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
252
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
253
+ }
254
+
255
+ &:active {
256
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("pressed"), transparent);
200
257
  }
201
258
 
202
259
  @include focus-visible;
203
260
  }
204
261
 
205
- li > a.button,
206
- li > a.circle,
207
- li > a.extra {
262
+ li > :is(a, button):is(.button, .circle, .extra) {
208
263
  margin: 10px 15px;
209
264
  }
210
265
 
211
- summary {
266
+ // Every <button> is a styled button ($_btn in _buttons is `button:not(...)`),
267
+ // so a command row has to be stripped back to a row the same way <summary>
268
+ // is. The chevron below stays summary-only.
269
+ :is(summary, li > button:not(.button, .icon-button, .circle, .extra)) {
212
270
  box-sizing: border-box;
271
+ // A <button> centres its own content, so a command row's label sat in the
272
+ // middle of the row while every anchor row started at the inset.
273
+ justify-content: flex-start;
213
274
  border: 0;
214
275
  background: transparent;
215
276
  font: inherit;
@@ -217,7 +278,9 @@ dialog.sidenav {
217
278
  list-style: none;
218
279
  cursor: pointer;
219
280
  appearance: none;
281
+ }
220
282
 
283
+ summary {
221
284
  &::-webkit-details-marker {
222
285
  display: none;
223
286
  }
@@ -282,16 +345,33 @@ dialog.sidenav {
282
345
  height: auto;
283
346
  padding: 0;
284
347
 
285
- &:hover {
348
+ &:is(:hover, :active) {
286
349
  background-color: transparent;
287
350
  }
288
351
  }
289
352
 
353
+ // The header image is clipped by .background, so the radius goes on the
354
+ // clipping box rather than the <img>: the image is a child of an
355
+ // overflow: hidden box, and rounding the child alone leaves the box's own
356
+ // square corners painting behind it.
290
357
  .background {
291
358
  overflow: hidden;
292
359
  position: absolute;
293
360
  inset: 0;
294
361
  z-index: -1;
362
+ border-end-start-radius: var(--md-comp-nav-drawer-user-view-shape);
363
+ border-end-end-radius: var(--md-comp-nav-drawer-user-view-shape);
364
+
365
+ // The name and email sit on whatever photograph the author supplied, so
366
+ // the scrim is what makes them legible - bottom-weighted, because that
367
+ // is where the text is. It rides inside .background so the rounded
368
+ // corners clip it too.
369
+ &::after {
370
+ content: "";
371
+ position: absolute;
372
+ inset: 0;
373
+ background: var(--md-comp-nav-drawer-user-view-scrim);
374
+ }
295
375
  }
296
376
 
297
377
  .circle,
@@ -311,6 +391,17 @@ dialog.sidenav {
311
391
  line-height: calc(var(--md-comp-nav-drawer-line-height) * 0.5);
312
392
  }
313
393
 
394
+ // Over an image the surface roles mean nothing and the link colour these
395
+ // spans inherit is unreadable on a mid-tone photograph. Scoped to
396
+ // :has(> .background): with no image the header is on the drawer surface
397
+ // and must keep tracking the theme, where fixed light ink would vanish.
398
+ &:has(> .background) {
399
+ .name,
400
+ .email {
401
+ color: var(--md-comp-nav-drawer-user-view-ink-color);
402
+ }
403
+ }
404
+
314
405
  .name {
315
406
  margin-top: 16px;
316
407
  font-weight: 500;
@@ -324,10 +415,10 @@ dialog.sidenav {
324
415
  }
325
416
 
326
417
  // Overlay only: the dialog is the panel, so the list fills it. When the
327
- // host is `display: contents` (a docked .sidenav-fixed) this must not
418
+ // host is `display: contents` (a docked :is(.sidenav-fixed, .navigation-drawer-fixed)) this must not
328
419
  // apply — static positioning would drop the list into the header flow
329
420
  // and it would read as part of the app bar.
330
- dialog.sidenav-overlay:modal > .sidenav {
421
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay):modal > :is(.sidenav, .navigation-drawer) {
331
422
  position: static;
332
423
  inset: auto;
333
424
  width: 100%;
@@ -338,7 +429,7 @@ dialog.sidenav-overlay:modal > .sidenav {
338
429
  z-index: auto;
339
430
  }
340
431
 
341
- .sidenav.sidenav-fixed {
432
+ :is(.sidenav, .navigation-drawer):is(.sidenav-fixed, .navigation-drawer-fixed) {
342
433
  transform: none;
343
434
 
344
435
  &.right-aligned {
@@ -346,11 +437,11 @@ dialog.sidenav-overlay:modal > .sidenav {
346
437
  }
347
438
  }
348
439
 
349
- @include bp-down("large") {
440
+ @include bp-down("expanded") {
350
441
  // Uninitialized / no-js: keep a fixed drawer off-canvas. Once JS wraps
351
442
  // it, the host's display:none hides the closed drawer and this must
352
443
  // not also hide the list inside an open dialog.
353
- .sidenav.sidenav-fixed {
444
+ :is(.sidenav, .navigation-drawer):is(.sidenav-fixed, .navigation-drawer-fixed) {
354
445
  transform: translateX(-100%);
355
446
 
356
447
  &.right-aligned {
@@ -358,7 +449,7 @@ dialog.sidenav-overlay:modal > .sidenav {
358
449
  }
359
450
  }
360
451
 
361
- .sidenav .user-view {
452
+ :is(.sidenav, .navigation-drawer) .user-view {
362
453
  padding: var(--md-comp-nav-drawer-padding) var(--md-comp-nav-drawer-padding) 0;
363
454
  }
364
455
  }
@@ -376,11 +467,10 @@ dialog.sidenav-overlay:modal > .sidenav {
376
467
  }
377
468
  }
378
469
 
379
- @include bp-up("large") {
470
+ @include bp-up("expanded") {
380
471
  .drag-target[data-fixed] {
381
472
  display: none;
382
473
  pointer-events: none;
383
474
  }
384
475
  }
385
476
 
386
-