material-inspired-component-library 9.1.0 → 9.1.2
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/README.md +2 -1
- package/components/appbar/index.scss +1 -1
- package/components/badge/README.md +43 -12
- package/components/badge/index.scss +15 -21
- package/components/bottomsheet/README.md +53 -22
- package/components/bottomsheet/index.scss +62 -45
- package/components/button/_shared.scss +9 -9
- package/components/card/README.md +55 -25
- package/components/card/index.scss +57 -38
- package/components/checkbox/README.md +97 -28
- package/components/checkbox/index.scss +39 -29
- package/components/chip/index.scss +6 -6
- package/components/datepicker/README.md +184 -59
- package/components/datepicker/index.scss +85 -21
- package/components/dialog/README.md +151 -56
- package/components/dialog/index.scss +50 -62
- package/components/iconbutton/index.scss +8 -8
- package/components/list/index.scss +15 -11
- package/components/menu/index.scss +13 -13
- package/components/navigationbar/index.scss +7 -1
- package/components/navigationrail/index.scss +27 -22
- package/components/radio/README.md +52 -12
- package/components/radio/index.scss +59 -41
- package/components/select/index.scss +3 -3
- package/components/sidesheet/README.md +90 -37
- package/components/sidesheet/index.scss +51 -41
- package/components/slider/README.md +35 -9
- package/components/slider/index.scss +182 -153
- package/components/snackbar/README.md +43 -25
- package/components/snackbar/index.scss +22 -27
- package/components/stepper/index.scss +5 -5
- package/components/switch/README.md +32 -4
- package/components/switch/index.scss +57 -52
- package/components/timepicker/README.md +131 -55
- package/components/timepicker/index.scss +47 -26
- package/dist/appbar.css +1 -1
- package/dist/badge.css +1 -1
- package/dist/bottomsheet.css +1 -1
- package/dist/bottomsheet.js +1 -1
- package/dist/button.css +1 -1
- package/dist/card.css +1 -1
- package/dist/checkbox.css +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/chip.css +1 -1
- package/dist/components/checkbox/index.d.ts +1 -0
- package/dist/datepicker.css +1 -1
- package/dist/datepicker.js +1 -1
- package/dist/dialog.css +1 -1
- package/dist/foundations/form/index.js +1 -1
- package/dist/iconbutton.css +1 -1
- package/dist/layout.css +1 -1
- package/dist/list.css +1 -1
- package/dist/menu.css +1 -1
- package/dist/micl.css +1 -1
- package/dist/micl.js +1 -1
- package/dist/navigationbar.css +1 -1
- package/dist/navigationrail.css +1 -1
- package/dist/radio.css +1 -1
- package/dist/select.css +1 -1
- package/dist/sidesheet.css +1 -1
- package/dist/slider.css +1 -1
- package/dist/slider.js +1 -1
- package/dist/snackbar.css +1 -1
- package/dist/snackbar.js +1 -1
- package/dist/stepper.css +1 -1
- package/dist/stepper.js +1 -1
- package/dist/switch.css +1 -1
- package/dist/timepicker.css +1 -1
- package/dist/timepicker.js +1 -1
- package/foundations/layout/_tokens.scss +55 -0
- package/foundations/layout/index.scss +5 -35
- package/package.json +2 -2
- package/{components/checkbox/_label.scss → styles/togglelabel.scss} +11 -3
package/README.md
CHANGED
|
@@ -162,9 +162,10 @@ MICL doubles as a working tour of what production CSS can do in 2026. A few of t
|
|
|
162
162
|
Each component's README documents its approach and its graceful-degradation story.
|
|
163
163
|
|
|
164
164
|
## Foundations 🪟
|
|
165
|
-
Separate CSS files, based on the [Material Design Layout Foundation](https://m3.material.io/foundations/layout/layout-overview), provide foundational styles that are not tied to a single component. The **Layout** foundation includes styles for the **window frame**, **rail region** and **panes** that adapt to the available screen space, ensuring your layout follows Material Design's adaptive design guidelines. The **Field** foundation arranges form fields in a grid with the standard Material vertical rhythm, optionally led by an icon column.
|
|
165
|
+
Separate CSS files, based on the [Material Design Layout Foundation](https://m3.material.io/foundations/layout/layout-overview), provide foundational styles that are not tied to a single component. The **Layout** foundation includes styles for the **window frame**, **rail region** and **panes** that adapt to the available screen space, ensuring your layout follows Material Design's adaptive design guidelines. The **Field** foundation arranges form fields in a grid with the standard Material vertical rhythm, optionally led by an icon column. The **Form** foundation ships no CSS: it validates a form with the browser's own Constraint Validation API and renders the result as MICL error styling.
|
|
166
166
|
|
|
167
167
|
- [x] [Field](foundations/field/README.md)
|
|
168
|
+
- [x] [Form](foundations/form/README.md)
|
|
168
169
|
- [x] [Layout](foundations/layout/README.md)
|
|
169
170
|
|
|
170
171
|
## Available components ✅
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
20
|
// SOFTWARE.
|
|
21
21
|
|
|
22
|
-
@use '../../foundations/layout';
|
|
22
|
+
@use '../../foundations/layout/tokens' as layout;
|
|
23
23
|
@use '../../styles/elevation';
|
|
24
24
|
@use '../../styles/shape';
|
|
25
25
|
@use '../../styles/statelayer';
|
|
@@ -4,7 +4,7 @@ This component implements the [Material Design 3 Expressive Badge](https://m3.ma
|
|
|
4
4
|
## Basic Usage
|
|
5
5
|
|
|
6
6
|
### HTML
|
|
7
|
-
To add a large badge, use a `<span>` element with the `micl-badge` class and add a short text inside. For a small badge, which is a simple dot, add the `micl-badge--small` class and leave the element empty.
|
|
7
|
+
To add a large badge, use a `<span>` element with the `micl-badge` class and add a short string of text inside. Keep the text to four characters at most, including a `+` (for example `999+`); anything longer is clipped. For a small badge, which is a simple dot, add the `micl-badge--small` class and leave the element empty.
|
|
8
8
|
|
|
9
9
|
```HTML
|
|
10
10
|
<span class="micl-badge">57</span>
|
|
@@ -28,26 +28,56 @@ Or import all MICL styles:
|
|
|
28
28
|
No custom JavaScript is required for the core functionality of this component.
|
|
29
29
|
|
|
30
30
|
### Live Demo
|
|
31
|
-
|
|
31
|
+
Badges are shown on the navigation items of the [Navigation bar](https://henkpb.github.io/micl/navigationbar.html) and [Navigation rail](https://henkpb.github.io/micl/navigationrail.html) pages, and on the [MICL home page](https://henkpb.github.io/micl/index.html).
|
|
32
32
|
|
|
33
33
|
## Anchoring
|
|
34
|
-
Badges are
|
|
34
|
+
Badges are placed on top of other elements, typically icons, using CSS anchor positioning:
|
|
35
35
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
36
|
+
1. Give the element the badge belongs to an `anchor-name`. The value must start with `--`.
|
|
37
|
+
2. Put the badge after that element in the markup and set its `position-anchor` to the same name.
|
|
38
38
|
|
|
39
39
|
```HTML
|
|
40
40
|
<span class="material-symbols-outlined" style="anchor-name:--inbox" aria-hidden="true">inbox</span>
|
|
41
|
-
<span class="micl-badge" style="position-anchor:--inbox">57</span>
|
|
41
|
+
<span class="micl-badge" style="position-anchor:--inbox" aria-hidden="true">57</span>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The badge is positioned relative to the center of its anchor, assuming a `--md-sys-icon-size` (24px) icon glyph is centered in it.
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
| ------------- | ------------- | ----------- |
|
|
48
|
-
| `--md-comp-badge-inline-offset` | `0px` | Adjusts the horizontal position of the badge |
|
|
49
|
-
| `--md-comp-badge-block-offset` | `0px` | Adjusts the vertical position of the badge |
|
|
46
|
+
Because the badge is absolutely positioned, its anchor must exist within the badge's containing block (the nearest positioned ancestor, or the document root). For naturally positioned elements like navigation items, the anchor name only needs to be unique within that specific item. Elsewhere, use a unique name per page or limit the names' reach with `anchor-scope` on a common ancestor.
|
|
50
47
|
|
|
48
|
+
### Trailing placement
|
|
49
|
+
When the icon is followed by text, a large badge on the icon would overlap the label. Following the Material Design guidance, place it after the label instead by adding the `micl-badge--trailing` class. The badge then needs no anchor: it is laid out in the item's flow, vertically centered, with a small gap after the label.
|
|
50
|
+
|
|
51
|
+
```HTML
|
|
52
|
+
<a href="#" class="micl-navigationrail__item" aria-label="Favorites, 84 new">
|
|
53
|
+
<span class="micl-navigationrail__icon material-symbols-outlined" aria-hidden="true">favorite</span>
|
|
54
|
+
<span class="micl-navigationrail__text">Favorites</span>
|
|
55
|
+
<span class="micl-badge micl-badge--trailing" aria-hidden="true">84</span>
|
|
56
|
+
</a>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Small badges keep their place on the icon in these layouts.
|
|
60
|
+
|
|
61
|
+
### Fine-tuning
|
|
62
|
+
You can fine-tune the position of an anchored badge using the following custom properties:
|
|
63
|
+
|
|
64
|
+
| Custom property | Meaning | Default |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `--md-comp-badge-inline-offset` | Moves the badge along the inline (horizontal) axis | `0px` |
|
|
67
|
+
| `--md-comp-badge-block-offset` | Moves the badge along the block (vertical) axis | `0px` |
|
|
68
|
+
|
|
69
|
+
## Accessibility
|
|
70
|
+
Badges are purely visual. To ensure screen readers interpret them correctly, hide the badge itself using `aria-hidden="true"` and update the destination's accessible name to include the badge's information (e.g., the number for a large badge, or "New notification" for a small one):
|
|
71
|
+
|
|
72
|
+
```HTML
|
|
73
|
+
<a href="#" class="micl-navigationbar__item" aria-label="Inbox, 57 new messages">
|
|
74
|
+
<span class="micl-navigationbar__icon material-symbols-outlined" style="anchor-name:--inbox" aria-hidden="true">inbox</span>
|
|
75
|
+
<span class="micl-navigationbar__text">Inbox</span>
|
|
76
|
+
<span class="micl-badge" style="position-anchor:--inbox" aria-hidden="true">57</span>
|
|
77
|
+
</a>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Update the accessible name whenever the badge content changes.
|
|
51
81
|
|
|
52
82
|
## Theming
|
|
53
83
|
Each badge can be themed with CSS custom properties that follow the Material Design 3 component-token naming convention. Set them on any appropriate parent element to affect its child badges.
|
|
@@ -57,6 +87,7 @@ Each badge can be themed with CSS custom properties that follow the Material Des
|
|
|
57
87
|
| `--md-comp-badge-size` | The height and width of the small badge | `6px` |
|
|
58
88
|
| `--md-comp-badge-large-size` | The height and minimum width of the large badge | `16px` |
|
|
59
89
|
| `--md-comp-badge-large-padding` | The horizontal padding used for the large badge | `4px` |
|
|
90
|
+
| `--md-comp-badge-trailing-space` | The gap between the label and a trailing badge | `4px` |
|
|
60
91
|
| `--md-comp-badge-color` | The background color of the badge | `--md-sys-color-error` |
|
|
61
92
|
| `--md-comp-badge-large-label-text-color` | The text color of the large badge | `--md-sys-color-on-error` |
|
|
62
93
|
|
|
@@ -69,4 +100,4 @@ Each badge can be themed with CSS custom properties that follow the Material Des
|
|
|
69
100
|
```
|
|
70
101
|
|
|
71
102
|
## Compatibility
|
|
72
|
-
This component uses **anchor positioning**, a modern CSS feature that may not be
|
|
103
|
+
This component uses **anchor positioning**, a modern CSS feature that may not be supported in older browsers; without it the badge is placed where it occurs in the markup. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility) for details.
|
|
@@ -26,22 +26,23 @@
|
|
|
26
26
|
.micl-badge {
|
|
27
27
|
--_size: var(--md-comp-badge-size, 6px);
|
|
28
28
|
--_large-size: var(--md-comp-badge-large-size, 16px);
|
|
29
|
+
--_icon: var(--md-sys-icon-size, 24px);
|
|
29
30
|
--_dx: var(--md-comp-badge-inline-offset, 0px);
|
|
30
31
|
--_dy: var(--md-comp-badge-block-offset, 0px);
|
|
31
32
|
|
|
32
33
|
box-sizing: border-box;
|
|
33
|
-
position:
|
|
34
|
-
inset:
|
|
34
|
+
position: absolute;
|
|
35
|
+
inset: auto;
|
|
35
36
|
margin: 0;
|
|
36
|
-
overflow
|
|
37
|
+
overflow: clip;
|
|
37
38
|
background-color: var(--md-comp-badge-color, var(--md-sys-color-error));
|
|
38
39
|
color: var(--md-comp-badge-large-label-text-color, var(--md-sys-color-on-error));
|
|
39
40
|
|
|
40
41
|
&.micl-badge--small {
|
|
41
42
|
inline-size: var(--_size);
|
|
42
43
|
block-size: var(--_size);
|
|
43
|
-
inset-block-start: calc(anchor(
|
|
44
|
-
inset-inline-start: calc(anchor(
|
|
44
|
+
inset-block-start: calc(anchor(center) - var(--_icon) / 2 + var(--_dy));
|
|
45
|
+
inset-inline-start: calc(anchor(center) + var(--_icon) / 2 - var(--_size) + var(--_dx));
|
|
45
46
|
padding: 0;
|
|
46
47
|
border-radius: calc(var(--_size) / 2);
|
|
47
48
|
}
|
|
@@ -52,24 +53,17 @@
|
|
|
52
53
|
max-inline-size: 34px;
|
|
53
54
|
min-inline-size: var(--_large-size);
|
|
54
55
|
block-size: var(--_large-size);
|
|
55
|
-
inset-block-start: calc(anchor(
|
|
56
|
-
inset-inline-start: calc(anchor(
|
|
56
|
+
inset-block-start: calc(anchor(center) - var(--_icon) / 2 - (var(--_large-size) - 14px) + var(--_dy));
|
|
57
|
+
inset-inline-start: calc(anchor(center) + var(--_icon) / 2 - 12px + var(--_dx));
|
|
57
58
|
padding-inline: var(--md-comp-badge-large-padding, 4px);
|
|
58
59
|
border-radius: calc(var(--_large-size) / 2);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
margin-
|
|
67
|
-
|
|
68
|
-
&.micl-badge--small {
|
|
69
|
-
margin-inline-start: calc(var(--md-sys-icon-size, 24px) - var(--_size) - var(--_dx));
|
|
70
|
-
}
|
|
71
|
-
&:not(.micl-badge--small) {
|
|
72
|
-
margin-inline-start: calc(12px - var(--_dx));
|
|
73
|
-
}
|
|
61
|
+
&.micl-badge--trailing {
|
|
62
|
+
position: static;
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
align-self: center;
|
|
67
|
+
margin-inline-start: var(--md-comp-badge-trailing-space, 4px);
|
|
74
68
|
}
|
|
75
69
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Bottom sheet
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
This component implements the [Material Design 3 Expressive Bottom sheet](https://m3.material.io/components/bottom-sheets/overview) specification. Bottom sheets display secondary content anchored to the bottom of the screen.
|
|
3
4
|
|
|
4
5
|
## Basic Usage
|
|
5
6
|
|
|
@@ -21,13 +22,14 @@ Import the bottom sheet styles into your project:
|
|
|
21
22
|
@use "material-inspired-component-library/dist/bottomsheet";
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Alternatively, import all MICL styles at once:
|
|
26
|
+
|
|
25
27
|
```CSS
|
|
26
28
|
@use "material-inspired-component-library/styles";
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
### JavaScript
|
|
30
|
-
This component requires JavaScript to support **resizable** bottom sheets. The library
|
|
32
|
+
This component requires JavaScript to support **resizable** bottom sheets. The library automatically initializes new components as they are added to the DOM.
|
|
31
33
|
|
|
32
34
|
```JavaScript
|
|
33
35
|
import micl from "material-inspired-component-library/dist/micl";
|
|
@@ -37,7 +39,9 @@ import micl from "material-inspired-component-library/dist/micl";
|
|
|
37
39
|
A live example of the [Bottom sheet component](https://henkpb.github.io/micl/bottomsheet.html) is available to interact with.
|
|
38
40
|
|
|
39
41
|
## Variants
|
|
40
|
-
|
|
42
|
+
|
|
43
|
+
### Co-Existing Bottom Sheet
|
|
44
|
+
Setting the `popover` attribute to `manual` allows a bottom sheet to remain open while the user interacts with the rest of the page. Because clicking outside the sheet will not dismiss it, you must provide a dedicated close button.
|
|
41
45
|
|
|
42
46
|
```HTML
|
|
43
47
|
<dialog id="mybottomsheet" class="micl-bottomsheet" popover="manual">
|
|
@@ -47,10 +51,16 @@ Setting the `popover` attribute to `manual` lets a bottom sheet co-exist and be
|
|
|
47
51
|
</dialog>
|
|
48
52
|
```
|
|
49
53
|
|
|
54
|
+
To open or close a manual popover bottom sheet, link a button to it using the `popovertarget` attribute:
|
|
55
|
+
|
|
56
|
+
```HTML
|
|
57
|
+
<button type="button" popovertarget="mybottomsheet">Open Bottom Sheet</button>
|
|
58
|
+
```
|
|
59
|
+
|
|
50
60
|
### Modal Bottom Sheet
|
|
51
|
-
A **modal** bottom sheet blocks access to the rest of the page and must be dismissed
|
|
61
|
+
A **modal** bottom sheet blocks access to the rest of the page and must be explicitly dismissed by the user. This is suitable for critical tasks or information requiring the user's full attention.
|
|
52
62
|
|
|
53
|
-
To create a modal bottom sheet, use the `<dialog>` element without the `popover` attribute. Add the `closedby="any"` to allow the bottom sheet to be dismissed
|
|
63
|
+
To create a modal bottom sheet, use the `<dialog>` element *without* the `popover` attribute. Add the `closedby="any"` attribute to allow the bottom sheet to be dismissed when the user clicks the backdrop.
|
|
54
64
|
|
|
55
65
|
```HTML
|
|
56
66
|
<dialog id="mybottomsheet" class="micl-bottomsheet" closedby="any">
|
|
@@ -60,14 +70,15 @@ To create a modal bottom sheet, use the `<dialog>` element without the `popover`
|
|
|
60
70
|
</dialog>
|
|
61
71
|
```
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
Because a modal bottom sheet is not a popover, `popovertarget` will not work. Use the `command` and `commandfor` attributes instead:
|
|
64
74
|
|
|
65
75
|
```HTML
|
|
66
|
-
<button type="button"
|
|
76
|
+
<button type="button" command="show-modal" commandfor="mybottomsheet">Open Bottom Sheet</button>
|
|
77
|
+
<button type="button" command="close" commandfor="mybottomsheet">Close</button>
|
|
67
78
|
```
|
|
68
79
|
|
|
69
80
|
### Resizable Bottom Sheet
|
|
70
|
-
To create a **resizable** bottom sheet,
|
|
81
|
+
To create a **resizable** bottom sheet, add a `micl-bottomsheet__headline` drag area above the content. While the visual drag-handle inside this area is optional, it is required for keyboard resizing and provides the only visual cue that the sheet is resizable.
|
|
71
82
|
|
|
72
83
|
```HTML
|
|
73
84
|
<dialog id="mybottomsheet" class="micl-bottomsheet" popover>
|
|
@@ -80,38 +91,58 @@ To create a **resizable** bottom sheet, include a heading and an optional drag-h
|
|
|
80
91
|
</dialog>
|
|
81
92
|
```
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
Upon opening, the bottom sheet defaults to the height of its content, capped at 50% of the screen's height. When resized, it can expand to the full height of the screen minus the `--md-comp-bottomsheet-margin-top` value. You can define specific preset snap points using the `data-miclsnapheights` attribute.
|
|
84
95
|
|
|
85
96
|
```HTML
|
|
86
97
|
<dialog data-miclsnapheights="0,200,420" ...>
|
|
87
98
|
```
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
**Behavior of Snap Heights:**
|
|
101
|
+
* The value `0` equates to closing the bottom sheet.
|
|
102
|
+
* Preset heights exceeding the maximum allowed height are ignored.
|
|
103
|
+
* Activating the drag-handle via keyboard cycles through the initial height and all valid preset heights in ascending order.
|
|
104
|
+
* When dragging via pointer, the sheet snaps to the nearest preset upon release.
|
|
105
|
+
* Dragging the sheet below 48px automatically closes it.
|
|
106
|
+
|
|
107
|
+
|
|
90
108
|
|
|
91
109
|
## Theming
|
|
92
|
-
|
|
110
|
+
|
|
111
|
+
Each bottom sheet style can be themed using CSS custom properties aligned with the Material Design 3 component-token naming convention. Declare these properties on the component itself, or on an appropriate parent element to affect all child bottom sheets.
|
|
93
112
|
|
|
94
113
|
| Custom property | Meaning | Default |
|
|
95
|
-
|
|
96
|
-
| `--md-comp-bottomsheet-height` | The height of the opened bottom sheet (managed
|
|
97
|
-
| `--md-comp-bottomsheet-margin` | The space between the
|
|
98
|
-
| `--md-comp-bottomsheet-margin-top` | The minimum space between the
|
|
99
|
-
| `--md-comp-bottomsheet-padding` | The
|
|
100
|
-
| `--md-comp-bottomsheet-container-color` | The background color
|
|
101
|
-
| `--md-comp-bottomsheet-container-shape` | The corner rounding
|
|
102
|
-
| `--md-comp-bottomsheet-container-elevation` | The shadow (elevation)
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `--md-comp-bottomsheet-height` | The height of the opened bottom sheet (managed dynamically for resizable sheets) | `max-content` |
|
|
116
|
+
| `--md-comp-bottomsheet-margin` | The space between the sheet and screen edges (medium breakpoint and wider) | `56px` |
|
|
117
|
+
| `--md-comp-bottomsheet-margin-top` | The minimum space between the sheet and the top of the screen | `72px` |
|
|
118
|
+
| `--md-comp-bottomsheet-padding` | The padding between the sheet edges and its content | `24px` |
|
|
119
|
+
| `--md-comp-bottomsheet-container-color` | The background color | `--md-sys-color-surface-container-low` |
|
|
120
|
+
| `--md-comp-bottomsheet-container-shape` | The corner rounding | `--md-sys-shape-corner-extra-large-top` |
|
|
121
|
+
| `--md-comp-bottomsheet-container-elevation` | The shadow depth (elevation) | `--md-sys-elevation-level1` |
|
|
103
122
|
| `--md-comp-bottomsheet-drag-handle-width` | The width of the drag handle | `32px` |
|
|
104
123
|
| `--md-comp-bottomsheet-drag-handle-height` | The height of the drag handle | `4px` |
|
|
105
124
|
| `--md-comp-bottomsheet-drag-handle-color` | The color of the drag handle | `--md-sys-color-on-surface-variant` |
|
|
106
125
|
| `--md-comp-bottomsheet-focus-indicator-color` | The color of the drag handle's keyboard-focus outline | `--md-sys-color-secondary` |
|
|
126
|
+
| `--md-comp-bottomsheet-motion-spatial` | The easing function for height changes (uses a spring animation that slightly overshoots before settling) | `--md-sys-motion-expressive-slow-spatial` |
|
|
127
|
+
| `--md-comp-bottomsheet-motion-duration` | Animation duration for opening and resizing | `650ms` |
|
|
128
|
+
| `--md-comp-bottomsheet-motion-duration-reverse` | Animation duration for closing | `500ms` |
|
|
107
129
|
|
|
108
130
|
**Example: Changing the background color of the bottom sheet**
|
|
109
131
|
|
|
110
132
|
```HTML
|
|
111
|
-
<dialog class="micl-bottomsheet" popover style="--md-comp-bottomsheet-container-color:var(--md-sys-color-surface-container)">
|
|
133
|
+
<dialog class="micl-bottomsheet" popover style="--md-comp-bottomsheet-container-color: var(--md-sys-color-surface-container);">
|
|
112
134
|
...
|
|
113
135
|
</dialog>
|
|
114
136
|
```
|
|
115
137
|
|
|
138
|
+
## Accessibility
|
|
139
|
+
|
|
140
|
+
* **Always label the bottom sheet:** Point `aria-labelledby` to the heading inside it, or provide an `aria-label` directly on the `<dialog>` if there is no visible heading.
|
|
141
|
+
* **Label the drag-handle:** Because it is an icon-only button, it requires a descriptive `aria-label` (e.g., "Resize bottom sheet").
|
|
142
|
+
* **Ensure keyboard accessibility:** A drag area without a handle can only be resized via pointer. Always include the drag-handle or provide alternative UI controls to adjust the sheet's height.
|
|
143
|
+
* **Provide a clear exit:** Modal and `popover="manual"` bottom sheets are not dismissed by clicking outside of them; you must include a dedicated close button within the sheet.
|
|
144
|
+
* **Respect motion preferences:** All motion animations are automatically disabled if the user has requested reduced motion at the OS level (the sheet will appear, resize, and close instantly).
|
|
145
|
+
|
|
116
146
|
## Compatibility
|
|
117
|
-
|
|
147
|
+
|
|
148
|
+
This component relies on the Popover API and the `interpolate-size` CSS property (which animates the height between zero and `max-content`). Review browser compatibility grids for the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) and [interpolate-size](https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size) if you are targeting older browsers.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
20
|
// SOFTWARE.
|
|
21
21
|
|
|
22
|
+
@use '../../foundations/layout/tokens' as layout;
|
|
22
23
|
@use '../../styles/elevation';
|
|
23
24
|
@use '../../styles/motion';
|
|
24
25
|
@use '../../styles/shape';
|
|
@@ -33,47 +34,55 @@
|
|
|
33
34
|
@include statelayer.token('focus-indicator-outer-offset');
|
|
34
35
|
@include statelayer.token('backdrop-opacity');
|
|
35
36
|
|
|
37
|
+
@property --micl-bottomsheet-scroll {
|
|
38
|
+
syntax: '<custom-ident>';
|
|
39
|
+
inherits: true;
|
|
40
|
+
initial-value: hidden;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
dialog.micl-bottomsheet {
|
|
37
|
-
--md-comp-bottomsheet-height
|
|
38
|
-
--md-comp-bottomsheet-margin
|
|
39
|
-
--md-comp-bottomsheet-margin-top
|
|
40
|
-
--md-comp-bottomsheet-padding
|
|
41
|
-
--md-comp-bottomsheet-drag-handle-width
|
|
42
|
-
--md-comp-bottomsheet-drag-handle-height
|
|
43
|
-
--md-comp-bottomsheet-motion-spatial
|
|
44
|
-
--md-comp-bottomsheet-motion-duration
|
|
45
|
-
--md-comp-bottomsheet-motion-duration-reverse
|
|
46
|
-
|
|
47
|
-
--micl-duration: var(--
|
|
44
|
+
--_height: var(--md-comp-bottomsheet-height, max-content);
|
|
45
|
+
--_margin: var(--md-comp-bottomsheet-margin, 56px);
|
|
46
|
+
--_margin-top: var(--md-comp-bottomsheet-margin-top, 72px);
|
|
47
|
+
--_padding: var(--md-comp-bottomsheet-padding, 24px);
|
|
48
|
+
--_drag-handle-width: var(--md-comp-bottomsheet-drag-handle-width, 32px);
|
|
49
|
+
--_drag-handle-height: var(--md-comp-bottomsheet-drag-handle-height, 4px);
|
|
50
|
+
--_motion-spatial: var(--md-comp-bottomsheet-motion-spatial, #{motion.$md-sys-motion-expressive-slow-spatial});
|
|
51
|
+
--_motion-duration: var(--md-comp-bottomsheet-motion-duration, #{motion.$md-sys-motion-expressive-slow-spatial-duration});
|
|
52
|
+
--_motion-duration-reverse: var(--md-comp-bottomsheet-motion-duration-reverse, #{motion.$md-sys-motion-expressive-default-spatial-duration});
|
|
53
|
+
|
|
54
|
+
--micl-duration: var(--_motion-duration-reverse);
|
|
48
55
|
--micl-easing: #{motion.$md-sys-motion-easing-emphasized-accelerate};
|
|
56
|
+
--micl-bottomsheet-scroll: hidden;
|
|
57
|
+
--_settle-delay: 0s;
|
|
49
58
|
|
|
50
59
|
box-sizing: border-box;
|
|
51
60
|
position: fixed;
|
|
52
61
|
inset-block: auto 0;
|
|
62
|
+
inset-inline: 0;
|
|
53
63
|
block-size: 0;
|
|
54
|
-
max-block-size:
|
|
64
|
+
max-block-size: 50dvb;
|
|
55
65
|
inline-size: 100%;
|
|
56
|
-
|
|
57
|
-
&[data-miclfitheight] {
|
|
58
|
-
max-block-size: calc(100vh - var(--md-comp-bottomsheet-margin-top));
|
|
59
|
-
}
|
|
60
|
-
min-inline-size: 100%;
|
|
61
|
-
margin: var(--md-comp-bottomsheet-margin-top) 0 0 0 ;
|
|
66
|
+
margin: 0;
|
|
62
67
|
padding: 0;
|
|
63
68
|
border: none;
|
|
64
69
|
border-radius: var(--md-comp-bottomsheet-container-shape, var(--md-sys-shape-corner-extra-large-top));
|
|
65
70
|
background-color: var(--md-comp-bottomsheet-container-color, var(--md-sys-color-surface-container-low));
|
|
66
71
|
box-shadow: var(--md-comp-bottomsheet-container-elevation, var(--md-sys-elevation-level1));
|
|
67
72
|
opacity: 0;
|
|
68
|
-
overflow
|
|
73
|
+
overflow: hidden;
|
|
69
74
|
interpolate-size: allow-keywords;
|
|
70
75
|
|
|
76
|
+
&[data-miclfitheight] {
|
|
77
|
+
max-block-size: calc(100dvb - var(--_margin-top));
|
|
78
|
+
}
|
|
71
79
|
&:not(.micl-bottomsheet--resizing) {
|
|
72
80
|
transition:
|
|
73
81
|
opacity var(--micl-duration) var(--micl-easing),
|
|
74
|
-
block-size var(--micl-duration) var(--
|
|
82
|
+
block-size var(--micl-duration) var(--_motion-spatial),
|
|
75
83
|
overlay var(--micl-duration) linear allow-discrete,
|
|
76
|
-
display var(--micl-duration) linear allow-discrete
|
|
84
|
+
display var(--micl-duration) linear allow-discrete,
|
|
85
|
+
--micl-bottomsheet-scroll 0s var(--_settle-delay) allow-discrete;
|
|
77
86
|
}
|
|
78
87
|
.micl-bottomsheet__headline {
|
|
79
88
|
box-sizing: border-box;
|
|
@@ -81,18 +90,18 @@ dialog.micl-bottomsheet {
|
|
|
81
90
|
flex-shrink: 0;
|
|
82
91
|
align-items: center;
|
|
83
92
|
inline-size: 100%;
|
|
84
|
-
block-size: var(--md-sys-target-size, 48px);
|
|
93
|
+
block-size: var(--md-sys-state-target-size, 48px);
|
|
85
94
|
justify-content: center;
|
|
86
95
|
cursor: grab;
|
|
87
96
|
touch-action: none;
|
|
97
|
+
-webkit-tap-highlight-color: transparent;
|
|
88
98
|
|
|
89
99
|
.micl-bottomsheet__draghandle {
|
|
90
100
|
box-sizing: content-box;
|
|
91
|
-
inline-size: var(--
|
|
92
|
-
block-size: var(--
|
|
93
|
-
padding: 16px calc((var(--md-sys-target-size, 48px) - var(--
|
|
101
|
+
inline-size: var(--_drag-handle-width);
|
|
102
|
+
block-size: var(--_drag-handle-height);
|
|
103
|
+
padding: 16px calc((var(--md-sys-state-target-size, 48px) - var(--_drag-handle-width)) / 2);
|
|
94
104
|
border: none;
|
|
95
|
-
// only shapes the focus ring; the visible handle is the ::before below
|
|
96
105
|
border-radius: var(--md-sys-shape-corner-extra-large);
|
|
97
106
|
background: none;
|
|
98
107
|
cursor: pointer;
|
|
@@ -101,7 +110,7 @@ dialog.micl-bottomsheet {
|
|
|
101
110
|
content: '';
|
|
102
111
|
display: block;
|
|
103
112
|
block-size: 100%;
|
|
104
|
-
border-radius: calc(var(--
|
|
113
|
+
border-radius: calc(var(--_drag-handle-height) / 2);
|
|
105
114
|
background-color: var(--md-comp-bottomsheet-drag-handle-color, var(--md-sys-color-on-surface-variant));
|
|
106
115
|
}
|
|
107
116
|
&:focus-visible {
|
|
@@ -110,14 +119,14 @@ dialog.micl-bottomsheet {
|
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
|
-
&.micl-bottomsheet--resizing .micl-bottomsheet__headline {
|
|
114
|
-
cursor: grabbing;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
122
|
.micl-bottomsheet__content {
|
|
118
123
|
flex: 1 1 auto;
|
|
119
|
-
padding: 0 var(--
|
|
120
|
-
overflow: hidden
|
|
124
|
+
padding: 0 var(--_padding) var(--_padding) var(--_padding);
|
|
125
|
+
overflow: hidden var(--micl-bottomsheet-scroll);
|
|
126
|
+
|
|
127
|
+
&:first-child {
|
|
128
|
+
padding-block-start: var(--_padding);
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
&::backdrop {
|
|
@@ -129,17 +138,20 @@ dialog.micl-bottomsheet {
|
|
|
129
138
|
}
|
|
130
139
|
&:popover-open,
|
|
131
140
|
&[open] {
|
|
132
|
-
--micl-duration: var(--
|
|
141
|
+
--micl-duration: var(--_motion-duration);
|
|
133
142
|
--micl-easing: #{motion.$md-sys-motion-easing-emphasized-decelerate};
|
|
143
|
+
--micl-bottomsheet-scroll: auto;
|
|
144
|
+
--_settle-delay: var(--_motion-duration);
|
|
134
145
|
|
|
135
146
|
display: flex;
|
|
136
147
|
flex-direction: column;
|
|
137
|
-
block-size: var(--
|
|
138
|
-
opacity:
|
|
148
|
+
block-size: var(--_height);
|
|
149
|
+
opacity: 1;
|
|
139
150
|
|
|
140
151
|
@starting-style {
|
|
141
152
|
block-size: 0;
|
|
142
153
|
opacity: 0;
|
|
154
|
+
--micl-bottomsheet-scroll: hidden;
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
&[open]::backdrop {
|
|
@@ -149,15 +161,20 @@ dialog.micl-bottomsheet {
|
|
|
149
161
|
background-color: rgba(0, 0, 0, 0);
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
|
-
|
|
164
|
+
&.micl-bottomsheet--resizing {
|
|
165
|
+
--micl-bottomsheet-scroll: hidden;
|
|
153
166
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
.micl-bottomsheet__headline {
|
|
168
|
+
cursor: grabbing;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@media (prefers-reduced-motion: reduce) {
|
|
173
|
+
--_motion-duration: 0ms;
|
|
174
|
+
--_motion-duration-reverse: 0ms;
|
|
175
|
+
}
|
|
176
|
+
@media (min-width: layout.$md-sys-breakpoint-medium-min) {
|
|
177
|
+
inline-size: min(100% - (2 * var(--_margin)), 640px);
|
|
178
|
+
margin-inline: auto;
|
|
162
179
|
}
|
|
163
180
|
}
|
|
@@ -48,8 +48,8 @@ $focused: ':is(:focus-visible, :has(input:focus-visible))';
|
|
|
48
48
|
@include typography.scale('label-large');
|
|
49
49
|
|
|
50
50
|
%micl-button-base {
|
|
51
|
-
--md-comp-button-motion-effects
|
|
52
|
-
--md-comp-button-motion-duration
|
|
51
|
+
--_motion-effects: var(--md-comp-button-motion-effects, #{motion.$md-sys-motion-expressive-fast-spatial});
|
|
52
|
+
--_motion-duration: var(--md-comp-button-motion-duration, #{motion.$md-sys-motion-expressive-fast-spatial-duration});
|
|
53
53
|
--micl-ripple: 1;
|
|
54
54
|
|
|
55
55
|
box-sizing: border-box;
|
|
@@ -75,10 +75,10 @@ $focused: ':is(:focus-visible, :has(input:focus-visible))';
|
|
|
75
75
|
-webkit-tap-highlight-color: transparent;
|
|
76
76
|
cursor: pointer;
|
|
77
77
|
transition:
|
|
78
|
-
border-radius var(--
|
|
79
|
-
box-shadow var(--
|
|
78
|
+
border-radius var(--_motion-duration) var(--_motion-effects),
|
|
79
|
+
box-shadow var(--_motion-duration) var(--_motion-effects),
|
|
80
80
|
background-size 0ms,
|
|
81
|
-
--statelayer-opacity var(--
|
|
81
|
+
--statelayer-opacity var(--_motion-duration) linear;
|
|
82
82
|
|
|
83
83
|
&#{$disabled} {
|
|
84
84
|
cursor: default;
|
|
@@ -106,7 +106,7 @@ $focused: ':is(:focus-visible, :has(input:focus-visible))';
|
|
|
106
106
|
}
|
|
107
107
|
.micl-button__icon {
|
|
108
108
|
font-variation-settings: 'FILL' 0;
|
|
109
|
-
transition: font-variation-settings var(--
|
|
109
|
+
transition: font-variation-settings var(--_motion-duration) linear;
|
|
110
110
|
}
|
|
111
111
|
&.micl-button--toggle:not([aria-pressed="true"]) .micl-button__icon::before {
|
|
112
112
|
content: attr(data-miclicon);
|
|
@@ -122,10 +122,10 @@ $focused: ':is(:focus-visible, :has(input:focus-visible))';
|
|
|
122
122
|
&::before {
|
|
123
123
|
content: "";
|
|
124
124
|
position: absolute;
|
|
125
|
-
block-size: var(--md-sys-target-size, 48px);
|
|
125
|
+
block-size: var(--md-sys-state-target-size, 48px);
|
|
126
126
|
inline-size: 100%;
|
|
127
|
-
min-inline-size: var(--md-sys-target-size, 48px);
|
|
128
|
-
inset: calc((var(--micl-height) - var(--md-sys-target-size, 48px)) / 2) 0 0 calc((min(100%, var(--md-sys-target-size, 48px)) - var(--md-sys-target-size, 48px)) / 2);
|
|
127
|
+
min-inline-size: var(--md-sys-state-target-size, 48px);
|
|
128
|
+
inset: calc((var(--micl-height) - var(--md-sys-state-target-size, 48px)) / 2) 0 0 calc((min(100%, var(--md-sys-state-target-size, 48px)) - var(--md-sys-state-target-size, 48px)) / 2);
|
|
129
129
|
background-color: transparent;
|
|
130
130
|
}
|
|
131
131
|
}
|