material-inspired-component-library 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.gitattributes +9 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/components/README.md +12 -0
- package/components/accordion/README.md +94 -0
- package/components/bottomsheet/README.md +77 -0
- package/components/bottomsheet/bottomsheet.scss +134 -0
- package/components/bottomsheet/index.ts +152 -0
- package/components/button/README.md +92 -0
- package/components/button/button.scss +515 -0
- package/components/button/index.ts +73 -0
- package/components/card/README.md +125 -0
- package/components/card/card.scss +261 -0
- package/components/checkbox/README.md +62 -0
- package/components/checkbox/checkbox.scss +275 -0
- package/components/checkbox/index.ts +48 -0
- package/components/dialog/README.md +133 -0
- package/components/dialog/dialog.scss +262 -0
- package/components/divider/README.md +52 -0
- package/components/divider/divider.scss +74 -0
- package/components/iconbutton/README.md +86 -0
- package/components/iconbutton/iconbutton.scss +461 -0
- package/components/iconbutton/index.ts +73 -0
- package/components/list/README.md +176 -0
- package/components/list/index.ts +108 -0
- package/components/list/list.scss +295 -0
- package/components/menu/README.md +96 -0
- package/components/menu/index.ts +77 -0
- package/components/menu/menu.scss +124 -0
- package/components/radio/README.md +53 -0
- package/components/radio/radio.scss +138 -0
- package/components/select/README.md +84 -0
- package/components/select/select.scss +122 -0
- package/components/sidesheet/README.md +99 -0
- package/components/sidesheet/sidesheet.scss +162 -0
- package/components/slider/README.md +69 -0
- package/components/slider/index.ts +114 -0
- package/components/slider/slider.scss +258 -0
- package/components/switch/README.md +49 -0
- package/components/switch/switch.scss +176 -0
- package/components/textfield/README.md +75 -0
- package/components/textfield/index.ts +81 -0
- package/components/textfield/textfield.scss +387 -0
- package/components.ts +169 -0
- package/dist/bottomsheet.css +1 -0
- package/dist/bottomsheet.js +0 -0
- package/dist/button.css +1 -0
- package/dist/button.js +0 -0
- package/dist/card.css +1 -0
- package/dist/card.js +0 -0
- package/dist/checkbox.css +1 -0
- package/dist/checkbox.js +0 -0
- package/dist/dialog.css +1 -0
- package/dist/dialog.js +0 -0
- package/dist/divider.css +1 -0
- package/dist/divider.js +0 -0
- package/dist/iconbutton.css +1 -0
- package/dist/iconbutton.js +0 -0
- package/dist/list.css +1 -0
- package/dist/list.js +0 -0
- package/dist/menu.css +1 -0
- package/dist/menu.js +0 -0
- package/dist/micl.css +1 -0
- package/dist/micl.js +1 -0
- package/dist/radio.css +1 -0
- package/dist/radio.js +0 -0
- package/dist/select.css +1 -0
- package/dist/select.js +0 -0
- package/dist/sidesheet.css +1 -0
- package/dist/sidesheet.js +0 -0
- package/dist/slider.css +1 -0
- package/dist/slider.js +0 -0
- package/dist/switch.css +1 -0
- package/dist/switch.js +0 -0
- package/dist/textfield.css +1 -0
- package/dist/textfield.js +0 -0
- package/docs/accordion.html +285 -0
- package/docs/bottomsheet.html +162 -0
- package/docs/button.html +206 -0
- package/docs/card-awards.webp +0 -0
- package/docs/card-cabinet.webp +0 -0
- package/docs/card-city.webp +0 -0
- package/docs/card-fingerprint.webp +0 -0
- package/docs/card-holiday.webp +0 -0
- package/docs/card-names.webp +0 -0
- package/docs/card.html +91 -0
- package/docs/checkbox.html +99 -0
- package/docs/dialog.html +153 -0
- package/docs/divider.html +103 -0
- package/docs/docs.css +34 -0
- package/docs/docs.js +69 -0
- package/docs/iconbutton.html +197 -0
- package/docs/index.html +319 -0
- package/docs/list.html +224 -0
- package/docs/menu.html +143 -0
- package/docs/micl.css +1 -0
- package/docs/micl.js +1 -0
- package/docs/radio.html +101 -0
- package/docs/select.html +205 -0
- package/docs/sidesheet.html +115 -0
- package/docs/slider.html +72 -0
- package/docs/switch.html +151 -0
- package/docs/textfield.html +151 -0
- package/docs/themes/airblue/dark-hc.css +51 -0
- package/docs/themes/airblue/dark-mc.css +51 -0
- package/docs/themes/airblue/dark.css +51 -0
- package/docs/themes/airblue/light-hc.css +51 -0
- package/docs/themes/airblue/light-mc.css +51 -0
- package/docs/themes/airblue/light.css +51 -0
- package/docs/themes/airblue/theme.css +306 -0
- package/docs/themes/barnred/dark-hc.css +51 -0
- package/docs/themes/barnred/dark-mc.css +51 -0
- package/docs/themes/barnred/dark.css +51 -0
- package/docs/themes/barnred/light-hc.css +51 -0
- package/docs/themes/barnred/light-mc.css +51 -0
- package/docs/themes/barnred/light.css +51 -0
- package/docs/themes/barnred/theme.css +306 -0
- package/docs/themes/citrine/dark-hc.css +51 -0
- package/docs/themes/citrine/dark-mc.css +51 -0
- package/docs/themes/citrine/dark.css +51 -0
- package/docs/themes/citrine/light-hc.css +51 -0
- package/docs/themes/citrine/light-mc.css +51 -0
- package/docs/themes/citrine/light.css +51 -0
- package/docs/themes/citrine/theme.css +306 -0
- package/docs/themes/olivegreen/dark-hc.css +51 -0
- package/docs/themes/olivegreen/dark-mc.css +51 -0
- package/docs/themes/olivegreen/dark.css +51 -0
- package/docs/themes/olivegreen/light-hc.css +51 -0
- package/docs/themes/olivegreen/light-mc.css +51 -0
- package/docs/themes/olivegreen/light.css +51 -0
- package/docs/themes/olivegreen/theme.css +306 -0
- package/package.json +62 -0
- package/styles/README.md +99 -0
- package/styles/elevation.scss +36 -0
- package/styles/motion.scss +124 -0
- package/styles/ripple.scss +50 -0
- package/styles/shapes.scss +46 -0
- package/styles/statelayer.scss +42 -0
- package/styles/typography.scss +568 -0
- package/styles.scss +43 -0
- package/themes/README.md +57 -0
- package/themes/airblue/dark-hc.css +51 -0
- package/themes/airblue/dark-mc.css +51 -0
- package/themes/airblue/dark.css +51 -0
- package/themes/airblue/light-hc.css +51 -0
- package/themes/airblue/light-mc.css +51 -0
- package/themes/airblue/light.css +51 -0
- package/themes/airblue/theme.css +306 -0
- package/themes/barnred/dark-hc.css +51 -0
- package/themes/barnred/dark-mc.css +51 -0
- package/themes/barnred/dark.css +51 -0
- package/themes/barnred/light-hc.css +51 -0
- package/themes/barnred/light-mc.css +51 -0
- package/themes/barnred/light.css +51 -0
- package/themes/barnred/theme.css +306 -0
- package/themes/citrine/dark-hc.css +51 -0
- package/themes/citrine/dark-mc.css +51 -0
- package/themes/citrine/dark.css +51 -0
- package/themes/citrine/light-hc.css +51 -0
- package/themes/citrine/light-mc.css +51 -0
- package/themes/citrine/light.css +51 -0
- package/themes/citrine/theme.css +306 -0
- package/themes/olivegreen/dark-hc.css +51 -0
- package/themes/olivegreen/dark-mc.css +51 -0
- package/themes/olivegreen/dark.css +51 -0
- package/themes/olivegreen/light-hc.css +51 -0
- package/themes/olivegreen/light-mc.css +51 -0
- package/themes/olivegreen/light.css +51 -0
- package/themes/olivegreen/theme.css +306 -0
- package/tsconfig.json +110 -0
- package/webpack.config.js +49 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# List
|
|
2
|
+
This component implements the the [Material Design 3 Expressive List](https://m3.material.io/components/lists/overview) design. Lists are continuous, vertical groups of text or images, representing a set of data.
|
|
3
|
+
|
|
4
|
+
## Basic Usage
|
|
5
|
+
|
|
6
|
+
### HTML
|
|
7
|
+
To create a basic list, use the `<ul>` element with the `micl-list` class and individual `<li>` elements for each list item. For a basic single-line item, use the `micl-list-item-one` class:
|
|
8
|
+
|
|
9
|
+
```HTML
|
|
10
|
+
<ul class="micl-list" role="listbox">
|
|
11
|
+
<li class="micl-list-item-one" tabindex="0">
|
|
12
|
+
<span class="micl-list-item__text">
|
|
13
|
+
<span class="micl-list-item__headline">A single-line item</span>
|
|
14
|
+
</span>
|
|
15
|
+
</li>
|
|
16
|
+
</ul>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- The `role="listbox"` is used for accessibility, indicating a selectable list.
|
|
20
|
+
|
|
21
|
+
- `tabindex="0"` on the `<li>` makes the list item focusable and allows keyboard navigation.
|
|
22
|
+
|
|
23
|
+
### CSS
|
|
24
|
+
Import the list styles into your project:
|
|
25
|
+
|
|
26
|
+
```CSS
|
|
27
|
+
@use "micl/components/list";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### TypeScript
|
|
31
|
+
This component requires a TypeScript module to support keyboard navigation. You can import the specific module or use the main MICL TypeScript library, which handles initialization automatically.
|
|
32
|
+
|
|
33
|
+
To manually initialize the component:
|
|
34
|
+
|
|
35
|
+
```TypeScript
|
|
36
|
+
import miclList from 'micl/components/list';
|
|
37
|
+
|
|
38
|
+
miclList.initialize(document.querySelector('.micl-list'));
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Demo
|
|
42
|
+
A live example of the [List component](https://henkpb.github.io/micl/list.html) is available for you to interact with.
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
The List component offers three CSS classes to control the height and content capacity of individual list items:
|
|
46
|
+
|
|
47
|
+
| CSS class | Description |
|
|
48
|
+
| --------- | ----------- |
|
|
49
|
+
| micl-list-item-one | For single-line items, accommodating one line of text. |
|
|
50
|
+
| micl-list-item-two | For two-line items, accommodating up to two lines of text. |
|
|
51
|
+
| micl-list-item-three | For three-line items, accommodating up to three lines of text. |
|
|
52
|
+
|
|
53
|
+
```HTML
|
|
54
|
+
<ul class="micl-list" role="listbox">
|
|
55
|
+
<li class="micl-list-item-three" tabindex="0">
|
|
56
|
+
<span class="micl-list-item__text">
|
|
57
|
+
<span class="micl-list-item__headline">A three-line item</span>
|
|
58
|
+
<span class="micl-list-item__supporting-text">Supporting text that is long enough to fill up multiple lines of text with words and sentences.</span>
|
|
59
|
+
</span>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Adding the `micl-list-item--disabled` class to the `<li>` element causes the list item to be displayed in a disabled state.
|
|
65
|
+
|
|
66
|
+
Add the `micl-list__divider` class to the `<ul>` element to automatically place a divider between each list item.
|
|
67
|
+
|
|
68
|
+
### Leading Content
|
|
69
|
+
The text content of a list item can be preceded by various media elements:
|
|
70
|
+
|
|
71
|
+
- **Icon**: Use `micl-list-item__icon` with a (Material Symbols) icon.
|
|
72
|
+
```HTML
|
|
73
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
74
|
+
<span class="material-symbols-outlined micl-list-item__icon" aria-hidden="true">person</span>
|
|
75
|
+
<span class="micl-list-item__text">
|
|
76
|
+
<span class="micl-list-item__headline">Bill Jones</span>
|
|
77
|
+
<span class="micl-list-item__supporting-text">bill.jones@email.com</span>
|
|
78
|
+
</span>
|
|
79
|
+
</li>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **Avatar**: Use `micl-list-item__avatar` with an avatar image (or icon).
|
|
83
|
+
```HTML
|
|
84
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
85
|
+
<span class="material-symbols-outlined micl-list-item__avatar">account_circle</span>
|
|
86
|
+
<span class="micl-list-item__text">
|
|
87
|
+
<span class="micl-list-item__headline">Bill Jones</span>
|
|
88
|
+
<span class="micl-list-item__supporting-text">bill.jones@email.com</span>
|
|
89
|
+
</span>
|
|
90
|
+
</li>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- **Image**: Use `micl-list-item__image` with a background image.
|
|
94
|
+
```HTML
|
|
95
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
96
|
+
<span class="micl-list-item__image" style="background-image:url(https://...jpg)"></span>
|
|
97
|
+
<span class="micl-list-item__text">
|
|
98
|
+
<span class="micl-list-item__headline">Bill Jones</span>
|
|
99
|
+
<span class="micl-list-item__supporting-text">bill.jones@email.com</span>
|
|
100
|
+
</span>
|
|
101
|
+
</li>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- **Thumbnail (Video)**: Use `micl-list-item__thumbnail` for video previews with a background-image.
|
|
105
|
+
```HTML
|
|
106
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
107
|
+
<span class="micl-list-item__thumbnail" style="background-image:url(https://...mp4)"></span>
|
|
108
|
+
<span class="micl-list-item__text">
|
|
109
|
+
<span class="micl-list-item__headline">Bill Jones</span>
|
|
110
|
+
<span class="micl-list-item__supporting-text">Short clip of Bill</span>
|
|
111
|
+
</span>
|
|
112
|
+
</li>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Trailing Content
|
|
116
|
+
The text of a list item may be followed by a trailing text or other elements (like a checkbox).
|
|
117
|
+
|
|
118
|
+
```HTML
|
|
119
|
+
<li class="micl-list-item-one" tabindex="0">
|
|
120
|
+
<span class="micl-list-item__text">
|
|
121
|
+
<span class="micl-list-item__headline">To-do items</span>
|
|
122
|
+
</span>
|
|
123
|
+
<span class="micl-list-item__trailing-text">100+</span>
|
|
124
|
+
</li>
|
|
125
|
+
````
|
|
126
|
+
|
|
127
|
+
### Selecting List Items
|
|
128
|
+
To enable selection of list items, integrate a checkbox component within the `<li>` element. The `micl-list-item__text` should typically wrap the headline and supporting text, acting as the label for the checkbox.
|
|
129
|
+
|
|
130
|
+
```HTML
|
|
131
|
+
<ul class="micl-list micl-list__divider" role="listbox">
|
|
132
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
133
|
+
<label for="checkbox1" class="micl-list-item__text">
|
|
134
|
+
<span class="micl-list-item__headline">Blue car</span>
|
|
135
|
+
<span class="micl-list-item__supporting-text">A blue car with four wheels.</span>
|
|
136
|
+
</label>
|
|
137
|
+
<input type="checkbox" id="checkbox1" class="micl-checkbox" value="cb1" tabindex="-1">
|
|
138
|
+
</li>
|
|
139
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
140
|
+
<label for="checkbox2" class="micl-list-item__text">
|
|
141
|
+
<span class="micl-list-item__headline">Red car</span>
|
|
142
|
+
<span class="micl-list-item__supporting-text">A red car with tinted windows.</span>
|
|
143
|
+
</label>
|
|
144
|
+
<input type="checkbox" id="checkbox2" class="micl-checkbox" value="cb2" tabindex="-1">
|
|
145
|
+
</li>
|
|
146
|
+
</ul>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `tabindex="-1"` on the `input` is important here, as the `<li>` should handle the focus for accessibility.
|
|
150
|
+
|
|
151
|
+
## Customizations
|
|
152
|
+
You can customize the appearance of the List component by overriding its global CSS variables. These variables are declared on the `:root` pseudo-class and can be changed on any appropriate parent element to affect its child lists.
|
|
153
|
+
|
|
154
|
+
| Variable name | Default Value | Description |
|
|
155
|
+
| ------------- | ------------- | ----------- |
|
|
156
|
+
| --md-sys-list-item-one-height | 56px | The height for a single-line list item. |
|
|
157
|
+
| --md-sys-list-item-two-height | 72px | The height for a two-line list item. |
|
|
158
|
+
| --md-sys-list-item-three-height | 88px | The height for a three-line list item. |
|
|
159
|
+
| --md-sys-list-item-one-padding | 8px | The vertical padding of a single-line list item. |
|
|
160
|
+
| --md-sys-list-item-two-padding | 8px | The vertical padding of a two-line list item. |
|
|
161
|
+
| --md-sys-list-item-three-padding | 12px | The vertical padding of a three-line list item. |
|
|
162
|
+
| --md-sys-list-item-space | 16px | The horizontal spacing between the elements within a list item. |
|
|
163
|
+
|
|
164
|
+
**Example: Changing the height of single-line list items**
|
|
165
|
+
|
|
166
|
+
```HTML
|
|
167
|
+
<div style="--md-sys-list-item-one-height:60px">
|
|
168
|
+
<ul class="micl-list">
|
|
169
|
+
<li class="micl-list-item-one" tabindex="0">
|
|
170
|
+
<span class="micl-list-item__text">
|
|
171
|
+
<span class="micl-list-item__headline">A single-line item</span>
|
|
172
|
+
</span>
|
|
173
|
+
</li>
|
|
174
|
+
</ul>
|
|
175
|
+
</div>
|
|
176
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2025 Hermana AS
|
|
3
|
+
//
|
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
// in the Software without restriction, including without limitation the rights
|
|
7
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
// furnished to do so, subject to the following conditions:
|
|
10
|
+
//
|
|
11
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
// copies or substantial portions of the Software.
|
|
13
|
+
//
|
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
// SOFTWARE.
|
|
21
|
+
|
|
22
|
+
export const listSelector = '.micl-list-item-one,.micl-list-item-two,.micl-list-item-three';
|
|
23
|
+
|
|
24
|
+
export default (() =>
|
|
25
|
+
{
|
|
26
|
+
const
|
|
27
|
+
isDisabled = (item: HTMLElement | null): boolean => !!item && item.classList.contains('micl-list-item--disabled'),
|
|
28
|
+
isSelected = (item: HTMLElement | null): boolean => !!item && item.matches(':has(input[type=checkbox]:checked)');
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
keydown: (event: Event) =>
|
|
32
|
+
{
|
|
33
|
+
if (
|
|
34
|
+
!(event instanceof KeyboardEvent)
|
|
35
|
+
|| !(event.target instanceof Element)
|
|
36
|
+
|| !event.target.matches('.micl-list-item-one,.micl-list-item-two,.micl-list-item-three')
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const parent = (event.target as Element).parentElement;
|
|
41
|
+
if (!parent) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let items: HTMLElement[] = [];
|
|
46
|
+
if (parent instanceof HTMLDetailsElement) {
|
|
47
|
+
items = Array.from(parent.parentElement?.children || []).map(details => {
|
|
48
|
+
let summary = details.querySelector(':scope > summary') as HTMLElement;
|
|
49
|
+
return isDisabled(summary) ? null : summary;
|
|
50
|
+
}).filter(item => !!item);
|
|
51
|
+
}
|
|
52
|
+
else if (parent instanceof HTMLUListElement) {
|
|
53
|
+
items = Array.from(parent.children).map(li => {
|
|
54
|
+
return ((li instanceof HTMLLIElement) && !isDisabled(li)) ? li : null;
|
|
55
|
+
}).filter(item => !!item);
|
|
56
|
+
}
|
|
57
|
+
if (items.length === 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let currentIndex = items.findIndex(item => isSelected(item));
|
|
62
|
+
if (currentIndex === -1) {
|
|
63
|
+
currentIndex = items.findIndex(item => item.tabIndex === 0);
|
|
64
|
+
}
|
|
65
|
+
if (currentIndex === -1) {
|
|
66
|
+
currentIndex = 0;
|
|
67
|
+
}
|
|
68
|
+
let nextIndex = currentIndex;
|
|
69
|
+
|
|
70
|
+
switch (event.key) {
|
|
71
|
+
case 'ArrowDown':
|
|
72
|
+
nextIndex = (currentIndex + 1) % items.length;
|
|
73
|
+
event.preventDefault(); // prevent page scrolling
|
|
74
|
+
break;
|
|
75
|
+
case 'ArrowUp':
|
|
76
|
+
nextIndex = (currentIndex - 1 + items.length) % items.length;
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
break;
|
|
79
|
+
case 'Tab':
|
|
80
|
+
let selectedIndex = items.findIndex(item => isSelected(item));
|
|
81
|
+
if (selectedIndex === -1) {
|
|
82
|
+
if (currentIndex !== 0) {
|
|
83
|
+
items[currentIndex].tabIndex = -1;
|
|
84
|
+
items[0].tabIndex = 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (selectedIndex !== currentIndex) {
|
|
88
|
+
items[currentIndex].tabIndex = -1;
|
|
89
|
+
items[selectedIndex].tabIndex = 0;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case 'Enter':
|
|
93
|
+
case ' ':
|
|
94
|
+
const cb = (event.target as Element).querySelector('input[type=checkbox].micl-checkbox');
|
|
95
|
+
if (cb instanceof HTMLInputElement) {
|
|
96
|
+
cb.checked = !cb.checked;
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
}
|
|
101
|
+
if (nextIndex !== currentIndex) {
|
|
102
|
+
items[currentIndex].tabIndex = -1;
|
|
103
|
+
items[nextIndex].tabIndex = 0;
|
|
104
|
+
items[nextIndex].focus();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
})();
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2025 Hermana AS
|
|
3
|
+
//
|
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
// in the Software without restriction, including without limitation the rights
|
|
7
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
// furnished to do so, subject to the following conditions:
|
|
10
|
+
//
|
|
11
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
// copies or substantial portions of the Software.
|
|
13
|
+
//
|
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
// SOFTWARE.
|
|
21
|
+
|
|
22
|
+
@use '../../styles/motion';
|
|
23
|
+
@use '../../styles/ripple';
|
|
24
|
+
@use '../../styles/typography';
|
|
25
|
+
|
|
26
|
+
:root {
|
|
27
|
+
--md-sys-list-item-one-height: 56px;
|
|
28
|
+
--md-sys-list-item-two-height: 72px;
|
|
29
|
+
--md-sys-list-item-three-height: 88px;
|
|
30
|
+
--md-sys-list-item-one-padding: 8px;
|
|
31
|
+
--md-sys-list-item-two-padding: 8px;
|
|
32
|
+
--md-sys-list-item-three-padding: 12px;
|
|
33
|
+
--md-sys-list-item-space: 16px;
|
|
34
|
+
--md-sys-list-item-padding-inline: 16px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.micl-list {
|
|
38
|
+
--md-sys-list-item-background-color: var(--md-sys-color-surface);
|
|
39
|
+
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
interpolate-size: allow-keywords;
|
|
43
|
+
list-style-type: none;
|
|
44
|
+
|
|
45
|
+
&> details {
|
|
46
|
+
&::details-content {
|
|
47
|
+
height: 0;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
transition:
|
|
50
|
+
content-visibility var(--md-sys-motion-duration-medium4) allow-discrete,
|
|
51
|
+
height var(--md-sys-motion-duration-medium4) linear(motion.$md-sys-motion-spring-default-spatial);
|
|
52
|
+
}
|
|
53
|
+
&[open]::details-content {
|
|
54
|
+
height: auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&> summary.micl-list-item-one,
|
|
58
|
+
&> summary.micl-list-item-two,
|
|
59
|
+
&> summary.micl-list-item-three {
|
|
60
|
+
position: relative;
|
|
61
|
+
|
|
62
|
+
&::-webkit-details-marker {
|
|
63
|
+
display: none;
|
|
64
|
+
}
|
|
65
|
+
&.micl-list-item--disabled {
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
}
|
|
68
|
+
&:not(.micl-list-item--disabled) {
|
|
69
|
+
--miclripple: 1;
|
|
70
|
+
@include ripple.effect;
|
|
71
|
+
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
.micl-list-item__content {
|
|
76
|
+
box-sizing: border-box;
|
|
77
|
+
padding-inline: var(--md-sys-list-item-padding-inline);
|
|
78
|
+
background-color: var(--md-sys-list-item-background-color);
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&.micl-list__divider {
|
|
84
|
+
.micl-list-item-one + .micl-list-item-one,
|
|
85
|
+
.micl-list-item-two + .micl-list-item-two,
|
|
86
|
+
.micl-list-item-three + .micl-list-item-three,
|
|
87
|
+
details + details {
|
|
88
|
+
border-top: var(--md-sys-divider-thickness) solid var(--md-sys-divider-color);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.micl-list-item-one,
|
|
94
|
+
.micl-list-item-two,
|
|
95
|
+
.micl-list-item-three {
|
|
96
|
+
box-sizing: border-box;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
column-gap: var(--md-sys-list-item-space);
|
|
100
|
+
padding-inline: var(--md-sys-list-item-space);
|
|
101
|
+
border-radius: var(--md-sys-shape-corner-none);
|
|
102
|
+
background-color: var(--md-sys-list-item-background-color);
|
|
103
|
+
list-style: none;
|
|
104
|
+
transition: background-color var(--md-sys-motion-duration-long2) linear;
|
|
105
|
+
|
|
106
|
+
&:disabled,
|
|
107
|
+
&.micl-list-item--disabled {
|
|
108
|
+
.micl-list-item__icon,
|
|
109
|
+
.micl-list-item__text,
|
|
110
|
+
.micl-list-item__text .micl-list-item__headline,
|
|
111
|
+
.micl-list-item__text .micl-list-item__supporting-text,
|
|
112
|
+
.micl-list-item__text::after,
|
|
113
|
+
.micl-list-item__trailing-text {
|
|
114
|
+
color: rgb(from var(--md-sys-color-on-surface) r g b / 38%);
|
|
115
|
+
}
|
|
116
|
+
.micl-list-item__image,
|
|
117
|
+
.micl-list-item__thumbnail {
|
|
118
|
+
opacity: 38%;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
&:not(:disabled):not(.micl-list-item--disabled) {
|
|
122
|
+
&:hover {
|
|
123
|
+
background-color: color-mix(in srgb, var(--md-sys-list-item-background-color), var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity));
|
|
124
|
+
|
|
125
|
+
&:has(input[type=checkbox].micl-checkbox) {
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
}
|
|
128
|
+
.micl-list-item__icon {
|
|
129
|
+
font-variation-settings: 'FILL' 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
&:focus-visible {
|
|
133
|
+
outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
|
|
134
|
+
background-color: color-mix(in srgb, var(--md-sys-list-item-background-color), var(--md-sys-color-on-surface) var(--md-sys-state-focus-state-layer-opacity));
|
|
135
|
+
z-index: 1;
|
|
136
|
+
|
|
137
|
+
.micl-list-item__icon {
|
|
138
|
+
font-variation-settings: 'FILL' 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
&:active {
|
|
142
|
+
background-color: color-mix(in srgb, var(--md-sys-list-item-background-color), var(--md-sys-color-on-surface) var(--md-sys-state-pressed-state-layer-opacity));
|
|
143
|
+
|
|
144
|
+
.micl-list-item__icon {
|
|
145
|
+
font-variation-settings: 'FILL' 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
&:has(input[type=checkbox].micl-checkbox) {
|
|
149
|
+
--miclripple: 1;
|
|
150
|
+
--md-sys-ripple-background-color: var(--md-sys-color-primary);
|
|
151
|
+
@include ripple.effect;
|
|
152
|
+
}
|
|
153
|
+
&:has(input[type=checkbox].micl-checkbox:checked) {
|
|
154
|
+
background-color: var(--md-sys-color-secondary-container);
|
|
155
|
+
|
|
156
|
+
.micl-list-item__headline {
|
|
157
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
158
|
+
}
|
|
159
|
+
.micl-list-item__icon,
|
|
160
|
+
.micl-list-item__supporting-text,
|
|
161
|
+
.micl-list-item__trailing-text {
|
|
162
|
+
color: var(--md-sys-color-on-surface);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
input[type=checkbox].micl-checkbox:not(:disabled):hover,
|
|
166
|
+
input[type=checkbox].micl-checkbox:not(:disabled):active {
|
|
167
|
+
--checkbox-state-layer-color: initial;
|
|
168
|
+
--checkbox-outline-color: initial;
|
|
169
|
+
--md-sys-ripple-background-color: transparent;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
&> a {
|
|
173
|
+
box-sizing: border-box;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
align-self: normal;
|
|
177
|
+
width: 100%;
|
|
178
|
+
column-gap: var(--md-sys-list-item-space);
|
|
179
|
+
text-decoration: none;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.micl-list-item-one {
|
|
184
|
+
min-height: var(--md-sys-list-item-one-height);
|
|
185
|
+
padding-block-start: var(--md-sys-list-item-one-padding);
|
|
186
|
+
padding-block-end: var(--md-sys-list-item-one-padding);
|
|
187
|
+
|
|
188
|
+
&:has(.micl-list-item__thumbnail) {
|
|
189
|
+
padding-block-start: var(--md-sys-list-item-three-padding);
|
|
190
|
+
padding-block-end: var(--md-sys-list-item-three-padding);
|
|
191
|
+
padding-inline-start: 0px;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
.micl-list-item-two {
|
|
195
|
+
min-height: var(--md-sys-list-item-two-height);
|
|
196
|
+
padding-block-start: var(--md-sys-list-item-two-padding);
|
|
197
|
+
padding-block-end: var(--md-sys-list-item-two-padding);
|
|
198
|
+
|
|
199
|
+
&:has(.micl-list-item__thumbnail) {
|
|
200
|
+
padding-inline-start: 0px;
|
|
201
|
+
padding-block-start: 12px;
|
|
202
|
+
padding-block-end: 12px;
|
|
203
|
+
}
|
|
204
|
+
.micl-list-item__supporting-text {
|
|
205
|
+
white-space: nowrap;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
text-overflow: ellipsis;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
.micl-list-item-three {
|
|
211
|
+
min-height: var(--md-sys-list-item-three-height);
|
|
212
|
+
padding-block-start: var(--md-sys-list-item-three-padding);
|
|
213
|
+
padding-block-end: var(--md-sys-list-item-three-padding);
|
|
214
|
+
|
|
215
|
+
&:has(.micl-list-item__thumbnail) {
|
|
216
|
+
padding-block-start: var(--md-sys-list-item-three-padding);
|
|
217
|
+
padding-block-end: var(--md-sys-list-item-three-padding);
|
|
218
|
+
padding-inline-start: 0px;
|
|
219
|
+
}
|
|
220
|
+
.micl-list-item__icon,
|
|
221
|
+
.micl-list-item__avatar,
|
|
222
|
+
.micl-list-item__image {
|
|
223
|
+
align-self: flex-start;
|
|
224
|
+
}
|
|
225
|
+
.micl-list-item__supporting-text {
|
|
226
|
+
display: -webkit-box;
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
-webkit-box-orient: vertical;
|
|
229
|
+
-webkit-line-clamp: 2;
|
|
230
|
+
}
|
|
231
|
+
input[type=checkbox].micl-checkbox {
|
|
232
|
+
align-self: flex-start;
|
|
233
|
+
margin-block-start: -12px;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.micl-list-item__icon {
|
|
238
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
239
|
+
font-variation-settings: 'FILL' 0;
|
|
240
|
+
transition: font-variation-settings var(--md-sys-motion-duration-long2) linear;
|
|
241
|
+
}
|
|
242
|
+
.micl-list-item__avatar {
|
|
243
|
+
font-size: 40px;
|
|
244
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
245
|
+
color: var(--md-sys-color-primary-container);
|
|
246
|
+
}
|
|
247
|
+
.micl-list-item__avatar-text {
|
|
248
|
+
@include typography.title-medium;
|
|
249
|
+
|
|
250
|
+
color: var(--md-sys-on-primary-container);
|
|
251
|
+
}
|
|
252
|
+
.micl-list-item__image {
|
|
253
|
+
display: inline-block;
|
|
254
|
+
width: 56px;
|
|
255
|
+
height: 56px;
|
|
256
|
+
min-width: 56px;
|
|
257
|
+
min-height: 56px;
|
|
258
|
+
border-radius: var(--md-sys-shape-corner-none);
|
|
259
|
+
background-size: cover;
|
|
260
|
+
}
|
|
261
|
+
.micl-list-item__thumbnail {
|
|
262
|
+
display: inline-block;
|
|
263
|
+
width: auto;
|
|
264
|
+
height: 64px;
|
|
265
|
+
min-height: 64px;
|
|
266
|
+
aspect-ratio: 1.5;
|
|
267
|
+
border-radius: var(--md-sys-shape-corner-none);
|
|
268
|
+
background-size: contain;
|
|
269
|
+
background-repeat: no-repeat;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.micl-list-item__text {
|
|
273
|
+
display: flex;
|
|
274
|
+
flex-direction: column;
|
|
275
|
+
flex-basis: 100%;
|
|
276
|
+
min-width: 0;
|
|
277
|
+
text-align: start;
|
|
278
|
+
|
|
279
|
+
.micl-list-item__headline {
|
|
280
|
+
@include typography.body-large;
|
|
281
|
+
|
|
282
|
+
color: var(--md-sys-color-on-surface);
|
|
283
|
+
}
|
|
284
|
+
.micl-list-item__supporting-text {
|
|
285
|
+
@include typography.body-medium;
|
|
286
|
+
|
|
287
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.micl-list-item__trailing-text {
|
|
292
|
+
@include typography.label-small;
|
|
293
|
+
|
|
294
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
295
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
This component implements the [Material Design 3 Expressive Menu](https://m3.material.io/components/menus/overview) design. Menus provide a list of choices on a temporary surface that appears when a user interacts with a control element.
|
|
3
|
+
|
|
4
|
+
## Basic Usage
|
|
5
|
+
|
|
6
|
+
### HTML
|
|
7
|
+
The Menu component is an extension of the [List component](../list/README.md). It consists of a `<nav>` element with the `micl-menu` class, which acts as the container for a `<ul>` with the `micl-list` class. The menu can be opened and closed using a control element with the `popovertarget` attribute.
|
|
8
|
+
|
|
9
|
+
```HTML
|
|
10
|
+
<nav id="mymenu" class="micl-menu" popover>
|
|
11
|
+
<ul class="micl-list">
|
|
12
|
+
<li class="micl-list-item-one" tabindex="0">
|
|
13
|
+
<span class="micl-list-item__text">
|
|
14
|
+
<span class="micl-list-item__headline">Menu item</span>
|
|
15
|
+
</span>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</nav>
|
|
19
|
+
|
|
20
|
+
<button type="button" popovertarget="mymenu">Open Basic Menu</button>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### CSS
|
|
24
|
+
Import the styles for both the menu and list components into your project:
|
|
25
|
+
|
|
26
|
+
```CSS
|
|
27
|
+
@use "micl/components/list";
|
|
28
|
+
@use "micl/components/menu";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### TypeScript
|
|
32
|
+
This component requires the **Menu** TypeScript module for functionality. You can import the specific module and handle initialization manually, or use the main MICL library for automatic initialization.
|
|
33
|
+
|
|
34
|
+
To manually initialize the component:
|
|
35
|
+
|
|
36
|
+
```TypeScript
|
|
37
|
+
import miclMenu from 'micl/components/menu';
|
|
38
|
+
|
|
39
|
+
miclMenu.initialize(document.querySelector('.micl-menu'));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Demo
|
|
43
|
+
A live example of the [Menu component](https://henkpb.github.io/micl/menu.html) is available for you to interact with.
|
|
44
|
+
|
|
45
|
+
## Variants
|
|
46
|
+
Since the Menu component is based on the **List component**, all of its list item variants and content features can be used. You can incorporate icons, avatars, images, multiple lines of text, and more.
|
|
47
|
+
|
|
48
|
+
**Example: A menu with graphics and supporting text**
|
|
49
|
+
|
|
50
|
+
```HTML
|
|
51
|
+
<nav id="mymenu" class="micl-menu" popover>
|
|
52
|
+
<ul class="micl-list">
|
|
53
|
+
<li class="micl-list-item-two" tabindex="0">
|
|
54
|
+
<span class="micl-list-item__icon material-symbols-outlined" aria-hidden="true">home</span>
|
|
55
|
+
<span class="micl-list-item__text">
|
|
56
|
+
<span class="micl-list-item__headline">Home</span>
|
|
57
|
+
<span class="micl-list-item__supporting-text">Click here to go home</span>
|
|
58
|
+
</span>
|
|
59
|
+
</li>
|
|
60
|
+
<li class="micl-list-item-two">
|
|
61
|
+
<span class="micl-list-item__image" style="background-image:url(https://...jpg)"></span>
|
|
62
|
+
<label for="cb" class="micl-list-item__text">
|
|
63
|
+
<span class="micl-list-item__headline">Person</span>
|
|
64
|
+
<span class="micl-list-item__supporting-text">This person is an administrator</span>
|
|
65
|
+
</label>
|
|
66
|
+
<input type="checkbox" id="cb" class="micl-checkbox" tabindex="-1">
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</nav>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Example: A menu with a link**
|
|
73
|
+
To make a menu item function as a link, wrap its content in an `<a>` tag and set the `tabindex` to `-1`. This ensures the link is clickable but does not interfere with menu navigation.
|
|
74
|
+
|
|
75
|
+
```HTML
|
|
76
|
+
<nav id="mymenu" class="micl-menu" popover>
|
|
77
|
+
<ul class="micl-list">
|
|
78
|
+
<li class="micl-list-item-two micl-list-item__divider">
|
|
79
|
+
<a href="https://www.nytimes.com" tabindex="-1">
|
|
80
|
+
<span class="micl-list-item__icon material-symbols-outlined" aria-hidden="true">newspaper</span>
|
|
81
|
+
<span class="micl-list-item__text">
|
|
82
|
+
<span class="micl-list-item__headline">The New York Times</span>
|
|
83
|
+
<span class="micl-list-item__supporting-text">Clicking this item opens the front page of The New York Times</span>
|
|
84
|
+
</span>
|
|
85
|
+
</a>
|
|
86
|
+
</li>
|
|
87
|
+
</ul>
|
|
88
|
+
</nav>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Adding the `micl-list-item--disabled` class to a menu item causes the item to be displayed in a disabled state.
|
|
92
|
+
|
|
93
|
+
Add the `micl-list-item__divider` class to a menu item to create a divider between the item and the previous menu item.
|
|
94
|
+
|
|
95
|
+
## Compatibility
|
|
96
|
+
This component uses **popover anchor positioning** to place the menu next to its invoker. This is a modern CSS feature that may not be fully supported in all browsers. To ensure the menu works in browsers that do not support anchor positioning, wrap the menu and its invoker in a `<div>` element with `position:relative`. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/anchor#browser_compatibility) for details.
|