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.
- package/dist/components/bottom-sheet/api.d.ts +43 -0
- package/dist/components/bottom-sheet/bottom-sheet.d.ts +24 -0
- package/dist/components/bottom-sheet/config.d.ts +34 -0
- package/dist/components/bottom-sheet/constants.d.ts +65 -0
- package/dist/components/bottom-sheet/features/drag.d.ts +32 -0
- package/dist/components/bottom-sheet/features/index.d.ts +3 -0
- package/dist/components/bottom-sheet/features/state.d.ts +31 -0
- package/dist/components/bottom-sheet/features/structure.d.ts +26 -0
- package/dist/components/bottom-sheet/index.d.ts +2 -0
- package/dist/components/bottom-sheet/types.d.ts +116 -0
- package/dist/components/dialog/types.d.ts +1 -1
- package/dist/components/drawer/types.d.ts +1 -1
- package/dist/components/index.d.ts +6 -0
- package/dist/components/menu/features/registry.d.ts +37 -0
- package/dist/components/menu/index.d.ts +1 -1
- package/dist/components/menu/types.d.ts +31 -9
- package/dist/components/progress/types.d.ts +2 -2
- package/dist/components/sheet/sheet.d.ts +12 -0
- package/dist/components/sheet/types.d.ts +8 -0
- package/dist/components/side-sheet/api.d.ts +33 -0
- package/dist/components/side-sheet/config.d.ts +34 -0
- package/dist/components/side-sheet/constants.d.ts +60 -0
- package/dist/components/side-sheet/features/index.d.ts +2 -0
- package/dist/components/side-sheet/features/state.d.ts +28 -0
- package/dist/components/side-sheet/features/structure.d.ts +21 -0
- package/dist/components/side-sheet/index.d.ts +2 -0
- package/dist/components/side-sheet/side-sheet.d.ts +24 -0
- package/dist/components/side-sheet/types.d.ts +102 -0
- package/dist/index.cjs +13 -13
- package/dist/index.cjs.map +26 -12
- package/dist/index.js +13 -13
- package/dist/index.js.map +26 -12
- package/dist/package.json +1 -1
- package/dist/styles.css +2 -2
- package/package.json +1 -2
- package/src/styles/abstract/_variables.scss +13 -1
- package/src/styles/components/_bottom-sheet.scss +167 -0
- package/src/styles/components/_button.scss +9 -3
- package/src/styles/components/_card.scss +78 -19
- package/src/styles/components/_menu.scss +89 -3
- package/src/styles/components/_segmented-button.scss +28 -70
- package/src/styles/components/_side-sheet.scss +175 -0
- 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.
|
|
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('
|
|
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
|
-
|
|
438
|
-
|
|
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
|
|
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
|
|
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);
|
|
64
|
-
|
|
68
|
+
@include m.elevation(1);
|
|
69
|
+
|
|
65
70
|
&:hover.#{$component}--interactive {
|
|
66
|
-
@include m.elevation(2);
|
|
71
|
+
@include m.elevation(2);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
&:active.#{$component}--interactive {
|
|
70
|
-
@include m.
|
|
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
|
|
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);
|
|
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);
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
447
|
-
//
|
|
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
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
139
|
+
// Last segment: right corners only
|
|
142
140
|
&:last-child {
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
//
|
|
146
|
+
// A lone segment is the whole container
|
|
158
147
|
&:only-child {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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
|
|
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
|
|
306
|
+
content: none;
|
|
349
307
|
}
|
|
350
308
|
|
|
351
309
|
// Override variant hover states for selected
|