mtrl 0.7.0 → 0.8.0-next.4

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 (48) hide show
  1. package/dist/components/button/api.d.ts +4 -0
  2. package/dist/components/button/features/toggle.d.ts +42 -0
  3. package/dist/components/button/types.d.ts +30 -0
  4. package/dist/components/button-group/button-group.d.ts +6 -4
  5. package/dist/components/button-group/config.d.ts +15 -7
  6. package/dist/components/button-group/constants.d.ts +85 -17
  7. package/dist/components/button-group/index.d.ts +1 -1
  8. package/dist/components/button-group/types.d.ts +68 -4
  9. package/dist/components/carousel/api.d.ts +11 -30
  10. package/dist/components/carousel/arrangement.d.ts +23 -0
  11. package/dist/components/carousel/carousel.d.ts +13 -63
  12. package/dist/components/carousel/config.d.ts +2 -31
  13. package/dist/components/carousel/constants.d.ts +25 -151
  14. package/dist/components/carousel/features/index.d.ts +2 -5
  15. package/dist/components/carousel/features/scroll.d.ts +19 -0
  16. package/dist/components/carousel/features/slides.d.ts +18 -8
  17. package/dist/components/carousel/index.d.ts +3 -37
  18. package/dist/components/carousel/keylines.d.ts +65 -0
  19. package/dist/components/carousel/strategy.d.ts +36 -0
  20. package/dist/components/carousel/types.d.ts +36 -242
  21. package/dist/components/dialog/api.d.ts +1 -1
  22. package/dist/components/index.d.ts +2 -0
  23. package/dist/components/slider/constants.d.ts +8 -8
  24. package/dist/components/slider/types.d.ts +4 -1
  25. package/dist/components/snackbar/api.d.ts +1 -1
  26. package/dist/components/snackbar/config.d.ts +2 -1
  27. package/dist/components/snackbar/constants.d.ts +18 -0
  28. package/dist/components/snackbar/index.d.ts +2 -1
  29. package/dist/components/snackbar/queue.d.ts +10 -2
  30. package/dist/components/snackbar/types.d.ts +37 -1
  31. package/dist/index.cjs +20 -10
  32. package/dist/index.cjs.map +54 -38
  33. package/dist/index.js +20 -10
  34. package/dist/index.js.map +54 -38
  35. package/dist/package.json +1 -1
  36. package/dist/styles.css +2 -2
  37. package/package.json +3 -6
  38. package/src/styles/abstract/_theme.scss +4 -2
  39. package/src/styles/abstract/_variables.scss +59 -12
  40. package/src/styles/components/_button-group.scss +122 -349
  41. package/src/styles/components/_button.scss +333 -591
  42. package/src/styles/components/_carousel.scss +131 -611
  43. package/src/styles/components/_icon-button.scss +23 -23
  44. package/src/styles/components/_select.scss +9 -1
  45. package/src/styles/components/_slider.scss +10 -9
  46. package/src/styles/themes/_index.scss +1 -0
  47. package/src/styles/themes/_legacy.scss +73 -0
  48. package/dist/components/carousel/features/drag.d.ts +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtrl",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-next.4",
4
4
  "description": "A functional TypeScript/JavaScript component library with composable architecture based on Material Design 3",
5
5
  "author": "floor",
6
6
  "license": "MIT License",
@@ -60,11 +60,8 @@
60
60
  "scripts": {
61
61
  "start": "bun run server.js",
62
62
  "dev": "bun --watch server.js",
63
- "build:js": "bun build demo/main.js --outfile=demo/dist/bundle.js --format=esm",
64
- "build:css": "sass src/styles/main.scss:demo/dist/styles.css --style=compressed",
65
- "build": "bun run build:js && bun run build:css",
66
- "build:lib": "bun run build.ts",
67
- "prepublishOnly": "bun run build:lib",
63
+ "build": "bun run build.ts",
64
+ "prepublishOnly": "bun run build",
68
65
  "lint": "bunx eslint src --ext .ts,.tsx",
69
66
  "lint:fix": "bunx eslint src --ext .ts,.tsx --fix",
70
67
  "format": "bunx prettier --write .",
@@ -56,8 +56,10 @@ $mtrl-sys-shape: (
56
56
  * @return {String} rgba CSS function with variable
57
57
  */
58
58
  @function alpha($key, $opacity) {
59
- // #{'...'} prevents SASS from interpreting rgba as a SASS function
60
- @return #{'rgba(var(--#{$prefix}-sys-color-#{$key}-rgb), #{$opacity})'};
59
+ // color-mix on the role itself: themes made with create-theme define the
60
+ // colour tokens only (no -rgb twins), so an rgba(var(--…-rgb)) would fall
61
+ // back to the baseline palette. #{'...'} keeps Sass from evaluating it.
62
+ @return #{'color-mix(in srgb, var(--#{$prefix}-sys-color-#{$key}) #{$opacity * 100}%, transparent)'};
61
63
  }
62
64
 
63
65
  @function state($key) {
@@ -5,8 +5,8 @@
5
5
  // Core design tokens
6
6
  $state: (
7
7
  'hover-state-layer-opacity': 0.08,
8
- 'focus-state-layer-opacity': 0.12,
9
- 'pressed-state-layer-opacity': 0.12,
8
+ 'focus-state-layer-opacity': 0.1,
9
+ 'pressed-state-layer-opacity': 0.1,
10
10
  'dragged-state-layer-opacity': 0.16
11
11
  ) !default;
12
12
 
@@ -41,7 +41,25 @@ $motion: (
41
41
  'easing-appear': cubic-bezier(0.0, 0.0, 0.2, 1.0), // Elements appearing (decelerated)
42
42
  'easing-disappear': cubic-bezier(0.3, 0.0, 1.0, 1.0), // Elements disappearing (accelerated)
43
43
  'easing-expand': cubic-bezier(0.0, 0.0, 0.2, 1.0), // Elements expanding (decelerated)
44
- 'easing-collapse': cubic-bezier(0.3, 0.0, 1.0, 1.0) // Elements collapsing (accelerated)
44
+ 'easing-collapse': cubic-bezier(0.3, 0.0, 1.0, 1.0), // Elements collapsing (accelerated)
45
+
46
+ // Material 3 expressive motion scheme: springs sampled as linear() easings
47
+ // (Compose ExpressiveMotionTokens.kt, mass 1). Spatial springs move size,
48
+ // shape and position and may overshoot; effects springs change colour and
49
+ // opacity without overshoot. The duration is the time for the spring to
50
+ // settle within 0.1%.
51
+ 'spring-default-spatial-duration': 450ms,
52
+ 'spring-default-spatial-easing': linear(0.0, 0.055, 0.18, 0.332, 0.483, 0.619, 0.733, 0.823, 0.891, 0.94, 0.973, 0.994, 1.007, 1.013, 1.015, 1.015, 1.013, 1.011, 1.009, 1.006, 1.005, 1.003, 1.002, 1.001, 1),
53
+ 'spring-fast-spatial-duration': 425ms,
54
+ 'spring-fast-spatial-easing': linear(0.0, 0.102, 0.323, 0.57, 0.784, 0.942, 1.039, 1.085, 1.094, 1.082, 1.06, 1.037, 1.018, 1.004, 0.995, 0.992, 0.991, 0.992, 0.995, 0.997, 0.999, 1.0, 1.001, 1.001, 1),
55
+ 'spring-slow-spatial-duration': 625ms,
56
+ 'spring-slow-spatial-easing': linear(0.0, 0.056, 0.182, 0.335, 0.488, 0.624, 0.738, 0.827, 0.895, 0.943, 0.975, 0.996, 1.007, 1.013, 1.015, 1.015, 1.013, 1.011, 1.008, 1.006, 1.004, 1.003, 1.002, 1.001, 1),
57
+ 'spring-default-effects-duration': 250ms,
58
+ 'spring-default-effects-easing': linear(0.0, 0.066, 0.203, 0.355, 0.496, 0.616, 0.713, 0.788, 0.845, 0.888, 0.92, 0.943, 0.96, 0.971, 0.98, 0.986, 0.99, 0.993, 0.995, 0.997, 0.998, 0.998, 0.999, 0.999, 1),
59
+ 'spring-fast-effects-duration': 175ms,
60
+ 'spring-fast-effects-easing': linear(0.0, 0.075, 0.227, 0.39, 0.537, 0.657, 0.751, 0.822, 0.874, 0.912, 0.939, 0.958, 0.971, 0.98, 0.987, 0.991, 0.994, 0.996, 0.997, 0.998, 0.999, 0.999, 0.999, 1.0, 1),
61
+ 'spring-slow-effects-duration': 350ms,
62
+ 'spring-slow-effects-easing': linear(0.0, 0.065, 0.2, 0.351, 0.491, 0.611, 0.708, 0.783, 0.841, 0.885, 0.917, 0.941, 0.958, 0.97, 0.979, 0.985, 0.99, 0.993, 0.995, 0.997, 0.998, 0.998, 0.999, 0.999, 1),
45
63
  ) !default;
46
64
 
47
65
  $elevation: (
@@ -221,30 +239,59 @@ $chip-config: (
221
239
  ) !default;
222
240
 
223
241
  $button: (
242
+ // M3 button tokens (Compose Material 3 ButtonXSmall/Small/Medium/Large/XLargeTokens.kt)
224
243
  'height': 40px,
225
- 'min-width': 64px,
226
- 'padding-horizontal': 24px,
227
- 'padding-horizontal-small': 16px,
228
- 'padding-icon': 16px,
229
- 'border-radius': 20px,
244
+ 'min-width': 58px, // ButtonDefaults.MinWidth
245
+ 'padding-horizontal': 16px,
246
+ 'padding-horizontal-small': 12px, // text button (ButtonDefaults.TextButtonContentPadding)
247
+ 'padding-icon': 12px, // small button leading space next to an icon (pointer devices)
248
+ 'border-radius': map.get($shape, 'full'),
230
249
  'transition-duration': map.get($motion, 'duration-short2'),
231
250
  'transition-easing': map.get($motion, 'easing-standard'),
232
- // Size-specific configurations based on MD3 specs
251
+ // Container height per size
233
252
  'height-xs': 32px,
234
253
  'height-s': 40px,
235
254
  'height-m': 56px,
236
255
  'height-l': 96px,
237
256
  'height-xl': 136px,
257
+ // Leading and trailing space per size
238
258
  'padding-xs': 12px,
239
259
  'padding-s': 16px,
240
260
  'padding-m': 24px,
241
261
  'padding-l': 48px,
242
262
  'padding-xl': 64px,
243
- 'icon-size-xs': 18px,
244
- 'icon-size-s': 22px,
263
+ // Icon size per size
264
+ 'icon-size-xs': 20px,
265
+ 'icon-size-s': 20px,
245
266
  'icon-size-m': 24px,
246
267
  'icon-size-l': 32px,
247
- 'icon-size-xl': 48px
268
+ 'icon-size-xl': 40px,
269
+ // Icon to label space per size
270
+ 'icon-gap-xs': 8px,
271
+ 'icon-gap-s': 8px,
272
+ 'icon-gap-m': 8px,
273
+ 'icon-gap-l': 12px,
274
+ 'icon-gap-xl': 16px,
275
+ // Outlined outline width per size
276
+ 'outline-width-xs': 1px,
277
+ 'outline-width-s': 1px,
278
+ 'outline-width-m': 1px,
279
+ 'outline-width-l': 2px,
280
+ 'outline-width-xl': 3px,
281
+ // Square container shape per size
282
+ 'shape-square-xs': map.get($shape, 'medium'),
283
+ 'shape-square-s': map.get($shape, 'medium'),
284
+ 'shape-square-m': map.get($shape, 'large'),
285
+ 'shape-square-l': map.get($shape, 'extra-large'),
286
+ 'shape-square-xl': map.get($shape, 'extra-large'),
287
+ // Pressed container shape per size (round and square)
288
+ 'shape-pressed-xs': map.get($shape, 'small'),
289
+ 'shape-pressed-s': map.get($shape, 'small'),
290
+ 'shape-pressed-m': map.get($shape, 'medium'),
291
+ 'shape-pressed-l': map.get($shape, 'large'),
292
+ 'shape-pressed-xl': map.get($shape, 'large'),
293
+ // Minimum touch target for the xs and s sizes
294
+ 'touch-target': 48px
248
295
  ) !default;
249
296
 
250
297
 
@@ -6,403 +6,176 @@
6
6
  @use '../../styles/abstract/theme' as t;
7
7
 
8
8
  $component: '#{base.$prefix}-button-group';
9
+ $button: '#{base.$prefix}-button';
10
+ $icon-button: '#{base.$prefix}-icon-button';
11
+ $shape: '--#{base.$prefix}-button-shape';
9
12
 
10
13
  // =============================================================================
11
14
  // BUTTON GROUP COMPONENT STYLES
12
15
  // =============================================================================
13
- // Material Design 3 Button Group Implementation
16
+ // Material 3 button groups (m3.material.io button group specs; Compose
17
+ // ButtonGroupDefaults, ButtonGroupSmallTokens.kt and
18
+ // ConnectedButtonGroupSmallTokens.kt).
14
19
  //
15
- // Button groups visually connect related action buttons. Unlike segmented
16
- // buttons (for selection), button groups are for grouping related actions
17
- // where each button triggers an independent action.
20
+ // A group is a row (or column) of ordinary buttons and icon buttons. The
21
+ // buttons keep their own colours, states, toggle colours and shape morphs;
22
+ // the group owns the spacing, the container height, and, for connected
23
+ // groups, the corner shapes per position. Shapes are handed to the buttons
24
+ // through the --<prefix>-button-shape custom properties.
18
25
  //
19
- // Supports:
20
- // - Multiple variants (filled, tonal, outlined, elevated, text)
21
- // - Horizontal and vertical orientations
22
- // - Density scaling (default, comfortable, compact)
23
- // - Equal width option
24
- // - Proper border handling between buttons
25
- // - Accessibility support
26
+ // - standard: 18/12/8/8/8dp between buttons by size; a selected toggle
27
+ // swaps round and square like a standalone button; a pressed button
28
+ // widens by 15% (set from the component)
29
+ // - connected: 2dp between buttons; round outer corners, square inner
30
+ // corners (4/8/8/16/20dp by size), 4dp inner corners while pressed; a
31
+ // selected button becomes a full pill
32
+ //
33
+ // The component sets --button-group-height, --button-group-icon,
34
+ // --button-group-gap, --button-group-inner-corner,
35
+ // --button-group-pressed-corner and --button-group-radius.
26
36
  // =============================================================================
27
37
 
28
38
  .#{$component} {
29
- // Base styles
30
39
  position: relative;
31
40
  display: inline-flex;
32
- align-items: stretch;
41
+ flex-direction: row;
42
+ align-items: center;
43
+ gap: var(--button-group-gap, 12px);
33
44
 
34
- // CSS custom properties for dynamic sizing
35
45
  --button-group-height: 40px;
46
+ --button-group-icon: 20px;
47
+ --button-group-gap: 12px;
48
+ --button-group-inner-corner: 8px;
49
+ --button-group-pressed-corner: 4px;
36
50
  --button-group-radius: 20px;
37
- --button-group-gap: 0;
38
51
 
39
- // Default to horizontal orientation
40
- flex-direction: row;
52
+ &--vertical {
53
+ flex-direction: column;
54
+ align-items: stretch;
55
+ }
41
56
 
42
- // Remove default button margins within group
43
- > .#{base.$prefix}-button {
57
+ > .#{$button},
58
+ > .#{$icon-button} {
44
59
  margin: 0;
45
60
  flex-shrink: 0;
61
+ height: var(--button-group-height);
62
+ // Size and shape changes ride the fast spatial spring, colours the
63
+ // default effects spring (ButtonGroup and ToggleButton animation specs)
64
+ transition:
65
+ background-color v.motion('spring-default-effects-duration') v.motion('spring-default-effects-easing'),
66
+ color v.motion('spring-default-effects-duration') v.motion('spring-default-effects-easing'),
67
+ box-shadow v.motion('spring-default-effects-duration') v.motion('spring-default-effects-easing'),
68
+ border-color v.motion('spring-default-effects-duration') v.motion('spring-default-effects-easing'),
69
+ border-radius v.motion('spring-fast-spatial-duration') v.motion('spring-fast-spatial-easing'),
70
+ width v.motion('spring-fast-spatial-duration') v.motion('spring-fast-spatial-easing'),
71
+ padding v.motion('spring-fast-spatial-duration') v.motion('spring-fast-spatial-easing'),
72
+ gap v.motion('spring-fast-spatial-duration') v.motion('spring-fast-spatial-easing');
46
73
  }
47
74
 
48
- // =========================================================================
49
- // ORIENTATION VARIANTS
50
- // =========================================================================
51
-
52
- &--horizontal {
53
- flex-direction: row;
54
-
55
- // First button - rounded left corners only
56
- > .#{$component}__button--first,
57
- > .#{base.$prefix}-button:first-child {
58
- border-top-left-radius: var(--button-group-radius);
59
- border-bottom-left-radius: var(--button-group-radius);
60
- border-top-right-radius: 0;
61
- border-bottom-right-radius: 0;
62
- }
63
-
64
- // Last button - rounded right corners only
65
- > .#{$component}__button--last,
66
- > .#{base.$prefix}-button:last-child {
67
- border-top-right-radius: var(--button-group-radius);
68
- border-bottom-right-radius: var(--button-group-radius);
69
- border-top-left-radius: 0;
70
- border-bottom-left-radius: 0;
71
- }
72
-
73
- // Middle buttons - no rounded corners
74
- > .#{$component}__button--middle,
75
- > .#{base.$prefix}-button:not(:first-child):not(:last-child) {
76
- border-radius: 0;
77
- }
78
-
79
- // Single button - all corners rounded
80
- > .#{$component}__button--single,
81
- > .#{base.$prefix}-button:only-child {
82
- border-radius: var(--button-group-radius);
83
- }
84
- }
85
-
86
- &--vertical {
87
- flex-direction: column;
88
-
89
- // First button - rounded top corners only
90
- > .#{$component}__button--first,
91
- > .#{base.$prefix}-button:first-child {
92
- border-top-left-radius: var(--button-group-radius);
93
- border-top-right-radius: var(--button-group-radius);
94
- border-bottom-left-radius: 0;
95
- border-bottom-right-radius: 0;
96
- }
97
-
98
- // Last button - rounded bottom corners only
99
- > .#{$component}__button--last,
100
- > .#{base.$prefix}-button:last-child {
101
- border-bottom-left-radius: var(--button-group-radius);
102
- border-bottom-right-radius: var(--button-group-radius);
103
- border-top-left-radius: 0;
104
- border-top-right-radius: 0;
105
- }
106
-
107
- // Middle buttons - no rounded corners
108
- > .#{$component}__button--middle,
109
- > .#{base.$prefix}-button:not(:first-child):not(:last-child) {
110
- border-radius: 0;
111
- }
112
-
113
- // Single button - all corners rounded
114
- > .#{$component}__button--single,
115
- > .#{base.$prefix}-button:only-child {
116
- border-radius: var(--button-group-radius);
117
- }
75
+ // Density lowers the height; a round icon button keeps its aspect
76
+ > .#{$icon-button} {
77
+ width: var(--button-group-height);
118
78
  }
119
79
 
120
- // =========================================================================
121
- // VARIANT STYLES
122
- // =========================================================================
123
-
124
- // Outlined variant (default for button groups per MD3)
125
- &--outlined {
126
- > .#{base.$prefix}-button {
127
- background-color: transparent;
128
- color: t.color('primary');
129
- box-shadow: none;
130
-
131
- &:hover:not([disabled]) {
132
- background-color: t.alpha('primary', 0.08);
133
- }
134
-
135
- &:focus-visible {
136
- background-color: t.alpha('primary', 0.12);
137
- }
138
-
139
- &:active:not([disabled]) {
140
- background-color: t.alpha('primary', 0.12);
141
- }
142
- }
143
-
144
- // Horizontal border handling
145
- &.#{$component}--horizontal {
146
- > .#{base.$prefix}-button {
147
- border: 1px solid t.color('outline');
148
-
149
- // Remove right border except for last button to avoid double borders
150
- &:not(:last-child) {
151
- border-right: none;
152
- }
153
- }
154
- }
155
-
156
- // Vertical border handling
157
- &.#{$component}--vertical {
158
- > .#{base.$prefix}-button {
159
- border: 1px solid t.color('outline');
160
-
161
- // Remove bottom border except for last button to avoid double borders
162
- &:not(:last-child) {
163
- border-bottom: none;
164
- }
165
- }
80
+ &--equal-width {
81
+ > .#{$button} {
82
+ flex: 1 1 0;
83
+ justify-content: center;
166
84
  }
167
85
  }
168
86
 
169
- // Filled variant
170
- &--filled {
171
- > .#{base.$prefix}-button {
172
- background-color: t.color('primary');
173
- color: t.color('on-primary');
174
- border: none;
175
- box-shadow: none;
176
-
177
- &:hover:not([disabled]) {
178
- background-color: t.alpha('primary', 0.92);
179
- box-shadow: v.elevation('level-1');
180
- }
181
-
182
- &:focus-visible {
183
- background-color: t.alpha('primary', 0.88);
184
- }
185
-
186
- &:active:not([disabled]) {
187
- background-color: t.alpha('primary', 0.88);
188
- box-shadow: none;
189
- }
190
- }
191
-
192
- // Add subtle dividers between buttons
193
- &.#{$component}--horizontal {
194
- > .#{base.$prefix}-button:not(:last-child) {
195
- border-right: 1px solid t.alpha('on-primary', 0.12);
196
- }
197
- }
198
-
199
- &.#{$component}--vertical {
200
- > .#{base.$prefix}-button:not(:last-child) {
201
- border-bottom: 1px solid t.alpha('on-primary', 0.12);
202
- }
203
- }
87
+ &--disabled {
88
+ pointer-events: none;
204
89
  }
205
90
 
206
- // Tonal variant
207
- &--tonal {
208
- > .#{base.$prefix}-button {
209
- background-color: t.color('secondary-container');
210
- color: t.color('on-secondary-container');
211
- border: none;
212
- box-shadow: none;
213
-
214
- &:hover:not([disabled]) {
215
- background-color: t.alpha('secondary-container', 0.92);
216
- box-shadow: v.elevation('level-1');
217
- }
218
-
219
- &:focus-visible {
220
- background-color: t.alpha('secondary-container', 0.88);
221
- }
222
-
223
- &:active:not([disabled]) {
224
- background-color: t.alpha('secondary-container', 0.88);
225
- box-shadow: none;
226
- }
227
- }
228
-
229
- // Add subtle dividers between buttons
230
- &.#{$component}--horizontal {
231
- > .#{base.$prefix}-button:not(:last-child) {
232
- border-right: 1px solid t.alpha('on-secondary-container', 0.12);
233
- }
91
+ // ---------------------------------------------------------------------------
92
+ // Connected groups: corner shapes per position
93
+ // ---------------------------------------------------------------------------
94
+ &--connected {
95
+ > .#{$button},
96
+ > .#{$icon-button} {
97
+ #{$shape}: var(--button-group-inner-corner);
98
+ #{$shape}-pressed: var(--button-group-pressed-corner);
99
+ #{$shape}-selected: var(--button-group-radius);
234
100
  }
235
101
 
236
- &.#{$component}--vertical {
237
- > .#{base.$prefix}-button:not(:last-child) {
238
- border-bottom: 1px solid t.alpha('on-secondary-container', 0.12);
239
- }
102
+ > .#{$component}__button--first {
103
+ #{$shape}: var(--button-group-radius) var(--button-group-inner-corner) var(--button-group-inner-corner) var(--button-group-radius);
104
+ #{$shape}-pressed: var(--button-group-radius) var(--button-group-pressed-corner) var(--button-group-pressed-corner) var(--button-group-radius);
240
105
  }
241
- }
242
-
243
- // Elevated variant
244
- &--elevated {
245
- box-shadow: v.elevation('level-1');
246
- border-radius: var(--button-group-radius);
247
-
248
- > .#{base.$prefix}-button {
249
- background-color: t.color('surface-container-low');
250
- color: t.color('primary');
251
- border: none;
252
- box-shadow: none;
253
-
254
- &:hover:not([disabled]) {
255
- background-color: t.alpha('primary', 0.08);
256
- }
257
-
258
- &:focus-visible {
259
- background-color: t.alpha('primary', 0.12);
260
- }
261
106
 
262
- &:active:not([disabled]) {
263
- background-color: t.alpha('primary', 0.12);
264
- }
107
+ > .#{$component}__button--last {
108
+ #{$shape}: var(--button-group-inner-corner) var(--button-group-radius) var(--button-group-radius) var(--button-group-inner-corner);
109
+ #{$shape}-pressed: var(--button-group-pressed-corner) var(--button-group-radius) var(--button-group-radius) var(--button-group-pressed-corner);
265
110
  }
266
111
 
267
- // Add subtle dividers between buttons
268
- &.#{$component}--horizontal {
269
- > .#{base.$prefix}-button:not(:last-child) {
270
- border-right: 1px solid t.color('outline-variant');
271
- }
112
+ > .#{$component}__button--single {
113
+ #{$shape}: var(--button-group-radius);
114
+ #{$shape}-pressed: var(--button-group-radius);
272
115
  }
273
116
 
274
117
  &.#{$component}--vertical {
275
- > .#{base.$prefix}-button:not(:last-child) {
276
- border-bottom: 1px solid t.color('outline-variant');
277
- }
278
- }
279
- }
280
-
281
- // Text variant
282
- &--text {
283
- > .#{base.$prefix}-button {
284
- background-color: transparent;
285
- color: t.color('primary');
286
- border: none;
287
- box-shadow: none;
288
-
289
- &:hover:not([disabled]) {
290
- background-color: t.alpha('primary', 0.08);
118
+ > .#{$component}__button--first {
119
+ #{$shape}: var(--button-group-radius) var(--button-group-radius) var(--button-group-inner-corner) var(--button-group-inner-corner);
120
+ #{$shape}-pressed: var(--button-group-radius) var(--button-group-radius) var(--button-group-pressed-corner) var(--button-group-pressed-corner);
291
121
  }
292
122
 
293
- &:focus-visible {
294
- background-color: t.alpha('primary', 0.12);
295
- }
296
-
297
- &:active:not([disabled]) {
298
- background-color: t.alpha('primary', 0.12);
123
+ > .#{$component}__button--last {
124
+ #{$shape}: var(--button-group-inner-corner) var(--button-group-inner-corner) var(--button-group-radius) var(--button-group-radius);
125
+ #{$shape}-pressed: var(--button-group-pressed-corner) var(--button-group-pressed-corner) var(--button-group-radius) var(--button-group-radius);
299
126
  }
300
127
  }
301
- }
302
-
303
- // =========================================================================
304
- // DENSITY VARIANTS
305
- // =========================================================================
306
128
 
307
- // Default density (40px height)
308
- // Uses CSS custom properties set by default
309
-
310
- // Comfortable density (36px height)
311
- &--density-comfortable {
312
- --button-group-height: 36px;
313
- --button-group-radius: 18px;
314
-
315
- > .#{base.$prefix}-button {
316
- height: 36px;
317
- padding: 0 20px;
318
-
319
- .#{base.$prefix}-button-text {
320
- font-size: 0.8125rem;
129
+ // Square connected groups: the outer corners take the inner corner size
130
+ &.#{$component}--square {
131
+ > .#{$component}__button--first,
132
+ > .#{$component}__button--last,
133
+ > .#{$component}__button--single {
134
+ #{$shape}: var(--button-group-inner-corner);
135
+ #{$shape}-pressed: var(--button-group-pressed-corner);
321
136
  }
322
137
  }
323
- }
324
138
 
325
- // Compact density (32px height)
326
- &--density-compact {
327
- --button-group-height: 32px;
328
- --button-group-radius: 16px;
329
-
330
- > .#{base.$prefix}-button {
331
- height: 32px;
332
- padding: 0 16px;
333
- min-width: 48px;
334
-
335
- .#{base.$prefix}-button-text {
336
- font-size: 0.75rem;
337
- }
338
-
339
- .#{base.$prefix}-button-icon {
340
- svg {
341
- width: 18px;
342
- height: 18px;
343
- }
139
+ // xs and s connected groups keep a 48dp minimum width per button
140
+ &.#{$component}--size-xs,
141
+ &.#{$component}--size-s {
142
+ > .#{$button},
143
+ > .#{$icon-button} {
144
+ min-width: 48px;
344
145
  }
345
146
  }
346
147
  }
347
148
 
348
- // =========================================================================
349
- // EQUAL WIDTH OPTION
350
- // =========================================================================
351
-
352
- &--equal-width {
353
- > .#{base.$prefix}-button {
354
- flex: 1 1 0;
355
- justify-content: center;
356
- }
357
- }
358
-
359
- // =========================================================================
360
- // DISABLED STATE
361
- // =========================================================================
362
-
363
- &--disabled {
364
- opacity: 0.38;
365
- pointer-events: none;
366
-
367
- > .#{base.$prefix}-button {
368
- pointer-events: none;
369
- }
370
- }
371
-
372
- // =========================================================================
373
- // BUTTON POSITION CLASSES
374
- // =========================================================================
375
-
376
- &__button {
377
- // Base button in group styling
378
- position: relative;
379
-
380
- &--first,
381
- &--middle,
382
- &--last,
383
- &--single {
384
- // Position-specific styles are handled above in orientation variants
385
- }
386
- }
387
- }
388
-
389
- // =============================================================================
390
- // DARK THEME ADJUSTMENTS
391
- // =============================================================================
392
-
393
- html[data-theme-mode=dark] {
394
- .#{$component} {
395
- &--outlined {
396
- > .#{base.$prefix}-button {
397
- border-color: t.color('outline');
398
- }
399
- }
400
-
401
- &--elevated {
402
- background-color: t.color('surface-container-high');
403
-
404
- > .#{base.$prefix}-button {
405
- background-color: transparent;
149
+ // ---------------------------------------------------------------------------
150
+ // Labels shown on the selected button only: buttons with icon and text
151
+ // collapse to the icon and the selected one widens to reveal its label
152
+ // ---------------------------------------------------------------------------
153
+ &--labels-selected {
154
+ > .#{$button} {
155
+ gap: 0;
156
+ min-width: var(--button-group-height);
157
+ padding: 0 calc((var(--button-group-height) - var(--button-group-icon)) / 2);
158
+
159
+ .#{$button}-text {
160
+ display: inline-block;
161
+ max-width: 0;
162
+ opacity: 0;
163
+ overflow: hidden;
164
+ white-space: nowrap;
165
+ text-overflow: clip;
166
+ transition:
167
+ max-width v.motion('spring-fast-spatial-duration') v.motion('spring-fast-spatial-easing'),
168
+ opacity v.motion('spring-default-effects-duration') v.motion('spring-default-effects-easing');
169
+ }
170
+ }
171
+
172
+ > .#{$component}__button--selected {
173
+ gap: v.button('icon-gap-s');
174
+ padding: 0 v.button('padding-s');
175
+
176
+ .#{$button}-text {
177
+ max-width: 12em;
178
+ opacity: 1;
406
179
  }
407
180
  }
408
181
  }