@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
@@ -0,0 +1,232 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // FAB menu. A FAB that expands into a list of labelled actions and morphs
4
+ // into a close button while it is open. `.fab-menu` is the host; `.active`
5
+ // and `aria-expanded` are the framework's, never the author's.
6
+ //
7
+ // It shares the FloatingActionButton class with the `.fab` speed dial next
8
+ // door and nothing else. The two never collide: `$_fab` is
9
+ // `:is(.fab, .fixed-action-btn)`, and a class selector matches whole class
10
+ // names, so `.fab` does not reach `.fab-menu`. The one name that looks shared
11
+ // is `--md-comp-fab-menu-gap`, which is the speed dial's own gap token and is
12
+ // deliberately not one of the names below.
13
+ //
14
+ // Tokens (M3 md.comp.fab-menu.*, DSP 34.0.21):
15
+ //
16
+ // close-button 56x56, icon 20dp, corner.full, elevation 3, 8dp above list
17
+ // menu-item 56dp tall, icon 24dp, 24dp insets, 8dp icon-label space,
18
+ // 4dp between items, corner.full, elevation 0, title-medium
19
+ //
20
+ // The colour axis is M3's three roles, and each one moves two pairs at once:
21
+ // the list item takes `<role>-container` / `on-<role>-container`, the close
22
+ // button the solid `<role>` / `on-<role>`. That pairing is M3's - the close
23
+ // button is deliberately the louder of the two.
24
+ $_fab-menu-roles: ("primary", "secondary", "tertiary");
25
+
26
+ .fab-menu {
27
+ --md-comp-fab-menu-close-button-container-height: 56px;
28
+ --md-comp-fab-menu-close-button-container-width: 56px;
29
+ --md-comp-fab-menu-close-button-icon-size: 20px;
30
+ --md-comp-fab-menu-close-button-between-space: 8px;
31
+ --md-comp-fab-menu-close-button-container-color: var(--md-sys-color-primary);
32
+ --md-comp-fab-menu-close-button-icon-color: var(--md-sys-color-on-primary);
33
+ --md-comp-fab-menu-menu-item-container-height: 56px;
34
+ --md-comp-fab-menu-menu-item-between-space: 4px;
35
+ --md-comp-fab-menu-menu-item-icon-size: 24px;
36
+ --md-comp-fab-menu-menu-item-icon-label-space: 8px;
37
+ --md-comp-fab-menu-menu-item-leading-space: 24px;
38
+ --md-comp-fab-menu-menu-item-trailing-space: 24px;
39
+ --md-comp-fab-menu-menu-item-container-color: var(
40
+ --md-sys-color-primary-container
41
+ );
42
+ --md-comp-fab-menu-menu-item-label-text-color: var(
43
+ --md-sys-color-on-primary-container
44
+ );
45
+ --md-comp-fab-menu-offset: 16px;
46
+ --md-comp-fab-menu-z: 997;
47
+ --md-comp-fab-menu-duration: 200ms;
48
+ --md-comp-fab-menu-stagger: 40ms;
49
+ --md-comp-fab-menu-easing: cubic-bezier(0.65, 0, 0.35, 1);
50
+
51
+ position: fixed;
52
+ inset-inline-end: var(--md-comp-fab-menu-offset);
53
+ bottom: var(--md-comp-fab-menu-offset);
54
+ z-index: var(--md-comp-fab-menu-z);
55
+ margin: 0;
56
+
57
+ // The trigger is the host's only in-flow child, so the host is exactly its
58
+ // size and the list can hang off `bottom: 100%`. Taking the list out of
59
+ // flow is what keeps a closed menu from reserving its own height.
60
+ > :is(a, button) {
61
+ position: relative;
62
+ z-index: 1;
63
+
64
+ // A second glyph in the trigger is the close button's, and only one of
65
+ // the two is ever in the box. With one glyph the morph is the whole
66
+ // affordance. `inline-block` is restated rather than reverted: the icon's
67
+ // own rule is in this layer too, so `revert-layer` would skip past it to
68
+ // the base default.
69
+ > #{$icon}:nth-child(2) {
70
+ display: none;
71
+ }
72
+ }
73
+
74
+ :is(ul, menu) {
75
+ position: absolute;
76
+ inset-inline-end: 0;
77
+ bottom: calc(100% + var(--md-comp-fab-menu-close-button-between-space));
78
+ display: flex;
79
+ flex-direction: column;
80
+ align-items: flex-end;
81
+ gap: var(--md-comp-fab-menu-menu-item-between-space);
82
+ margin: 0;
83
+ padding: 0;
84
+ list-style: none;
85
+ visibility: hidden;
86
+ transition: visibility 0s var(--md-comp-fab-menu-duration);
87
+
88
+ // Opening runs bottom-up, so the item nearest the FAB moves first. Past
89
+ // six the ladder runs out and the rest move together, which is where M3
90
+ // stops recommending actions anyway.
91
+ li:nth-last-child(1) { --i: 0; }
92
+ li:nth-last-child(2) { --i: 1; }
93
+ li:nth-last-child(3) { --i: 2; }
94
+ li:nth-last-child(4) { --i: 3; }
95
+ li:nth-last-child(5) { --i: 4; }
96
+ li:nth-last-child(6) { --i: 5; }
97
+
98
+ > li > :is(a, button) {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ gap: var(--md-comp-fab-menu-menu-item-icon-label-space);
102
+ height: var(--md-comp-fab-menu-menu-item-container-height);
103
+ padding-inline: var(--md-comp-fab-menu-menu-item-leading-space)
104
+ var(--md-comp-fab-menu-menu-item-trailing-space);
105
+ border: none;
106
+ border-radius: 9999px;
107
+ color: var(--md-comp-fab-menu-menu-item-label-text-color);
108
+ background-color: var(--md-comp-fab-menu-menu-item-container-color);
109
+ white-space: nowrap;
110
+ text-decoration: none;
111
+ cursor: pointer;
112
+ appearance: none;
113
+ opacity: 0;
114
+ transform: scale(.8) translateY(1rem);
115
+ transition:
116
+ opacity var(--md-comp-fab-menu-duration) var(--md-comp-fab-menu-easing),
117
+ transform var(--md-comp-fab-menu-duration) var(--md-comp-fab-menu-easing),
118
+ background-color .2s ease-out,
119
+ box-shadow .2s ease-out;
120
+ @include typescale("title-medium");
121
+ @include focus-visible;
122
+
123
+ > #{$icon} {
124
+ font-size: var(--md-comp-fab-menu-menu-item-icon-size);
125
+ width: var(--md-comp-fab-menu-menu-item-icon-size);
126
+ height: var(--md-comp-fab-menu-menu-item-icon-size);
127
+ line-height: var(--md-comp-fab-menu-menu-item-icon-size);
128
+ color: inherit;
129
+ }
130
+
131
+ // Enabled is elevation 0; only the states lift.
132
+ &:hover {
133
+ @include z-depth("4");
134
+ background-color: color-mix(
135
+ in oklab,
136
+ var(--md-comp-fab-menu-menu-item-label-text-color) state-opacity("hover"),
137
+ var(--md-comp-fab-menu-menu-item-container-color)
138
+ );
139
+ }
140
+
141
+ &:active,
142
+ &:focus {
143
+ @include z-depth("3");
144
+ background-color: color-mix(
145
+ in oklab,
146
+ var(--md-comp-fab-menu-menu-item-label-text-color) state-opacity("focus"),
147
+ var(--md-comp-fab-menu-menu-item-container-color)
148
+ );
149
+ }
150
+ }
151
+ }
152
+
153
+ &.active :is(ul, menu) {
154
+ visibility: visible;
155
+ transition-delay: 0s;
156
+
157
+ > li > :is(a, button) {
158
+ opacity: 1;
159
+ transform: none;
160
+ transition-delay: calc(var(--md-comp-fab-menu-stagger) * var(--i, 0));
161
+ }
162
+ }
163
+
164
+ // Open, the FAB is the close button: the same disc on corner.full, the
165
+ // solid role colour, and a 20dp glyph. Four of those are the FAB's own
166
+ // tokens, so retuning them is the whole morph - `.fab-menu.active` outweighs
167
+ // `:where(.button).extra.circle`, which contributes only `.extra.circle`.
168
+ &.active > :is(a, button) {
169
+ --md-comp-fab-container-height: var(
170
+ --md-comp-fab-menu-close-button-container-height
171
+ );
172
+ --md-comp-fab-container-width: var(
173
+ --md-comp-fab-menu-close-button-container-width
174
+ );
175
+ --md-comp-fab-container-shape: 9999px;
176
+ --md-comp-fab-icon-size: var(--md-comp-fab-menu-close-button-icon-size);
177
+
178
+ color: var(--md-comp-fab-menu-close-button-icon-color);
179
+ background-color: var(--md-comp-fab-menu-close-button-container-color);
180
+
181
+ > #{$icon}:first-child:not(:only-child) {
182
+ display: none;
183
+ }
184
+
185
+ > #{$icon}:nth-child(2) {
186
+ display: inline-block;
187
+ }
188
+
189
+ &:hover {
190
+ background-color: color-mix(
191
+ in oklab,
192
+ var(--md-comp-fab-menu-close-button-icon-color) state-opacity("hover"),
193
+ var(--md-comp-fab-menu-close-button-container-color)
194
+ );
195
+ }
196
+
197
+ &:active,
198
+ &:focus {
199
+ background-color: color-mix(
200
+ in oklab,
201
+ var(--md-comp-fab-menu-close-button-icon-color) state-opacity("focus"),
202
+ var(--md-comp-fab-menu-close-button-container-color)
203
+ );
204
+ }
205
+ }
206
+
207
+ // `.primary-container` is the default and is named anyway, so all three
208
+ // roles are a class a page can be written with.
209
+ @each $role in $_fab-menu-roles {
210
+ &.#{$role}-container {
211
+ --md-comp-fab-menu-menu-item-container-color: var(
212
+ --md-sys-color-#{$role}-container
213
+ );
214
+ --md-comp-fab-menu-menu-item-label-text-color: var(
215
+ --md-sys-color-on-#{$role}-container
216
+ );
217
+ --md-comp-fab-menu-close-button-container-color: var(
218
+ --md-sys-color-#{$role}
219
+ );
220
+ --md-comp-fab-menu-close-button-icon-color: var(
221
+ --md-sys-color-on-#{$role}
222
+ );
223
+ }
224
+ }
225
+ }
226
+
227
+ @media (prefers-reduced-motion: reduce) {
228
+ .fab-menu {
229
+ --md-comp-fab-menu-duration: 0s;
230
+ --md-comp-fab-menu-stagger: 0s;
231
+ }
232
+ }
@@ -0,0 +1,86 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // Floating sheets.
4
+ //
5
+ // The third member of M3's sheet family (`md.comp.sheet.floating`). A
6
+ // dialog.floating-sheet is the surface, detached from every window edge
7
+ // instead of docked to one. showModal() is the modal variant (scrim,
8
+ // page inert); show() is the standard variant (no scrim, the page stays
9
+ // interactive and the sheet floats above it).
10
+ //
11
+ // It is a <dialog>, so every dialog slot already applies - heading, body,
12
+ // optional <hr> and extra content, and a last-child form[method=dialog]
13
+ // as the action row. Only the container differs, and the sheet states it
14
+ // by re-pointing the basic-dialog container tokens at its own.
15
+ //
16
+ // The token set is small on purpose: md.comp.sheet.floating publishes
17
+ // exactly three values (DSP 34.0.21) and nothing about placement.
18
+ // container surface-container-low, 28dp corners, elevation 1
19
+ // inset 24dp from every edge; the sheet centres in what is left
20
+ //
21
+ // There are deliberately no edge modifiers. `.bottom` and `.left` /
22
+ // `.right` already select a bottom or side sheet, so a floating sheet
23
+ // that borrowed those names would pick up two sheets' rules at once.
24
+ // Author placement with `inset` / `margin` when a corner is wanted.
25
+
26
+ dialog.floating-sheet {
27
+ --md-comp-floating-sheet-container-color: var(--md-sys-color-surface-container-low);
28
+ --md-comp-floating-sheet-container-shape: 28px;
29
+ --md-comp-floating-sheet-container-max-width: 400px;
30
+ --md-comp-floating-sheet-inset: 24px;
31
+
32
+ // The surface is the dialog's; the sheet only swaps what it owns.
33
+ --md-comp-basic-dialog-container-color: var(--md-comp-floating-sheet-container-color);
34
+ --md-comp-basic-dialog-container-shape: var(--md-comp-floating-sheet-container-shape);
35
+
36
+ box-sizing: border-box;
37
+ position: fixed;
38
+ inset: var(--md-comp-floating-sheet-inset);
39
+ width: min(
40
+ var(--md-comp-floating-sheet-container-max-width),
41
+ calc(100% - 2 * var(--md-comp-floating-sheet-inset))
42
+ );
43
+ min-width: 0;
44
+ max-width: var(--md-comp-floating-sheet-container-max-width);
45
+ // `inset` sets both block edges, and an `auto` height between two fixed
46
+ // edges is *solved*, not shrink-wrapped - the sheet would fill the window
47
+ // whatever it holds. `fit-content` makes all three non-auto, which is the
48
+ // case where auto margins centre instead.
49
+ height: fit-content;
50
+ // A percentage max-height resolves against the containing block, which is
51
+ // the whole viewport - not the box `inset` leaves. At 100% a tall sheet
52
+ // grew to viewport height, went over-constrained, and auto margins split
53
+ // the overflow as -24px top and bottom: the inset disappeared exactly when
54
+ // the sheet was large enough to need it.
55
+ max-height: calc(100% - 2 * var(--md-comp-floating-sheet-inset));
56
+ margin: auto;
57
+ @include z-depth("1");
58
+ opacity: 1;
59
+ scale: 1;
60
+ transition: opacity 200ms ease, scale 200ms ease;
61
+
62
+ // Non-modal `show()` is not in the top layer - sit above page content
63
+ // and below snackbars (z-index 10000).
64
+ &:not(:modal) {
65
+ z-index: 1100;
66
+ }
67
+
68
+ // Standard: no scrim. Modal keeps the basic-dialog backdrop.
69
+ &:not(:modal)::backdrop {
70
+ background: none;
71
+ backdrop-filter: none;
72
+ }
73
+ }
74
+
75
+ @starting-style {
76
+ dialog.floating-sheet[open] {
77
+ opacity: 0;
78
+ scale: 0.9;
79
+ }
80
+ }
81
+
82
+ @media (prefers-reduced-motion: reduce) {
83
+ dialog.floating-sheet {
84
+ transition: none;
85
+ }
86
+ }
@@ -0,0 +1,172 @@
1
+ @use "sass:map";
2
+ @use "../abstracts" as *;
3
+
4
+ // Icon buttons.
5
+ //
6
+ // Its own component, not a modifier on the common button: M3 gives it nine
7
+ // token families of its own — md.comp.icon-button.{xsmall…xlarge} for the size
8
+ // ladder and md.comp.icon-button-{standard,filled,tonal,outlined} for the
9
+ // colours — and neither the sizes nor the colours line up with a common
10
+ // button's. `.icon-button` is listed in `$_not-btn` in `_buttons.scss` for
11
+ // exactly that reason, so none of the common-button geometry reaches it.
12
+ //
13
+ // The 40dp `.button.circle` icon button is untouched. It stays what it always
14
+ // was: a common button wearing a round shape.
15
+ //
16
+ // Hosts step aside for it. A component that styles its own child controls
17
+ // already lets a control that styles itself opt out - that is what `.button` is
18
+ // doing in those `:not()` lists - and `.icon-button` is now in every one of
19
+ // them (toolbar, app bar, list row, dialog, snackbar, rail, panes, footer,
20
+ // side sheet). `tests/icon-buttons.test.js` keeps the lists in step; the member
21
+ // goes inside the existing `:not()` argument list, never chained after it, so
22
+ // the host rule stays exactly as specific as it was.
23
+ //
24
+ // The navigation drawer is the deliberate exception. Its `$_row` geometry has
25
+ // no opt-out for `.button` either: every row in a drawer is one shape, and
26
+ // carving an icon button out of that is the drawer's decision to make, not
27
+ // this component's.
28
+ //
29
+ // Anatomy is the HTML — one control, one icon, and the name on the control.
30
+ // The icon is decoration (`aria-hidden`), so `aria-label` is the only name the
31
+ // control has; `semantics.json` enforces both halves of that.
32
+ //
33
+ // Tokens (M3 md.comp.icon-button.*, DSP 34.0.21):
34
+ //
35
+ // size height icon space narrow wide square pressed outline
36
+ // xsmall 32 20 6 4 10 12 8 1
37
+ // small 40 24 8 4 14 12 8 1 <- default
38
+ // medium 56 24 16 12 24 16 12 1
39
+ // large 96 32 32 16 48 28 16 2
40
+ // xlarge 136 40 48 32 72 28 16 3
41
+ //
42
+ // Width is not a token: it is leading + icon + trailing. That is why every
43
+ // size's default width equals its height, and why `.narrow` / `.wide` need
44
+ // nothing but a different inset.
45
+ // Standard is the default style: no container, `on-surface-variant` icon. Every
46
+ // other style is two tokens.
47
+ .icon-button {
48
+ @include icon-button-size("small");
49
+
50
+ --md-comp-icon-button-color: var(--md-sys-color-on-surface-variant);
51
+ --md-comp-icon-button-container-color: transparent;
52
+ --md-comp-icon-button-container-shape: 9999px;
53
+
54
+ box-sizing: border-box;
55
+ display: inline-flex;
56
+ flex: 0 0 auto;
57
+ align-items: center;
58
+ justify-content: center;
59
+ height: var(--md-comp-icon-button-container-height);
60
+ padding-block: 0;
61
+ padding-inline: var(--md-comp-icon-button-leading-space)
62
+ var(--md-comp-icon-button-trailing-space);
63
+ border: none;
64
+ border-radius: var(--md-comp-icon-button-container-shape);
65
+ color: var(--md-comp-icon-button-color);
66
+ background-color: var(--md-comp-icon-button-container-color);
67
+ text-decoration: none;
68
+ cursor: pointer;
69
+ outline: 0;
70
+ user-select: none;
71
+ appearance: none;
72
+ vertical-align: middle;
73
+ -webkit-tap-highlight-color: transparent;
74
+ transition: background-color .2s ease-out, border-radius .2s ease-out;
75
+
76
+ > #{$icon} {
77
+ flex: 0 0 auto;
78
+ font-size: var(--md-comp-icon-button-icon-size);
79
+ width: var(--md-comp-icon-button-icon-size);
80
+ height: var(--md-comp-icon-button-icon-size);
81
+ line-height: var(--md-comp-icon-button-icon-size);
82
+ color: inherit;
83
+ overflow: hidden;
84
+ }
85
+
86
+ // The state layer is the icon colour mixed into the container, whatever the
87
+ // style set those two to - `transparent` included, which is how a standard
88
+ // icon button gets a translucent tint instead of an opaque one.
89
+ @each $state, $selector in ("hover": "&:hover", "focus": "&:focus", "pressed": "&:active") {
90
+ #{$selector} {
91
+ background-color: color-mix(
92
+ in oklab,
93
+ var(--md-comp-icon-button-color) state-opacity($state),
94
+ var(--md-comp-icon-button-container-color)
95
+ );
96
+ }
97
+ }
98
+
99
+ @include focus-visible;
100
+ }
101
+
102
+ // Styles. Each is the icon colour and the container it sits on; the state
103
+ // layers above follow from those two.
104
+ .icon-button.filled {
105
+ --md-comp-icon-button-color: var(--md-sys-color-on-primary);
106
+ --md-comp-icon-button-container-color: var(--md-sys-color-primary);
107
+ }
108
+
109
+ .icon-button.tonal {
110
+ --md-comp-icon-button-color: var(--md-sys-color-on-secondary-container);
111
+ --md-comp-icon-button-container-color: var(--md-sys-color-secondary-container);
112
+ }
113
+
114
+ // The outline is a border rather than an inset box-shadow so it survives
115
+ // forced-colors mode, and the inset gives the width back — otherwise a 3dp
116
+ // xlarge outline makes a round button 6px wider than it is tall.
117
+ .icon-button.outlined {
118
+ border: var(--md-comp-icon-button-outline-width) solid var(--md-sys-color-outline-variant);
119
+ padding-inline: calc(var(--md-comp-icon-button-leading-space) - var(--md-comp-icon-button-outline-width))
120
+ calc(var(--md-comp-icon-button-trailing-space) - var(--md-comp-icon-button-outline-width));
121
+ }
122
+
123
+ // Sizes. Default is small (40dp), which is M3's default icon button.
124
+ @each $name in ("xsmall", "medium", "large", "xlarge") {
125
+ .icon-button.#{$name} {
126
+ @include icon-button-size($name);
127
+ }
128
+ }
129
+
130
+ // Width axis. Narrow and wide only move the inset, so they compose with every
131
+ // size and with the outlined padding above.
132
+ .icon-button.narrow {
133
+ --md-comp-icon-button-leading-space: var(--md-comp-icon-button-narrow-space);
134
+ --md-comp-icon-button-trailing-space: var(--md-comp-icon-button-narrow-space);
135
+ }
136
+
137
+ .icon-button.wide {
138
+ --md-comp-icon-button-leading-space: var(--md-comp-icon-button-wide-space);
139
+ --md-comp-icon-button-trailing-space: var(--md-comp-icon-button-wide-space);
140
+ }
141
+
142
+ // Shape. Round by default, `.square` for the squared container. The pressed
143
+ // shape is the M3 Expressive corner morph, and it has to be written after
144
+ // `.square`: both are one class plus the root, so a tie is decided by order,
145
+ // and a squared button pressed would otherwise keep its resting corner.
146
+ .icon-button.square {
147
+ --md-comp-icon-button-container-shape: var(--md-comp-icon-button-container-shape-square);
148
+ }
149
+
150
+ .icon-button:active {
151
+ border-radius: var(--md-comp-icon-button-pressed-container-shape);
152
+ }
153
+
154
+ // Disabled. The icon fades to 38% on-surface; a container that exists fades to
155
+ // 10%. Standard and outlined have none, so theirs stays transparent.
156
+ //
157
+ // `aria-disabled="true"` is in the list because an <a> cannot take `disabled`,
158
+ // and it is the attribute that actually tells a screen reader. Note what CSS
159
+ // cannot do for a link: `pointer-events: none` stops the pointer and nothing
160
+ // else, so a disabled-looking <a href> is still reachable by Tab and still
161
+ // navigates on Enter. Drop the `href` (or the link) rather than painting a
162
+ // control that lies about being inert - the docs page says so too.
163
+ .icon-button:is(:disabled, [disabled], [aria-disabled="true"], .disabled) {
164
+ --md-comp-icon-button-color: color-mix(in oklab, var(--md-sys-color-on-surface) 38%, transparent);
165
+
166
+ pointer-events: none;
167
+ cursor: default;
168
+ }
169
+
170
+ .icon-button:is(.filled, .tonal):is(:disabled, [disabled], [aria-disabled="true"], .disabled) {
171
+ --md-comp-icon-button-container-color: color-mix(in oklab, var(--md-sys-color-on-surface) 10%, transparent);
172
+ }
@@ -1,18 +1,18 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
- // Material Symbols. The framework does not ship the font files — outlined,
4
- // rounded, and sharp are three separate variable fonts, and pulling all
5
- // three into the main sheet would dwarf the rest of the CSS. Load the
6
- // families you use (see the Icons docs). Outlined with every axis:
7
- //
8
- // fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200
3
+ // Material Symbols. The compiled sheet ships @font-face rules for outlined,
4
+ // rounded, and sharp variable fonts (opsz / wght / FILL / GRAD) plus Roboto
5
+ // and Noto Sans. Browsers only download a family after markup uses it, so
6
+ // rounded and sharp stay cheap until `icon-style` or a family class asks
7
+ // for them. Override $expressive-include-fonts when compiling Sass if the
8
+ // page already loads these families.
9
9
  //
10
10
  // Style (outlined / rounded / sharp) is the font family, not a variation
11
11
  // axis. Switch it with the `icon-style` attribute (same idea as `theme`)
12
12
  // or by setting --md-icon-font. Fill, weight, grade, and optical size
13
13
  // are axes and inherit as custom properties.
14
14
 
15
- :root {
15
+ :root, :host {
16
16
  --md-icon-font-outlined: "Material Symbols Outlined";
17
17
  --md-icon-font-rounded: "Material Symbols Rounded";
18
18
  --md-icon-font-sharp: "Material Symbols Sharp";
@@ -23,9 +23,12 @@
23
23
  --md-icon-optical-size: 24;
24
24
  }
25
25
 
26
- [icon-style="outlined"] { --md-icon-font: var(--md-icon-font-outlined); }
27
- [icon-style="rounded"] { --md-icon-font: var(--md-icon-font-rounded); }
28
- [icon-style="sharp"] { --md-icon-font: var(--md-icon-font-sharp); }
26
+ [icon-style="outlined"],
27
+ :host([icon-style="outlined"]) { --md-icon-font: var(--md-icon-font-outlined); }
28
+ [icon-style="rounded"],
29
+ :host([icon-style="rounded"]) { --md-icon-font: var(--md-icon-font-rounded); }
30
+ [icon-style="sharp"],
31
+ :host([icon-style="sharp"]) { --md-icon-font: var(--md-icon-font-sharp); }
29
32
 
30
33
  $_icon: ".material-symbols, .material-symbols-outlined, .material-symbols-rounded, .material-symbols-sharp, .material-icons";
31
34
 
@@ -54,6 +57,15 @@ $_icon: ".material-symbols, .material-symbols-outlined, .material-symbols-rounde
54
57
  // Nest so each class in $_icon gets the suffix. `#{$_icon}.tiny` would
55
58
  // only attach `.tiny` to the last item and leave every other icon
56
59
  // matching the bare `.material-symbols` selector — including `.large`.
60
+ // Float helpers, moved off the `i` element selector in base/_global so they
61
+ // survive the canonical <span class="material-symbols"> form.
62
+ &.left {
63
+ float: left;
64
+ margin-left: -8px;
65
+ }
66
+ &.right {
67
+ float: right;
68
+ }
57
69
  &.tiny {
58
70
  font-size: 1rem;
59
71
  --md-icon-optical-size: 20;
@@ -1,10 +1,19 @@
1
1
  // One line per component. Order is cascade order, so keep related widgets
2
2
  // together and append new components at the end unless order matters.
3
3
 
4
+ // Icons first: it is the base glyph default (font-size, line-height) that a
5
+ // component's own icon rule is meant to override. `$icon` interpolates to an
6
+ // :is() carrying classes, so a component rule ties with the base rule on
7
+ // specificity and the winner is decided by source order alone - with icons
8
+ // loaded 4th, `buttons`, `list` and `badges` silently lost that tie. That is
9
+ // why a button icon rendered as an 18px box around a 24px glyph.
10
+ @forward "icons-material-design";
4
11
  @forward "buttons";
12
+ @forward "button-groups";
13
+ @forward "split-button";
14
+ @forward "icon-buttons";
5
15
  @forward "list";
6
16
  @forward "badges";
7
- @forward "icons-material-design";
8
17
  @forward "navbar";
9
18
  @forward "navigation-bar";
10
19
  @forward "navigation-rail";
@@ -16,23 +25,30 @@
16
25
  @forward "tooltip";
17
26
  @forward "menu";
18
27
  @forward "dialog";
28
+ @forward "expanding-card";
19
29
  @forward "bottom-sheet";
20
30
  @forward "side-sheet";
31
+ @forward "floating-sheet";
21
32
  @forward "chips";
22
33
  @forward "lightbox";
23
- @forward "parallax";
24
34
  @forward "docked-display";
25
35
  @forward "page-footer";
26
36
  @forward "forms";
27
37
  @forward "table_of_contents";
28
38
  @forward "scrollspy";
29
- @forward "sidenav";
30
- @forward "preloader";
31
- @forward "slider";
39
+ @forward "navigation-drawer";
40
+ @forward "progress";
32
41
  @forward "carousel";
33
- @forward "pulse";
34
42
  @forward "datepicker";
35
43
  @forward "timepicker";
36
44
  @forward "breadcrumb";
37
45
  @forward "pagination";
38
46
  @forward "panes";
47
+ @forward "fab-menu";
48
+ @forward "loading-indicator";
49
+ @forward "search";
50
+ @forward "segmented-buttons";
51
+ @forward "banners";
52
+ @forward "drag-handle";
53
+ @forward "bottom-app-bar";
54
+ @forward "scrim";