@expressivecss/expressive 0.4.0 → 0.6.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 (69) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +4 -9
  3. package/dist/css/expressive.css +2617 -951
  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 +643 -804
  8. package/dist/js/expressive.cjs.map +4 -4
  9. package/dist/js/expressive.js +643 -804
  10. package/dist/js/expressive.js.map +4 -4
  11. package/dist/js/expressive.min.js +11 -11
  12. package/dist/js/expressive.min.js.map +4 -4
  13. package/dist/js/expressive.mjs +643 -804
  14. package/dist/js/expressive.mjs.map +4 -4
  15. package/dist/types/behaviors/bottomSheets.d.ts +18 -0
  16. package/dist/types/behaviors/sideSheets.d.ts +18 -0
  17. package/dist/types/components/autocomplete.d.ts +11 -11
  18. package/dist/types/components/index.d.ts +3 -5
  19. package/dist/types/components/{dropdown.d.ts → menu.d.ts} +38 -37
  20. package/dist/types/components/navigationRail.d.ts +53 -0
  21. package/dist/types/components/range.d.ts +3 -0
  22. package/dist/types/components/registry.d.ts +7 -15
  23. package/dist/types/components/select.d.ts +12 -12
  24. package/dist/types/components/{toasts.d.ts → snackbar.d.ts} +28 -25
  25. package/dist/types/index.d.ts +3 -1
  26. package/package.json +3 -2
  27. package/src/sass/README.md +13 -11
  28. package/src/sass/abstracts/_breakpoints.scss +14 -3
  29. package/src/sass/abstracts/_mixins.scss +3 -3
  30. package/src/sass/abstracts/_variables.scss +27 -2
  31. package/src/sass/base/_global.scss +1 -1
  32. package/src/sass/base/_grid.scss +66 -116
  33. package/src/sass/base/_index.scss +1 -1
  34. package/src/sass/components/_badges.scss +80 -60
  35. package/src/sass/components/_bottom-sheet.scss +137 -0
  36. package/src/sass/components/_cards.scss +4 -4
  37. package/src/sass/components/_chips.scss +6 -5
  38. package/src/sass/components/_dialog.scss +268 -0
  39. package/src/sass/components/_index.scss +9 -7
  40. package/src/sass/components/_list.scss +253 -0
  41. package/src/sass/components/_menu.scss +341 -0
  42. package/src/sass/components/_navbar.scss +65 -117
  43. package/src/sass/components/_navigation-bar.scss +165 -0
  44. package/src/sass/components/_navigation-rail.scss +308 -0
  45. package/src/sass/components/_page-footer.scss +7 -3
  46. package/src/sass/components/_panes.scss +294 -0
  47. package/src/sass/components/_side-sheet.scss +193 -0
  48. package/src/sass/components/_sidenav.scss +78 -49
  49. package/src/sass/components/{_toast.scss → _snackbar.scss} +35 -34
  50. package/src/sass/components/_tabs.scss +1 -27
  51. package/src/sass/components/_toolbar.scss +91 -25
  52. package/src/sass/components/_tooltip.scss +1 -1
  53. package/src/sass/components/forms/_fieldset.scss +1 -1
  54. package/src/sass/components/forms/_input-fields.scss +3 -3
  55. package/src/sass/components/forms/_range.scss +423 -98
  56. package/src/sass/components/forms/_select.scss +19 -17
  57. package/src/sass/tokens/_reference.scss +37 -13
  58. package/src/sass/tokens/_theme.scss +5 -13
  59. package/src/sass/utilities/_colors.scss +16 -74
  60. package/src/sass/utilities/_visibility.scss +8 -0
  61. package/dist/types/components/collapsible.d.ts +0 -79
  62. package/dist/types/components/pushpin.d.ts +0 -51
  63. package/dist/types/components/tapTarget.d.ts +0 -57
  64. package/src/sass/components/_collapsible.scss +0 -179
  65. package/src/sass/components/_collection.scss +0 -180
  66. package/src/sass/components/_dropdown.scss +0 -225
  67. package/src/sass/components/_modal.scss +0 -190
  68. package/src/sass/components/_pushpin.scss +0 -11
  69. package/src/sass/components/_tapTarget.scss +0 -187
@@ -0,0 +1,341 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // Vertical menus (M3 Expressive).
4
+ //
5
+ // Anatomy is the HTML. A <menu id> is the surface; each <li> is an item.
6
+ // A leading <i> is the leading icon; a trailing <i> or <kbd> is trailing
7
+ // content. An <hr> is a divider. A .gap <li> splits groups. A .label
8
+ // <li> is a 32dp group heading. A nested <menu> is a flyout. The trigger
9
+ // is .menu-trigger[data-target] — that is the JS contract.
10
+ //
11
+ // Tokens follow https://m3.material.io/components/menus/specs
12
+ // container surface-container, 16dp corners, elevation 2, 112–280
13
+ // padding 4dp
14
+ // item 48dp, extra-small (4dp) corners, label-large / on-surface
15
+ // selected medium (12dp) corners, tertiary-container / on-tertiary
16
+ // icon 20dp, on-surface-variant
17
+ // divider 1dp outline-variant, inset (not full-bleed)
18
+ // gap 6dp, more expressive than a divider
19
+ // label 32dp, on-surface-variant
20
+ // vibrant tertiary-container surface; selected is tertiary
21
+ // submenu scale + fade; focused flyout rounds up, parent rounds down
22
+
23
+ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='M10 6l6 6-6 6'/%3E%3C/svg%3E");
24
+
25
+ [popover] {
26
+ outline: none;
27
+ padding: 0;
28
+ border: none;
29
+ }
30
+
31
+ @mixin menu-surface {
32
+ --md-comp-menu-container-color: var(--md-sys-color-surface-container);
33
+ --md-comp-menu-container-shape: 16px;
34
+ --md-comp-menu-item-container-height: 48px;
35
+ --md-comp-menu-item-container-shape: 4px;
36
+ --md-comp-menu-item-selected-container-shape: 12px;
37
+ --md-comp-menu-item-label-text-color: var(--md-sys-color-on-surface);
38
+ --md-comp-menu-item-leading-icon-color: var(--md-sys-color-on-surface-variant);
39
+ --md-comp-menu-item-trailing-icon-color: var(--md-sys-color-on-surface-variant);
40
+ --md-comp-menu-item-selected-container-color: var(--md-sys-color-tertiary-container);
41
+ --md-comp-menu-item-selected-label-text-color: var(--md-sys-color-on-tertiary-container);
42
+ --md-comp-menu-item-selected-leading-icon-color: var(--md-sys-color-on-tertiary-container);
43
+ --md-comp-menu-item-icon-size: 20px;
44
+ --md-comp-menu-supporting-text-color: var(--md-sys-color-on-surface-variant);
45
+
46
+ box-sizing: border-box;
47
+ min-width: 112px;
48
+ margin: 0;
49
+ padding: 4px;
50
+ list-style: none;
51
+ background-color: var(--md-comp-menu-container-color);
52
+ border-radius: var(--md-comp-menu-container-shape);
53
+ color: var(--md-comp-menu-item-label-text-color);
54
+ @include z-depth("2");
55
+ transition: border-radius 200ms ease;
56
+
57
+ &.vibrant {
58
+ --md-comp-menu-container-color: var(--md-sys-color-tertiary-container);
59
+ --md-comp-menu-item-label-text-color: var(--md-sys-color-on-tertiary-container);
60
+ --md-comp-menu-item-leading-icon-color: var(--md-sys-color-on-tertiary-container);
61
+ --md-comp-menu-item-trailing-icon-color: var(--md-sys-color-on-tertiary-container);
62
+ --md-comp-menu-supporting-text-color: var(--md-sys-color-on-tertiary-container);
63
+ --md-comp-menu-item-selected-container-color: var(--md-sys-color-tertiary);
64
+ --md-comp-menu-item-selected-label-text-color: var(--md-sys-color-on-tertiary);
65
+ --md-comp-menu-item-selected-leading-icon-color: var(--md-sys-color-on-tertiary);
66
+ }
67
+
68
+ > li {
69
+ box-sizing: border-box;
70
+ margin: 0;
71
+ padding: 0;
72
+ width: 100%;
73
+ cursor: pointer;
74
+ text-align: left;
75
+ border-radius: var(--md-comp-menu-item-container-shape);
76
+
77
+ > a,
78
+ > button,
79
+ > span {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 12px;
83
+ box-sizing: border-box;
84
+ width: 100%;
85
+ min-height: var(--md-comp-menu-item-container-height);
86
+ margin: 0;
87
+ padding: 0 8px;
88
+ border: 0;
89
+ background: none;
90
+ color: var(--md-comp-menu-item-label-text-color);
91
+ text-decoration: none;
92
+ cursor: pointer;
93
+
94
+ &:hover,
95
+ &:focus {
96
+ background: none;
97
+ }
98
+ @include typescale("label-large");
99
+ font-family: $font-stack;
100
+ }
101
+
102
+ > a > i,
103
+ > button > i,
104
+ > i {
105
+ flex: 0 0 var(--md-comp-menu-item-icon-size);
106
+ font-size: var(--md-comp-menu-item-icon-size);
107
+ width: var(--md-comp-menu-item-icon-size);
108
+ height: var(--md-comp-menu-item-icon-size);
109
+ line-height: var(--md-comp-menu-item-icon-size);
110
+ color: var(--md-comp-menu-item-leading-icon-color);
111
+ }
112
+
113
+ > :is(a, button) > i:last-child:not(:only-child) {
114
+ margin-inline-start: auto;
115
+ color: var(--md-comp-menu-item-trailing-icon-color);
116
+ }
117
+
118
+ > :is(a, button) > :is(kbd, .shortcut) {
119
+ margin-inline-start: auto;
120
+ padding: 0;
121
+ border: 0;
122
+ background: none;
123
+ color: var(--md-comp-menu-supporting-text-color);
124
+ font-family: inherit;
125
+ @include typescale("label-large");
126
+ }
127
+ }
128
+
129
+ > li:not(.disabled, .divider, .optgroup, .gap, .label) {
130
+ min-height: var(--md-comp-menu-item-container-height);
131
+ }
132
+
133
+ // Hover / press sit on extra-small corners and do not span the container.
134
+ > li:not(.disabled, .divider, .optgroup, .gap, .label):hover,
135
+ > li.active:not(.selected, [aria-selected="true"]) {
136
+ background-color: color-mix(
137
+ in oklab,
138
+ var(--md-comp-menu-item-label-text-color) 8%,
139
+ transparent
140
+ );
141
+ }
142
+
143
+ > li:not(.disabled, .divider, .optgroup, .gap, .label):active {
144
+ background-color: color-mix(
145
+ in oklab,
146
+ var(--md-comp-menu-item-label-text-color) 10%,
147
+ transparent
148
+ );
149
+ }
150
+
151
+ > li.selected,
152
+ > li[aria-selected="true"] {
153
+ border-radius: var(--md-comp-menu-item-selected-container-shape);
154
+ background-color: var(--md-comp-menu-item-selected-container-color);
155
+ color: var(--md-comp-menu-item-selected-label-text-color);
156
+
157
+ > :is(a, button, span) {
158
+ color: var(--md-comp-menu-item-selected-label-text-color);
159
+ }
160
+
161
+ > :is(a, button, span) > i,
162
+ > i {
163
+ color: var(--md-comp-menu-item-selected-leading-icon-color);
164
+ }
165
+ }
166
+
167
+ > li.selected:hover,
168
+ > li[aria-selected="true"]:hover {
169
+ background-color: color-mix(
170
+ in oklab,
171
+ var(--md-comp-menu-item-selected-label-text-color) 8%,
172
+ var(--md-comp-menu-item-selected-container-color)
173
+ );
174
+ }
175
+
176
+ > li:focus-visible {
177
+ outline: 3px solid var(--md-sys-color-secondary);
178
+ outline-offset: -3px;
179
+ }
180
+
181
+ > li.disabled {
182
+ pointer-events: none;
183
+ opacity: 0.38;
184
+ }
185
+
186
+ // Inset divider — selection and rules do not span the container.
187
+ > hr,
188
+ > li.divider {
189
+ display: block;
190
+ width: calc(100% - 8px);
191
+ height: 1px;
192
+ min-height: 0;
193
+ margin: 4px auto;
194
+ padding: 0;
195
+ border: 0;
196
+ background-color: var(--md-sys-color-outline-variant);
197
+ cursor: default;
198
+ pointer-events: none;
199
+ border-radius: 0;
200
+ }
201
+
202
+ // Gap: more expressive than a divider. One or two per menu; not in a scroller.
203
+ > li.gap {
204
+ height: 6px;
205
+ min-height: 6px;
206
+ margin: 0 -4px;
207
+ padding: 0;
208
+ pointer-events: none;
209
+ cursor: default;
210
+ background: transparent;
211
+ border-radius: 0;
212
+ box-shadow: none;
213
+ }
214
+
215
+ > li.label {
216
+ display: flex;
217
+ align-items: center;
218
+ min-height: 32px;
219
+ padding: 0 8px;
220
+ pointer-events: none;
221
+ cursor: default;
222
+ color: var(--md-comp-menu-supporting-text-color);
223
+ @include typescale("label-large");
224
+ font-family: $font-stack;
225
+ }
226
+
227
+ li:has(> menu) {
228
+ position: relative;
229
+ }
230
+
231
+ li:has(> menu) > :is(a, button, span)::after {
232
+ content: "";
233
+ flex: 0 0 var(--md-comp-menu-item-icon-size);
234
+ width: var(--md-comp-menu-item-icon-size);
235
+ height: var(--md-comp-menu-item-icon-size);
236
+ margin-inline-start: auto;
237
+ background-color: var(--md-comp-menu-item-trailing-icon-color);
238
+ mask: $_menu-chevron center / 20px no-repeat;
239
+ }
240
+ }
241
+
242
+ menu[id] {
243
+ @include menu-surface;
244
+
245
+ display: none;
246
+ position: absolute;
247
+ left: 0;
248
+ top: 0;
249
+ z-index: 9999;
250
+ overflow-y: auto;
251
+ opacity: 0;
252
+ user-select: none;
253
+ transform-origin: 0 0;
254
+
255
+ // Nested flyouts sit outside the list box; do not clip them.
256
+ &:has(> li > menu) {
257
+ overflow: visible;
258
+ }
259
+
260
+ // Unfocused parent rounds down while a flyout is the focus.
261
+ &:has(> li:is(:hover, :focus-within, .open) > menu) {
262
+ --md-comp-menu-container-shape: 8px;
263
+ }
264
+ }
265
+
266
+ // Nested menu. Optional, desktop-first. No second Menu instance —
267
+ // a <menu> inside an <li> is a flyout. Hover / :focus-within on fine
268
+ // pointers; .open is the tap / keyboard switch.
269
+ menu[id] li > menu {
270
+ @include menu-surface;
271
+
272
+ display: block;
273
+ position: absolute;
274
+ inset-block-start: 0;
275
+ inset-inline-start: 100%;
276
+ z-index: 1;
277
+ overflow: visible;
278
+ visibility: hidden;
279
+ opacity: 0;
280
+ pointer-events: none;
281
+ transform: translateX(-4px) scale(0.96);
282
+ transform-origin: 0 0;
283
+ transition:
284
+ opacity 180ms ease,
285
+ transform 180ms ease,
286
+ visibility 180ms ease,
287
+ border-radius 200ms ease;
288
+ }
289
+
290
+ @media (hover: hover) and (pointer: fine) {
291
+ menu[id] li:is(:hover, :focus-within) > menu {
292
+ visibility: visible;
293
+ opacity: 1;
294
+ pointer-events: auto;
295
+ transform: none;
296
+ --md-comp-menu-container-shape: 20px;
297
+ }
298
+ }
299
+
300
+ menu[id] li.open > menu {
301
+ visibility: visible;
302
+ opacity: 1;
303
+ pointer-events: auto;
304
+ transform: none;
305
+ --md-comp-menu-container-shape: 20px;
306
+ }
307
+
308
+ menu[id] li.submenu-start > menu {
309
+ inset-inline-start: auto;
310
+ inset-inline-end: 100%;
311
+ transform-origin: 100% 0;
312
+ }
313
+
314
+ menu[id] li.submenu-start:is(:hover, :focus-within, .open) > menu {
315
+ transform: none;
316
+ }
317
+
318
+ body.keyboard-focused {
319
+ menu[id] > li:focus {
320
+ background-color: color-mix(
321
+ in oklab,
322
+ var(--md-comp-menu-item-label-text-color) 10%,
323
+ transparent
324
+ );
325
+ }
326
+
327
+ menu[id] > li:focus:not(.selected, [aria-selected="true"]) {
328
+ outline: 3px solid var(--md-sys-color-secondary);
329
+ outline-offset: -3px;
330
+ }
331
+ }
332
+
333
+ @media (prefers-reduced-motion: reduce) {
334
+ menu[id] li > menu {
335
+ transition: none;
336
+ }
337
+ }
338
+
339
+ .menu-trigger {
340
+ cursor: pointer;
341
+ }
@@ -1,22 +1,37 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
- // Top and bottom app bars.
3
+ // Top app bar.
4
4
  //
5
- // Anatomy is the HTML. A header whose child is a nav is a top app bar; a
6
- // footer whose child is a nav (and that is not .page-footer) is a bottom app
7
- // bar. Headings are the headline, icon-only links/buttons are the leading and
5
+ // Anatomy is the HTML. A header whose child is a nav is a top app bar.
6
+ // Headings are the headline, icon-only links/buttons are the leading and
8
7
  // trailing actions, <menu> / <ul> hold text destinations. Helpers are only
9
8
  // for variants the markup cannot express: .center, .medium, .large, .fixed.
9
+ // Destinations at the bottom of the screen are nav.navigation-bar, not this.
10
10
  //
11
- // Tokens follow the M3 top-app-bar / bottom-app-bar specs
11
+ // Tokens follow the M3 top-app-bar spec
12
12
  // (https://m3.material.io/components/app-bars/specs):
13
- // container surface, 64dp small / 112 medium / 152 large / 80 bottom
13
+ // container surface, 64dp small / 112 medium / 152 large
14
14
  // headline on-surface, title-large (small), headline-small/medium
15
15
  // icons on-surface-variant, 24dp in a 48dp target, 4dp inset
16
- // elevation 0 at rest; level 2 once a .fixed bar has scrolled
16
+ // on scroll surface-container fill (not elevation) so the bar
17
+ // separates from the body. A color utility on the
18
+ // header still wins — it sets background-color itself.
19
+
20
+ // Scroll progress, not the fill color. Registering the fill as <color>
21
+ // with initial-value: transparent made the 8px lerp run from
22
+ // transparent-black through black: light-dark() tokens are IACVT for a
23
+ // typed <color> property, so the used value collapsed to the initial
24
+ // value. A <number> interpolates; color-mix() then resolves the two
25
+ // live surfaces at use time.
26
+ @property --md-comp-top-app-bar-scroll {
27
+ syntax: "<number>";
28
+ inherits: false;
29
+ initial-value: 0;
30
+ }
17
31
 
18
- header:has(> nav) {
32
+ header:has(> nav:not(.navigation-bar, .navigation-rail)) {
19
33
  --md-comp-top-app-bar-container-color: var(--md-sys-color-surface);
34
+ --md-comp-top-app-bar-scrolled-container-color: var(--md-sys-color-surface-container);
20
35
  --md-comp-top-app-bar-headline-color: var(--md-sys-color-on-surface);
21
36
  // Inherit so a fill + on-* pair on the header recolors the icons.
22
37
  // Set this to --md-sys-color-on-surface-variant for the spec mute.
@@ -26,12 +41,17 @@ header:has(> nav) {
26
41
  --md-comp-top-app-bar-leading-icon-size: 24px;
27
42
  --md-comp-top-app-bar-container-shape: 0;
28
43
 
29
- background-color: var(--md-comp-top-app-bar-container-color);
44
+ background-color: color-mix(
45
+ in oklab,
46
+ var(--md-comp-top-app-bar-scrolled-container-color)
47
+ calc(100% * var(--md-comp-top-app-bar-scroll)),
48
+ var(--md-comp-top-app-bar-container-color)
49
+ );
30
50
  color: var(--md-comp-top-app-bar-headline-color);
31
51
  border-radius: var(--md-comp-top-app-bar-container-shape);
32
52
  width: 100%;
33
53
 
34
- > nav:not(.tabs) {
54
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) {
35
55
  display: flex;
36
56
  align-items: center;
37
57
  box-sizing: border-box;
@@ -41,7 +61,7 @@ header:has(> nav) {
41
61
  margin: 0;
42
62
  }
43
63
 
44
- > nav:not(.tabs) > :is(h1, h2, h3, h4, h5, h6) {
64
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(h1, h2, h3, h4, h5, h6) {
45
65
  flex: 1 1 auto;
46
66
  min-width: 0;
47
67
  margin: 0;
@@ -63,7 +83,7 @@ header:has(> nav) {
63
83
  }
64
84
 
65
85
  // Icon-only actions — 48dp target, 24dp glyph, 4dp from the container edge.
66
- > nav:not(.tabs) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
86
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
67
87
  display: inline-flex;
68
88
  flex: 0 0 auto;
69
89
  align-items: center;
@@ -71,7 +91,10 @@ header:has(> nav) {
71
91
  width: 48px;
72
92
  height: 48px;
73
93
  padding: 0;
74
- margin: 0;
94
+ // Leave margin-inline-start unset so a medium/large bar can push
95
+ // the first trailing action to the end with margin-inline-start: auto.
96
+ margin-block: 0;
97
+ margin-inline-end: 0;
75
98
  border: 0;
76
99
  border-radius: 50%;
77
100
  background: transparent;
@@ -100,11 +123,11 @@ header:has(> nav) {
100
123
  }
101
124
 
102
125
  // Text destinations, either loose or grouped in a menu/list.
103
- > nav:not(.tabs) > a:not(:has(> i:only-child)):not(.button):not(.circle):not(.breadcrumb) {
126
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > a:not(:has(> i:only-child)):not(.button):not(.circle):not(.breadcrumb) {
104
127
  @extend %app-bar-destination;
105
128
  }
106
129
 
107
- > nav:not(.tabs) > :is(menu, ul) {
130
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > :is(menu, ul) {
108
131
  display: flex;
109
132
  align-items: stretch;
110
133
  align-self: stretch;
@@ -117,7 +140,7 @@ header:has(> nav) {
117
140
  align-items: stretch;
118
141
  }
119
142
 
120
- // Direct destinations only. Dropdown JS parks the menu next to the
143
+ // Direct destinations only. Menu JS parks the menu next to the
121
144
  // trigger, so a descendant `a` would also paint a square hover on
122
145
  // top of the menu's pill.
123
146
  > li > a {
@@ -128,7 +151,7 @@ header:has(> nav) {
128
151
  // Breadcrumb trail. Layout only — colors stay on the breadcrumb tokens
129
152
  // (which this header remaps to inherit). Do not treat the crumbs as
130
153
  // destinations.
131
- > nav:not(.tabs) > ol {
154
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > ol {
132
155
  display: flex;
133
156
  flex-wrap: wrap;
134
157
  align-items: center;
@@ -140,7 +163,7 @@ header:has(> nav) {
140
163
  padding-inline: 12px;
141
164
  }
142
165
 
143
- > nav:not(.tabs) > form {
166
+ > nav:not(.tabs, .navigation-bar, .navigation-rail) > form {
144
167
  display: flex;
145
168
  flex: 1 1 auto;
146
169
  align-items: center;
@@ -171,25 +194,6 @@ header:has(> nav) {
171
194
  }
172
195
  }
173
196
  }
174
-
175
- // Secondary row (tabs sit under the bar, not inside it).
176
- > .tabs {
177
- background-color: transparent;
178
- width: 100%;
179
- }
180
-
181
- @include bp-up("large") {
182
- // Beats the icon-button display:inline-flex (higher specificity).
183
- // Same flag the visibility utilities use for breakpoint hides.
184
- > nav:not(.tabs) > .sidenav-trigger {
185
- display: none !important;
186
- }
187
-
188
- // The trigger is still :first-child, so the title needs the no-icon inset.
189
- > nav:not(.tabs) > .sidenav-trigger + :is(h1, h2, h3, h4, h5, h6) {
190
- padding-inline-start: 12px;
191
- }
192
- }
193
197
  }
194
198
 
195
199
  %app-bar-destination {
@@ -216,7 +220,7 @@ header:has(> nav) {
216
220
 
217
221
  // Center-aligned small bar. The headline is taken out of flow so leading and
218
222
  // trailing actions can sit on the edges without shifting the title.
219
- header.center:has(> nav) > nav:not(.tabs) {
223
+ header.center:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
220
224
  position: relative;
221
225
  justify-content: space-between;
222
226
 
@@ -232,12 +236,18 @@ header.center:has(> nav) > nav:not(.tabs) {
232
236
  }
233
237
  }
234
238
 
235
- // Medium (112dp) and large (152dp): same markup, title wraps to a second row.
236
- header.medium:has(> nav) > nav:not(.tabs),
237
- header.large:has(> nav) > nav:not(.tabs) {
239
+ // Medium (112dp) and large (152dp): same markup, title on a second row.
240
+ // The title is pulled out of the first flex line (`order` + 100% basis),
241
+ // so leading/trailing icons would otherwise pack together at the start.
242
+ // `space-between` parks the icon row at the top and the title at the
243
+ // 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) {
238
246
  flex-wrap: wrap;
239
- align-content: flex-start;
247
+ align-content: space-between;
248
+ align-items: flex-start;
240
249
  height: auto;
250
+ padding-top: 4px;
241
251
 
242
252
  > :is(h1, h2, h3, h4, h5, h6) {
243
253
  order: 1;
@@ -246,9 +256,13 @@ header.large:has(> nav) > nav:not(.tabs) {
246
256
  overflow: visible;
247
257
  text-overflow: unset;
248
258
  }
259
+
260
+ > :is(h1, h2, h3, h4, h5, h6) + * {
261
+ margin-inline-start: auto;
262
+ }
249
263
  }
250
264
 
251
- header.medium:has(> nav) > nav:not(.tabs) {
265
+ header.medium:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
252
266
  min-height: 112px;
253
267
  padding-bottom: 16px;
254
268
 
@@ -262,7 +276,7 @@ header.medium:has(> nav) > nav:not(.tabs) {
262
276
  }
263
277
  }
264
278
 
265
- header.large:has(> nav) > nav:not(.tabs) {
279
+ header.large:has(> nav) > nav:not(.tabs, .navigation-bar, .navigation-rail) {
266
280
  min-height: 152px;
267
281
  padding-bottom: 28px;
268
282
 
@@ -276,9 +290,12 @@ header.large:has(> nav) > nav:not(.tabs) {
276
290
  }
277
291
  }
278
292
 
279
- // Pin the bar. Scroll-driven elevation is progressive enhancement: without
280
- // animation-timeline the bar stays at rest (level 0), which is the spec default.
281
- header.fixed:has(> nav) {
293
+ // Pin the bar. On scroll, fill with surface-container so it separates
294
+ // from the body (M3 Expressive). The animation only drives the mix
295
+ // progress; a utility class on the header sets background-color itself
296
+ // and is not overwritten. Without animation-timeline the bar stays at
297
+ // rest (surface), the spec default.
298
+ header.fixed:has(> nav:not(.navigation-bar, .navigation-rail)) {
282
299
  position: sticky;
283
300
  top: 0;
284
301
  z-index: 997;
@@ -289,75 +306,6 @@ header.fixed:has(> nav) {
289
306
 
290
307
  @keyframes top-app-bar-scroll {
291
308
  to {
292
- box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14),
293
- 0 1px 10px 0 rgba(0, 0, 0, 0.12),
294
- 0 2px 4px -1px rgba(0, 0, 0, 0.3);
295
- }
296
- }
297
-
298
- // Bottom app bar. 80dp row, icons on the start, a FAB (any .btn) pushed to the end.
299
- footer:has(> nav:only-child) {
300
- --md-comp-bottom-app-bar-container-color: var(--md-sys-color-surface);
301
- --md-comp-bottom-app-bar-container-height: 80px;
302
-
303
- background-color: var(--md-comp-bottom-app-bar-container-color);
304
- color: var(--md-sys-color-on-surface);
305
- width: 100%;
306
- @include z-depth("2");
307
-
308
- > nav:not(.tabs) {
309
- display: flex;
310
- align-items: center;
311
- box-sizing: border-box;
312
- width: 100%;
313
- min-height: var(--md-comp-bottom-app-bar-container-height);
314
- padding: 12px 4px;
315
- margin: 0;
316
- }
317
-
318
- > nav:not(.tabs) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
319
- display: inline-flex;
320
- flex: 0 0 auto;
321
- align-items: center;
322
- justify-content: center;
323
- width: 48px;
324
- height: 48px;
325
- padding: 0;
326
- margin: 0;
327
- border: 0;
328
- border-radius: 50%;
329
- background: transparent;
330
- color: var(--md-sys-color-on-surface-variant);
331
- cursor: pointer;
332
- appearance: none;
333
- font: inherit;
334
- text-decoration: none;
335
- @include focus-visible;
336
-
337
- > i {
338
- display: block;
339
- font-size: 24px;
340
- width: 24px;
341
- height: 24px;
342
- line-height: 24px;
343
- }
344
-
345
- &:hover {
346
- background-color: color-mix(in oklab, currentColor 8%, transparent);
347
- }
348
-
349
- &:active {
350
- background-color: color-mix(in oklab, currentColor 10%, transparent);
351
- }
352
- }
353
-
354
- > nav:not(.tabs) > :is(.button, .circle, .extra) {
355
- margin-inline-start: auto;
356
- }
357
-
358
- &.fixed {
359
- position: sticky;
360
- bottom: 0;
361
- z-index: 997;
309
+ --md-comp-top-app-bar-scroll: 1;
362
310
  }
363
311
  }