mtrl 0.8.0-next.21 → 0.8.0-next.24

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 (43) hide show
  1. package/dist/components/bottom-sheet/api.d.ts +43 -0
  2. package/dist/components/bottom-sheet/bottom-sheet.d.ts +24 -0
  3. package/dist/components/bottom-sheet/config.d.ts +34 -0
  4. package/dist/components/bottom-sheet/constants.d.ts +65 -0
  5. package/dist/components/bottom-sheet/features/drag.d.ts +32 -0
  6. package/dist/components/bottom-sheet/features/index.d.ts +3 -0
  7. package/dist/components/bottom-sheet/features/state.d.ts +31 -0
  8. package/dist/components/bottom-sheet/features/structure.d.ts +26 -0
  9. package/dist/components/bottom-sheet/index.d.ts +2 -0
  10. package/dist/components/bottom-sheet/types.d.ts +116 -0
  11. package/dist/components/dialog/types.d.ts +1 -1
  12. package/dist/components/drawer/types.d.ts +1 -1
  13. package/dist/components/index.d.ts +6 -0
  14. package/dist/components/menu/features/registry.d.ts +37 -0
  15. package/dist/components/menu/index.d.ts +1 -1
  16. package/dist/components/menu/types.d.ts +31 -9
  17. package/dist/components/progress/types.d.ts +2 -2
  18. package/dist/components/sheet/sheet.d.ts +12 -0
  19. package/dist/components/sheet/types.d.ts +8 -0
  20. package/dist/components/side-sheet/api.d.ts +33 -0
  21. package/dist/components/side-sheet/config.d.ts +34 -0
  22. package/dist/components/side-sheet/constants.d.ts +60 -0
  23. package/dist/components/side-sheet/features/index.d.ts +2 -0
  24. package/dist/components/side-sheet/features/state.d.ts +28 -0
  25. package/dist/components/side-sheet/features/structure.d.ts +21 -0
  26. package/dist/components/side-sheet/index.d.ts +2 -0
  27. package/dist/components/side-sheet/side-sheet.d.ts +24 -0
  28. package/dist/components/side-sheet/types.d.ts +102 -0
  29. package/dist/index.cjs +13 -13
  30. package/dist/index.cjs.map +26 -12
  31. package/dist/index.js +13 -13
  32. package/dist/index.js.map +26 -12
  33. package/dist/package.json +1 -1
  34. package/dist/styles.css +2 -2
  35. package/package.json +1 -2
  36. package/src/styles/abstract/_variables.scss +13 -1
  37. package/src/styles/components/_bottom-sheet.scss +167 -0
  38. package/src/styles/components/_button.scss +9 -3
  39. package/src/styles/components/_card.scss +78 -19
  40. package/src/styles/components/_menu.scss +89 -3
  41. package/src/styles/components/_segmented-button.scss +28 -70
  42. package/src/styles/components/_side-sheet.scss +175 -0
  43. package/src/styles/main.scss +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtrl",
3
- "version": "0.8.0-next.21",
3
+ "version": "0.8.0-next.24",
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",
@@ -52,7 +52,6 @@
52
52
  "test:ui": "bun test --watch --ui",
53
53
  "docs": "typedoc --skipErrorChecking",
54
54
  "docs:js": "typedoc --options typedoc.simple.json",
55
- "docs:alt": "bun generate-docs.js",
56
55
  "docs:serve": "bun run --bun serve docs",
57
56
  "docs:fix-errors": "tsc --noEmit --allowJs --skipLibCheck",
58
57
  "ts:check": "tsc --noEmit --skipLibCheck",
@@ -249,7 +249,6 @@ $button: (
249
249
  'padding-horizontal': 16px,
250
250
  'padding-horizontal-small': 12px, // text button (ButtonDefaults.TextButtonContentPadding)
251
251
  'padding-icon': 12px, // small button leading space next to an icon (pointer devices)
252
- 'border-radius': map.get($shape, 'full'),
253
252
  'transition-duration': map.get($motion, 'duration-short2'),
254
253
  'transition-easing': map.get($motion, 'easing-standard'),
255
254
  // Container height per size
@@ -288,6 +287,19 @@ $button: (
288
287
  'shape-square-m': map.get($shape, 'large'),
289
288
  'shape-square-l': map.get($shape, 'extra-large'),
290
289
  'shape-square-xl': map.get($shape, 'extra-large'),
290
+ // Round container shape per size: exactly half the container height.
291
+ //
292
+ // A pill needs the real radius, not merely a large one. border-radius
293
+ // animates on the number in the stylesheet, not on the corner you see, so a
294
+ // 9999px pill morphing to a 12px pressed shape spends almost the whole
295
+ // transition above half the height, paints as a pill throughout, and then
296
+ // snaps square at the very end. Half the height interpolates linearly and
297
+ // the morph reads as a morph.
298
+ 'shape-round-xs': 16px,
299
+ 'shape-round-s': 20px,
300
+ 'shape-round-m': 28px,
301
+ 'shape-round-l': 48px,
302
+ 'shape-round-xl': 68px,
291
303
  // Pressed container shape per size (round and square)
292
304
  'shape-pressed-xs': map.get($shape, 'small'),
293
305
  'shape-pressed-s': map.get($shape, 'small'),
@@ -0,0 +1,167 @@
1
+ // src/styles/components/_bottom-sheet.scss
2
+ @use "../abstract/base" as base;
3
+ @use "../abstract/variables" as v;
4
+ @use "../abstract/mixins" as m;
5
+ @use "../abstract/theme" as t;
6
+
7
+ $component: "#{base.$prefix}-bottom-sheet";
8
+
9
+ // -----------------------------------------------------------------------------
10
+ // Bottom sheet (Compose SheetBottomTokens and BottomSheetDefaults)
11
+ //
12
+ // A surface anchored to the bottom edge: surface-container-low, extra-large
13
+ // corners on the top two only, elevation level 1, and a 32x4dp handle that says
14
+ // it can be dragged. It stops growing at 640dp and centres itself, so it does
15
+ // not stretch across a desktop window.
16
+ // -----------------------------------------------------------------------------
17
+ .#{$component} {
18
+ position: fixed;
19
+ inset: 0;
20
+ z-index: v.z-index("modal");
21
+ display: flex;
22
+ align-items: flex-end;
23
+ justify-content: center;
24
+
25
+ // A standard sheet leaves the page usable, so the layer itself must not
26
+ // swallow clicks meant for what is behind it
27
+ pointer-events: none;
28
+
29
+ &--scrim-visible,
30
+ &--partial,
31
+ &--expanded {
32
+ pointer-events: auto;
33
+ }
34
+
35
+ // The scrim, on modal sheets only (ScrimTokens.ContainerOpacity)
36
+ &-scrim {
37
+ position: absolute;
38
+ inset: 0;
39
+ background-color: t.alpha("scrim", 0.32);
40
+ opacity: 0;
41
+ visibility: hidden;
42
+ transition:
43
+ opacity v.motion("duration-medium1") v.motion("easing-emphasized"),
44
+ visibility v.motion("duration-medium1") v.motion("easing-emphasized");
45
+ }
46
+
47
+ // The sheet itself
48
+ &-container {
49
+ position: relative;
50
+ width: 100%;
51
+ max-width: 640px; // BottomSheetDefaults.SheetMaxWidth
52
+ max-height: 100%;
53
+ display: flex;
54
+ flex-direction: column;
55
+ box-sizing: border-box;
56
+ pointer-events: auto;
57
+
58
+ background-color: t.color("surface-container-low"); // DockedContainerColor
59
+ color: t.color("on-surface");
60
+
61
+ // CornerExtraLargeTop: the bottom edge meets the screen, so it is square
62
+ border-start-start-radius: v.shape("extra-large");
63
+ border-start-end-radius: v.shape("extra-large");
64
+ @include m.elevation(1); // DockedStandardContainerElevation
65
+
66
+ // Hidden by default: translated fully out of view
67
+ transform: translateY(100%);
68
+ transition: transform v.motion("duration-medium2") v.motion("easing-emphasized");
69
+
70
+ &:focus {
71
+ outline: none;
72
+ }
73
+
74
+ // Focus ring uses the secondary role (FocusIndicatorColor)
75
+ &:focus-visible {
76
+ outline: 3px solid t.color("secondary");
77
+ outline-offset: -3px;
78
+ }
79
+ }
80
+
81
+ // The 32x4dp bar (DockedDragHandleWidth / DockedDragHandleHeight)
82
+ &-handle {
83
+ flex: 0 0 auto;
84
+ width: 32px;
85
+ height: 4px;
86
+ margin: 16px auto;
87
+ border-radius: v.shape("extra-large"); // BottomSheetDefaults.DragHandle shape
88
+ background-color: t.color("on-surface-variant"); // DockedDragHandleColor
89
+ cursor: grab;
90
+ touch-action: none; // the sheet handles the gesture, not the browser
91
+
92
+ &:active {
93
+ cursor: grabbing;
94
+ }
95
+ }
96
+
97
+ &-header {
98
+ flex: 0 0 auto;
99
+ padding: 0 24px 16px;
100
+ }
101
+
102
+ &-title {
103
+ margin: 0;
104
+ color: t.color("on-surface");
105
+ @include m.typography("headline-small");
106
+ }
107
+
108
+ &-content {
109
+ flex: 1 1 auto;
110
+ min-height: 0;
111
+ overflow-y: auto;
112
+ padding: 0 24px 24px;
113
+ @include m.typography("body-medium");
114
+ color: t.color("on-surface-variant");
115
+ }
116
+
117
+ // ---------------------------------------------------------------------
118
+ // States
119
+ // ---------------------------------------------------------------------
120
+ &--partial {
121
+ .#{$component}-container {
122
+ // the peek height, showing what the sheet holds without covering
123
+ // the page (BottomSheetDefaults.SheetPeekHeight and its content)
124
+ transform: translateY(0);
125
+ max-height: 50%;
126
+ }
127
+
128
+ .#{$component}-scrim {
129
+ opacity: 1;
130
+ visibility: visible;
131
+ }
132
+ }
133
+
134
+ &--expanded {
135
+ .#{$component}-container {
136
+ transform: translateY(0);
137
+ max-height: 100%;
138
+ // level with the top of the screen, the corners square off
139
+ // (DockedMinimizedContainerShape is CornerNone)
140
+ &:where(.#{$component}--full-height *) {
141
+ border-start-start-radius: 0;
142
+ border-start-end-radius: 0;
143
+ }
144
+ }
145
+
146
+ .#{$component}-scrim {
147
+ opacity: 1;
148
+ visibility: visible;
149
+ }
150
+ }
151
+
152
+ // A standard sheet has no scrim and does not block the page
153
+ &--standard {
154
+ pointer-events: none;
155
+
156
+ .#{$component}-container {
157
+ pointer-events: auto;
158
+ }
159
+ }
160
+
161
+ @include m.reduced-motion {
162
+ .#{$component}-container,
163
+ .#{$component}-scrim {
164
+ transition: none;
165
+ }
166
+ }
167
+ }
@@ -64,7 +64,7 @@ $content-color: (
64
64
  height: v.button('height-s');
65
65
  padding: 0 v.button('padding-s');
66
66
  border: none;
67
- border-radius: var(--#{base.$prefix}-button-shape, v.button('border-radius'));
67
+ border-radius: var(--#{base.$prefix}-button-shape, v.button('shape-round-s'));
68
68
  background-color: transparent;
69
69
  color: inherit;
70
70
  font: inherit;
@@ -183,6 +183,10 @@ $content-color: (
183
183
  height: v.button('icon-size-#{$size}');
184
184
  }
185
185
 
186
+ // Round shape at rest: half the height, so the pressed shape is a
187
+ // morph rather than a snap. See shape-round-* in $button.
188
+ border-radius: var(--#{base.$prefix}-button-shape, v.button('shape-round-#{$size}'));
189
+
186
190
  // Square shape at rest
187
191
  &.#{$component}--square {
188
192
  border-radius: var(--#{base.$prefix}-button-shape, v.button('shape-square-#{$size}'));
@@ -434,8 +438,10 @@ $content-color: (
434
438
  }
435
439
  }
436
440
 
437
- &.#{$component}--square {
438
- border-radius: var(--#{base.$prefix}-button-shape-selected, v.button('border-radius'));
441
+ @each $size in $sizes {
442
+ &.#{$component}--square.#{$component}--#{$size} {
443
+ border-radius: var(--#{base.$prefix}-button-shape-selected, v.button('shape-round-#{$size}'));
444
+ }
439
445
  }
440
446
 
441
447
  @each $size in $sizes {
@@ -17,7 +17,11 @@ $component: '#{base.$prefix}-card';
17
17
  background-color: t.color('surface');
18
18
  color: t.color('on-surface');
19
19
  overflow: hidden;
20
- width: v.card('width'); // Use card width variable
20
+ // No width of its own. A card fills the column, grid cell or flex track it
21
+ // is placed in, as the M3 card specs describe. This used to be a hard 344px,
22
+ // which meant every card had to opt out of its own component to sit in a
23
+ // layout. The sizes are still available as modifiers below for the cases
24
+ // that genuinely want them.
21
25
  --card-elevation: 0;
22
26
 
23
27
  // Typography - use mixin
@@ -57,47 +61,89 @@ $component: '#{base.$prefix}-card';
57
61
 
58
62
  // === Variants - use proper theme colors ===
59
63
 
60
- // Elevated variant
64
+ // Elevated: ElevatedCardTokens. Level 1 at rest, 2 hovered, back to 1 when
65
+ // pressed or focused, 4 while dragged.
61
66
  &--elevated {
62
67
  background-color: t.color('surface-container-low');
63
- @include m.elevation(1); // Use elevation mixin
64
-
68
+ @include m.elevation(1);
69
+
65
70
  &:hover.#{$component}--interactive {
66
- @include m.elevation(2); // Use elevation mixin for hover state
71
+ @include m.elevation(2);
67
72
  }
68
73
 
69
74
  &:active.#{$component}--interactive {
70
- @include m.state-layer(t.color('on-surface'), 'pressed'); // Use state-layer mixin
75
+ @include m.elevation(1);
76
+ @include m.state-layer(t.color('on-surface'), 'pressed');
77
+ }
78
+
79
+ &.#{$component}--dragging {
80
+ @include m.elevation(4); // DraggedContainerElevation
81
+ }
82
+
83
+ &.#{$component}--state-disabled {
84
+ background-color: t.color('surface'); // DisabledContainerColor
85
+ @include m.elevation(1); // it keeps its elevation while disabled
71
86
  }
72
87
  }
73
-
74
- // Filled variant
88
+
89
+ // Filled: FilledCardTokens. Flat at rest, and it rises to level 1 on hover,
90
+ // which is the only elevation it ever takes.
75
91
  &--filled {
76
92
  background-color: t.color('surface-container-highest');
77
- @include m.elevation(0); // No elevation
78
-
93
+ @include m.elevation(0);
94
+
79
95
  &:hover.#{$component}--interactive {
96
+ @include m.elevation(1); // HoverContainerElevation
80
97
  @include m.state-layer(t.color('on-surface'), 'hover');
81
98
  }
82
99
 
83
100
  &:active.#{$component}--interactive {
101
+ @include m.elevation(0);
84
102
  @include m.state-layer(t.color('on-surface'), 'pressed');
85
103
  }
104
+
105
+ &.#{$component}--dragging {
106
+ @include m.elevation(3); // DraggedContainerElevation
107
+ }
108
+
109
+ &.#{$component}--state-disabled {
110
+ background-color: t.color('surface-variant'); // DisabledContainerColor
111
+ }
86
112
  }
87
-
88
- // Outlined variant
113
+
114
+ // Outlined: OutlinedCardTokens. The outline is outline-variant at rest and
115
+ // hovered, and on-surface once focused, which is the only state that changes
116
+ // it. It used to sit at the darker `outline` and lighten on hover, which is
117
+ // the wrong role and the wrong direction.
89
118
  &--outlined {
90
- border: 1px solid t.color('outline');
119
+ border: 1px solid t.color('outline-variant'); // OutlineColor
91
120
  background-color: t.color('surface');
92
- @include m.elevation(0); // No elevation
93
-
121
+ @include m.elevation(0);
122
+
94
123
  &:hover.#{$component}--interactive {
124
+ @include m.elevation(1); // HoverContainerElevation
95
125
  @include m.state-layer(t.color('on-surface'), 'hover');
96
- border-color: t.color('outline-variant');
126
+ border-color: t.color('outline-variant'); // HoverOutlineColor
127
+ }
128
+
129
+ &:focus-visible,
130
+ &.#{$component}--focused {
131
+ border-color: t.color('on-surface'); // FocusOutlineColor
97
132
  }
98
133
 
99
134
  &:active.#{$component}--interactive {
135
+ @include m.elevation(0);
100
136
  @include m.state-layer(t.color('on-surface'), 'pressed');
137
+ border-color: t.color('outline-variant'); // PressedOutlineColor
138
+ }
139
+
140
+ &.#{$component}--dragging {
141
+ @include m.elevation(3); // DraggedContainerElevation
142
+ }
143
+
144
+ &.#{$component}--state-disabled {
145
+ // DisabledOutlineColor at DisabledOutlineOpacity
146
+ border-color: t.alpha('outline', 0.12);
101
147
  }
102
148
  }
103
149
 
@@ -109,10 +155,22 @@ $component: '#{base.$prefix}-card';
109
155
  user-select: none;
110
156
  }
111
157
 
112
- // Full-width cards
158
+ // Widths, for the cases that want a fixed one. A card has none by default.
113
159
  &--full-width {
114
160
  width: 100%;
115
161
  }
162
+
163
+ &--small {
164
+ width: v.card('width-small');
165
+ }
166
+
167
+ &--medium {
168
+ width: v.card('width-medium');
169
+ }
170
+
171
+ &--large {
172
+ width: v.card('width-large');
173
+ }
116
174
 
117
175
  // === Sub-components ===
118
176
 
@@ -310,9 +368,10 @@ $component: '#{base.$prefix}-card';
310
368
  pointer-events: none;
311
369
  }
312
370
 
371
+ // Elevation while dragged differs by variant, so it is set with each of them
372
+ // above. The 0.9 opacity that used to be here is in no token.
313
373
  &--dragging {
314
- @include m.elevation(4); // Use elevation mixin
315
- opacity: 0.9;
374
+ cursor: grabbing;
316
375
  }
317
376
 
318
377
  // Loading overlay
@@ -59,6 +59,24 @@ $component: "#{$prefix}-menu";
59
59
  z-index: f.get-z-index("menu") + 1;
60
60
  }
61
61
 
62
+ // A run of items between gaps. The element is presentational: it exists so
63
+ // a gap can split the menu into separate surfaces in the vertical variant.
64
+ // A standard menu is one surface, so here a group is only spacing.
65
+ &-group {
66
+ display: block;
67
+ list-style: none;
68
+
69
+ > ul {
70
+ margin: 0;
71
+ padding: 0;
72
+ list-style: none;
73
+ }
74
+
75
+ + .#{$component}-group {
76
+ margin-top: 8px;
77
+ }
78
+ }
79
+
62
80
  // List container
63
81
  &-list {
64
82
  margin: 0;
@@ -331,7 +349,11 @@ $component: "#{$prefix}-menu";
331
349
  border-radius: v.shape("large");
332
350
  background-color: var(--#{$prefix}-menu-container);
333
351
  color: var(--#{$prefix}-menu-label);
334
- padding: 8px;
352
+ // GroupPadding. The vertical menu has no container spacing token of
353
+ // its own: it is made of groups, and a menu with no gaps is one group,
354
+ // so the container's inset is the group's. The 8dp that was here is
355
+ // the horizontal variant's ContainerTopSpace, which does not apply.
356
+ padding: 4px;
335
357
 
336
358
  // Vibrant: tertiary-based, more prominent, to be used sparingly
337
359
  &.#{$component}--vibrant {
@@ -443,14 +465,78 @@ $component: "#{$prefix}-menu";
443
465
  }
444
466
  }
445
467
 
446
- // A grouped menu separates its runs of items with a divider; the gap
447
- // treatment the spec shows is not offered on the web
468
+ // A grouped menu separates its runs of items two ways, as the spec
469
+ // shows: a divider line across one surface, or a gap
448
470
  // (m3.material.io menu guidelines, "Gaps & dividers")
449
471
  .#{$component}-divider {
450
472
  margin: 6px 0;
451
473
  }
452
474
  }
453
475
 
476
+ // -------------------------------------------------------------------------
477
+ // The gap separator: the menu stops being one surface and each group
478
+ // becomes its own, keeping the container's shape and elevation.
479
+ //
480
+ // The measurements come from the vertical menu specimen on
481
+ // m3.material.io/components/menus, which the guidelines show but do not
482
+ // give numbers for. Read off that image at its 2px-per-dp scale: a 44dp
483
+ // item, a 16dp group corner, 4dp of padding inside each group and 2dp
484
+ // between them, so two items either side of a boundary sit 10dp apart.
485
+ // The group hugs its items more tightly than the whole-menu container
486
+ // does, which is what keeps a gap from reading as a chasm.
487
+ // -------------------------------------------------------------------------
488
+ &--vertical:has(.#{$component}-group) {
489
+ background-color: transparent;
490
+ box-shadow: none;
491
+ padding: 0;
492
+
493
+ // Each group casts its own shadow. The container clips so that item
494
+ // backgrounds stay inside its rounded corners, but there is no
495
+ // container here to stay inside, and clipping would cut the shadows
496
+ // off square.
497
+ overflow: visible;
498
+
499
+ .#{$component}-list {
500
+ overflow: visible;
501
+ // the separator is the 2dp the variant already puts between items
502
+ gap: 2px;
503
+ }
504
+
505
+ .#{$component}-group {
506
+ background-color: var(--#{$prefix}-menu-container);
507
+ padding: 4px; // GroupPadding
508
+ @include m.elevation(2);
509
+
510
+ // A corner facing a gap is the group's own shape, 8dp
511
+ // (SegmentedMenuTokens.GroupShape). Only the outside of the menu
512
+ // keeps the 16dp container shape, so the run of groups reads as
513
+ // one menu that has been cut, rather than as loose cards.
514
+ border-radius: v.shape("small");
515
+
516
+ &:first-child {
517
+ border-start-start-radius: v.shape("large"); // ContainerShape
518
+ border-start-end-radius: v.shape("large");
519
+ }
520
+
521
+ &:last-child {
522
+ border-end-start-radius: v.shape("large");
523
+ border-end-end-radius: v.shape("large");
524
+ }
525
+
526
+ // its own margin would add to the list gap
527
+ + .#{$component}-group {
528
+ margin-top: 0;
529
+ }
530
+
531
+ // and the items inside keep the 2dp separation of the variant
532
+ > ul {
533
+ display: flex;
534
+ flex-direction: column;
535
+ gap: 2px;
536
+ }
537
+ }
538
+ }
539
+
454
540
  // -------------------------------------------------------------------------
455
541
  // Shape morph on the container: while a submenu is open, the menu that
456
542
  // opened it steps back to an 8dp corner and the active one takes 24dp
@@ -27,7 +27,6 @@ $component: "#{base.$prefix}-segmented-button";
27
27
  position: relative;
28
28
  display: inline-flex;
29
29
  align-items: stretch;
30
- border-radius: 20px;
31
30
  border: 1px solid t.color("outline");
32
31
  background-color: transparent;
33
32
  overflow: hidden;
@@ -95,7 +94,6 @@ $component: "#{base.$prefix}-segmented-button";
95
94
  background-color: transparent;
96
95
  border: none;
97
96
  position: relative;
98
- border-radius: 0 !important; // Override button's border-radius completely
99
97
  min-width: 48px;
100
98
  height: 100%;
101
99
  color: t.color("on-surface");
@@ -105,12 +103,16 @@ $component: "#{base.$prefix}-segmented-button";
105
103
  // Use CSS variables for dynamic sizing
106
104
  padding: 0 var(--segment-padding-x);
107
105
 
108
- // CRITICAL: Override button's active state border-radius changes
109
- // Segmented button segments must maintain their shape
110
- &:active,
111
- &.#{base.$prefix}-button--active {
112
- border-radius: 0 !important;
113
- }
106
+ // Corner shapes are handed to the button through its own shape hooks
107
+ // rather than declared over the top of it. The button reads every one
108
+ // of its radii through these, resting, pressed and selected alike, so
109
+ // one declaration per position holds the segment's shape in every
110
+ // state. Overriding border-radius directly meant re-stating each
111
+ // corner for each state and each variant, and losing to the button
112
+ // stylesheet without !important on all of it.
113
+ --#{base.$prefix}-button-shape: 0;
114
+ --#{base.$prefix}-button-shape-pressed: 0;
115
+ --#{base.$prefix}-button-shape-selected: 0;
114
116
 
115
117
  // Icon-only segments
116
118
  &.#{base.$prefix}-button--circular {
@@ -124,50 +126,34 @@ $component: "#{base.$prefix}-segmented-button";
124
126
  var(--segment-padding-icon-text-left);
125
127
  }
126
128
 
127
- // First segment - rounded left corners only
129
+ // The container is one rounded shape, so only its ends round off
130
+ $round: var(--segment-border-radius);
131
+
132
+ // First segment: left corners only
128
133
  &:first-child {
129
- border-top-left-radius: var(--segment-border-radius) !important;
130
- border-bottom-left-radius: var(--segment-border-radius) !important;
131
-
132
- &:active,
133
- &.#{base.$prefix}-button--active {
134
- border-top-left-radius: var(--segment-border-radius) !important;
135
- border-bottom-left-radius: var(
136
- --segment-border-radius
137
- ) !important;
138
- }
134
+ --#{base.$prefix}-button-shape: #{$round} 0 0 #{$round};
135
+ --#{base.$prefix}-button-shape-pressed: #{$round} 0 0 #{$round};
136
+ --#{base.$prefix}-button-shape-selected: #{$round} 0 0 #{$round};
139
137
  }
140
138
 
141
- // Last segment - rounded right corners only
139
+ // Last segment: right corners only
142
140
  &:last-child {
143
- border-top-right-radius: var(--segment-border-radius) !important;
144
- border-bottom-right-radius: var(--segment-border-radius) !important;
145
-
146
- &:active,
147
- &.#{base.$prefix}-button--active {
148
- border-top-right-radius: var(
149
- --segment-border-radius
150
- ) !important;
151
- border-bottom-right-radius: var(
152
- --segment-border-radius
153
- ) !important;
154
- }
141
+ --#{base.$prefix}-button-shape: 0 #{$round} #{$round} 0;
142
+ --#{base.$prefix}-button-shape-pressed: 0 #{$round} #{$round} 0;
143
+ --#{base.$prefix}-button-shape-selected: 0 #{$round} #{$round} 0;
155
144
  }
156
145
 
157
- // Single segment - all corners rounded
146
+ // A lone segment is the whole container
158
147
  &:only-child {
159
- border-radius: var(--segment-border-radius) !important;
160
-
161
- &:active,
162
- &.#{base.$prefix}-button--active {
163
- border-radius: var(--segment-border-radius) !important;
164
- }
148
+ --#{base.$prefix}-button-shape: #{$round};
149
+ --#{base.$prefix}-button-shape-pressed: #{$round};
150
+ --#{base.$prefix}-button-shape-selected: #{$round};
165
151
  }
166
152
 
167
153
  // Reset button ::before pseudo-element (used for state layers)
168
154
  // This prevents button hover effects from interfering
169
155
  &::before {
170
- content: none !important;
156
+ content: none;
171
157
  }
172
158
 
173
159
  // Hover state (MD3: 8% state layer with on-surface color over surface)
@@ -254,7 +240,7 @@ $component: "#{base.$prefix}-segmented-button";
254
240
 
255
241
  // Reset variant ::before pseudo-element (state layers)
256
242
  &::before {
257
- content: none !important;
243
+ content: none;
258
244
  }
259
245
 
260
246
  // Override variant hover states
@@ -277,34 +263,6 @@ $component: "#{base.$prefix}-segmented-button";
277
263
  box-shadow: none;
278
264
  }
279
265
 
280
- &:active,
281
- &.#{base.$prefix}-button--active {
282
- border-radius: 0 !important;
283
- }
284
-
285
- &:first-child {
286
- &:active,
287
- &.#{base.$prefix}-button--active {
288
- border-top-left-radius: var(
289
- --segment-border-radius
290
- ) !important;
291
- border-bottom-left-radius: var(
292
- --segment-border-radius
293
- ) !important;
294
- }
295
- }
296
-
297
- &:last-child {
298
- &:active,
299
- &.#{base.$prefix}-button--active {
300
- border-top-right-radius: var(
301
- --segment-border-radius
302
- ) !important;
303
- border-bottom-right-radius: var(
304
- --segment-border-radius
305
- ) !important;
306
- }
307
- }
308
266
  }
309
267
  }
310
268
 
@@ -345,7 +303,7 @@ $component: "#{base.$prefix}-segmented-button";
345
303
 
346
304
  // Reset variant ::before pseudo-element
347
305
  &::before {
348
- content: none !important;
306
+ content: none;
349
307
  }
350
308
 
351
309
  // Override variant hover states for selected