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,77 @@
|
|
|
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 menuSelector = '.micl-menu[popover]';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set the origin for menu transformations just before transitions start.
|
|
26
|
+
* By default, the origin is "top left" (the menu opens just below the invoker, left aligned),
|
|
27
|
+
* but could also be "top right", "bottom left" or "bottom right".
|
|
28
|
+
* When the browser needs to apply a position-try-fallbacks, because there is not enough space
|
|
29
|
+
* for the menu in the default location, then the reverse transformation will be applied from
|
|
30
|
+
* the wrong origin.
|
|
31
|
+
* Therefore, when the menu is open, calculate the transformation origin just before the
|
|
32
|
+
* transitions start. When the menu is closed, do the same just after the 'display:none' has
|
|
33
|
+
* been removed by the browser (the 'toggle' event has then been triggered).
|
|
34
|
+
*/
|
|
35
|
+
export default (() =>
|
|
36
|
+
{
|
|
37
|
+
const getOrigin = (invoker: Element, popover: Element): string =>
|
|
38
|
+
{
|
|
39
|
+
const invokerY = invoker.getBoundingClientRect().y,
|
|
40
|
+
popoverY = popover.getBoundingClientRect().y,
|
|
41
|
+
oldOrigin = window.getComputedStyle(popover).getPropertyValue('transform-origin');
|
|
42
|
+
|
|
43
|
+
return ((invokerY > popoverY) ? 'bottom ' : 'top ') +
|
|
44
|
+
((parseInt(oldOrigin) > 0) ? 'right' : 'left');
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
initialize: (element: HTMLElement): void =>
|
|
49
|
+
{
|
|
50
|
+
if (
|
|
51
|
+
!element.matches('.micl-menu[popover]')
|
|
52
|
+
|| element.dataset.miclinitialized
|
|
53
|
+
) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
element.dataset.miclinitialized = '1';
|
|
57
|
+
|
|
58
|
+
const invoker = document.querySelector(`[popovertarget="${element.id}"]`);
|
|
59
|
+
|
|
60
|
+
invoker && element.addEventListener('beforetoggle', event =>
|
|
61
|
+
{
|
|
62
|
+
if ((event as ToggleEvent).oldState === 'open') {
|
|
63
|
+
// The popover is about to be closed.
|
|
64
|
+
element.style.transformOrigin = getOrigin(invoker, element);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
invoker && element.addEventListener('toggle', event =>
|
|
68
|
+
{
|
|
69
|
+
if ((event as ToggleEvent).oldState === 'closed') {
|
|
70
|
+
// The popover has just opened.
|
|
71
|
+
element.style.transformOrigin = getOrigin(invoker, element);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
})();
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
|
|
25
|
+
.micl-menu[popover] {
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: unset;
|
|
28
|
+
inset-block-start: anchor(end);
|
|
29
|
+
inset-inline-start: anchor(start);
|
|
30
|
+
position-try-fallbacks: flip-block, flip-inline;
|
|
31
|
+
min-width: 112px;
|
|
32
|
+
max-width: 280px;
|
|
33
|
+
padding-block: 8px;
|
|
34
|
+
padding-inline: 0;
|
|
35
|
+
border: none;
|
|
36
|
+
border-radius: var(--md-sys-shape-corner-extra-small);
|
|
37
|
+
background-color: var(--md-sys-color-surface-container);
|
|
38
|
+
box-shadow: var(--md-sys-elevation-level2);
|
|
39
|
+
opacity: 0;
|
|
40
|
+
transform: scaleY(0);
|
|
41
|
+
transform-origin: top left;
|
|
42
|
+
transition:
|
|
43
|
+
opacity var(--md-sys-motion-duration-medium1),
|
|
44
|
+
transform var(--md-sys-motion-duration-medium1),
|
|
45
|
+
overlay var(--md-sys-motion-duration-medium1) allow-discrete,
|
|
46
|
+
display var(--md-sys-motion-duration-medium1) allow-discrete;
|
|
47
|
+
|
|
48
|
+
&:popover-open {
|
|
49
|
+
opacity: 1;
|
|
50
|
+
transform: scaleY(1);
|
|
51
|
+
transition:
|
|
52
|
+
opacity var(--md-sys-motion-duration-long2) motion.$md-sys-motion-easing-emphasized-decelerate,
|
|
53
|
+
transform var(--md-sys-motion-duration-long2) linear(motion.$md-sys-motion-spring-default-spatial),
|
|
54
|
+
overlay var(--md-sys-motion-duration-long2) linear allow-discrete,
|
|
55
|
+
display var(--md-sys-motion-duration-long2) linear allow-discrete;
|
|
56
|
+
|
|
57
|
+
@position-try-on {
|
|
58
|
+
transform-origin: bottom left;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@starting-style {
|
|
62
|
+
opacity: 0;
|
|
63
|
+
transform: scaleY(0);
|
|
64
|
+
|
|
65
|
+
@position-try-on {
|
|
66
|
+
transform-origin: bottom left;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
&::backdrop {
|
|
70
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
71
|
+
|
|
72
|
+
@starting-style {
|
|
73
|
+
background-color: rgba(0, 0, 0, 0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
&::backdrop {
|
|
78
|
+
background-color: rgba(0, 0, 0, 0);
|
|
79
|
+
transition:
|
|
80
|
+
background-color var(--md-sys-motion-duration-long2),
|
|
81
|
+
overlay var(--md-sys-motion-duration-long2) linear allow-discrete,
|
|
82
|
+
display var(--md-sys-motion-duration-long2) linear allow-discrete;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.micl-list {
|
|
86
|
+
--md-sys-list-item-one-height: 48px;
|
|
87
|
+
--md-sys-list-item-one-padding: 0;
|
|
88
|
+
--md-sys-list-item-two-padding: 0;
|
|
89
|
+
--md-sys-list-item-space: 12px;
|
|
90
|
+
--md-sys-list-item-background-color: var(--md-sys-color-surface-container);
|
|
91
|
+
|
|
92
|
+
.micl-list-item-one,
|
|
93
|
+
.micl-list-item-two,
|
|
94
|
+
.micl-list-item-three {
|
|
95
|
+
&:not(.micl-list-item--disabled) {
|
|
96
|
+
--miclripple: 1;
|
|
97
|
+
|
|
98
|
+
@include ripple.effect;
|
|
99
|
+
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
|
|
102
|
+
&:focus-visible {
|
|
103
|
+
outline-offset: calc(-1 * var(--md-sys-state-focus-indicator-thickness));
|
|
104
|
+
z-index: 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
&.micl-list-item__divider {
|
|
108
|
+
position: relative;
|
|
109
|
+
margin-block-start: 16px;
|
|
110
|
+
overflow: clip;
|
|
111
|
+
overflow-clip-margin: 8px;
|
|
112
|
+
|
|
113
|
+
&::after {
|
|
114
|
+
content: "";
|
|
115
|
+
position: absolute;
|
|
116
|
+
inset-block-start: -8px;
|
|
117
|
+
inset-inline-start: 0;
|
|
118
|
+
width: 100%;
|
|
119
|
+
border-top: var(--md-sys-divider-thickness) solid var(--md-sys-divider-color);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Radio button
|
|
2
|
+
This component implements the the [Material Design 3 Expressive Radio button](https://m3.material.io/components/radio-button/overview) design.
|
|
3
|
+
|
|
4
|
+
## Basic Usage
|
|
5
|
+
|
|
6
|
+
### HTML
|
|
7
|
+
To add a basic radio button, use the `<input type="radio">` element with the `micl-radio` class, paired with a `<label>` element:
|
|
8
|
+
|
|
9
|
+
```HTML
|
|
10
|
+
<input type="radio" id="myradio" class="micl-radio" name="foo" value="bar">
|
|
11
|
+
<label for="myradio">First choice</label>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### CSS
|
|
15
|
+
Import the radio button styles into your project:
|
|
16
|
+
|
|
17
|
+
```CSS
|
|
18
|
+
@use "micl/components/radio";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### TypeScript
|
|
22
|
+
No custom TypeScript is required for the core functionality of this component. However, to enable the ripple effect on interaction, include the Ripple TypeScript in your project.
|
|
23
|
+
|
|
24
|
+
### Demo
|
|
25
|
+
A live example of the [Radio button component](https://henkpb.github.io/micl/radio.html) is available for you to interact with.
|
|
26
|
+
|
|
27
|
+
## Variants
|
|
28
|
+
A radio button can be disabled by adding the `disabled` attribute to the `<input>` element.
|
|
29
|
+
|
|
30
|
+
The Radio Button component respects the `dir` global attribute, automatically adjusting its layout for right-to-left (RTL) languages when `dir="rtl"` is applied to an ancestor element.
|
|
31
|
+
|
|
32
|
+
By default, the component applies a specific color and `cursor: pointer` to `<label>` elements immediately preceding or following an `<input type="radio">` with the `micl-radio` class. You are encouraged to customize these CSS settings to match your design system.
|
|
33
|
+
|
|
34
|
+
## Customizations
|
|
35
|
+
You can customize the appearance of the Radio Button 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 radio buttons.
|
|
36
|
+
|
|
37
|
+
| Variable name | Default Value | Description |
|
|
38
|
+
| ------------- | ------------- | ----------- |
|
|
39
|
+
| --md-sys-radio-border-width | 2px | Controls the thickness of the radio button's border |
|
|
40
|
+
| --md-sys-radio-container-size | 20px | Defines the diameter of the radio button itself |
|
|
41
|
+
| --md-sys-radio-state-layer-size | 40px | Sets the size of the interactive area that indicates the component's current state (e.g., hover, focus, press) |
|
|
42
|
+
|
|
43
|
+
**Example: Changing the size of the radio button**
|
|
44
|
+
|
|
45
|
+
```HTML
|
|
46
|
+
<div style="--md-sys-radio-container-size:28px">
|
|
47
|
+
<input type="radio" id="myradio" class="micl-radio">
|
|
48
|
+
<label for="myradio">Large radio button</label>
|
|
49
|
+
</div>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Compatibility
|
|
53
|
+
This component utilizes relative RGB color values, which may not be fully supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#browser_compatibility) for details.
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
|
|
25
|
+
:root {
|
|
26
|
+
--md-sys-radio-border-width: 2px;
|
|
27
|
+
--md-sys-radio-container-size: 20px;
|
|
28
|
+
--md-sys-radio-state-layer-size: 40px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
input[type=radio].micl-radio {
|
|
32
|
+
--md-sys-ripple-background-color: var(--md-sys-color-primary);
|
|
33
|
+
|
|
34
|
+
appearance: none;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
position: relative;
|
|
37
|
+
width: var(--md-sys-target-size);
|
|
38
|
+
height: var(--md-sys-target-size);
|
|
39
|
+
margin: 0;
|
|
40
|
+
border: calc((var(--md-sys-target-size) - var(--md-sys-radio-state-layer-size)) / 2) solid transparent;
|
|
41
|
+
background-clip: content-box;
|
|
42
|
+
background-color: transparent;
|
|
43
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
44
|
+
outline-offset: -7px;
|
|
45
|
+
transition: background-color var(--md-sys-motion-duration-long2);
|
|
46
|
+
|
|
47
|
+
&::after {
|
|
48
|
+
content: "";
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
position: absolute;
|
|
51
|
+
width: var(--md-sys-radio-container-size);
|
|
52
|
+
height: var(--md-sys-radio-container-size);
|
|
53
|
+
inset: 0;
|
|
54
|
+
margin: auto;
|
|
55
|
+
padding: calc((var(--md-sys-radio-container-size) / 2) - var(--md-sys-radio-border-width));
|
|
56
|
+
border: var(--md-sys-radio-border-width) solid var(--md-sys-color-on-surface-variant);
|
|
57
|
+
background-color: var(--md-sys-color-primary);
|
|
58
|
+
background-clip: content-box;
|
|
59
|
+
border-radius: var(--md-sys-shape-corner-full);
|
|
60
|
+
transition:
|
|
61
|
+
padding var(--md-sys-motion-duration-medium2) motion.$md-sys-motion-easing-emphasized,
|
|
62
|
+
border-color var(--md-sys-motion-duration-medium2) motion.$md-sys-motion-easing-emphasized;
|
|
63
|
+
}
|
|
64
|
+
&:checked::after {
|
|
65
|
+
border-color: var(--md-sys-color-primary);
|
|
66
|
+
padding: 3px;
|
|
67
|
+
transition:
|
|
68
|
+
padding var(--md-sys-motion-duration-long4) linear(motion.$md-sys-motion-spring-slow-spatial),
|
|
69
|
+
border-color var(--md-sys-motion-duration-long4) motion.$md-sys-motion-easing-emphasized;
|
|
70
|
+
}
|
|
71
|
+
&:not(:disabled) {
|
|
72
|
+
--miclripple: 1;
|
|
73
|
+
|
|
74
|
+
@include ripple.effect;
|
|
75
|
+
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
|
|
78
|
+
&:hover {
|
|
79
|
+
background-color: rgb(from var(--md-sys-color-on-surface) r g b / var(--md-sys-state-hover-state-layer-opacity));
|
|
80
|
+
|
|
81
|
+
&:checked {
|
|
82
|
+
background-color: rgb(from var(--md-sys-color-primary) r g b / var(--md-sys-state-hover-state-layer-opacity));
|
|
83
|
+
}
|
|
84
|
+
&::after {
|
|
85
|
+
border-color: var(--md-sys-color-on-surface);
|
|
86
|
+
}
|
|
87
|
+
&:checked::after {
|
|
88
|
+
border-color: var(--md-sys-color-primary);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
&:focus-visible {
|
|
92
|
+
background-color: rgb(from var(--md-sys-color-on-surface) r g b / var(--md-sys-state-focus-state-layer-opacity));
|
|
93
|
+
outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
|
|
94
|
+
|
|
95
|
+
&:checked {
|
|
96
|
+
background-color: rgb(from var(--md-sys-color-primary) r g b / var(--md-sys-state-focus-state-layer-opacity));
|
|
97
|
+
}
|
|
98
|
+
&::after {
|
|
99
|
+
border-color: var(--md-sys-color-on-surface);
|
|
100
|
+
}
|
|
101
|
+
&:checked::after {
|
|
102
|
+
border-color: var(--md-sys-color-primary);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
&:active {
|
|
106
|
+
background-color: rgb(from var(--md-sys-color-on-surface) r g b / var(--md-sys-state-pressed-state-layer-opacity));
|
|
107
|
+
|
|
108
|
+
&:checked {
|
|
109
|
+
background-color: rgb(from var(--md-sys-color-primary) r g b / var(--md-sys-state-pressed-state-layer-opacity));
|
|
110
|
+
}
|
|
111
|
+
&::after {
|
|
112
|
+
border-color: var(--md-sys-color-on-surface);
|
|
113
|
+
}
|
|
114
|
+
&:checked::after {
|
|
115
|
+
border-color: var(--md-sys-color-primary);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
&:disabled {
|
|
120
|
+
opacity: 38%;
|
|
121
|
+
|
|
122
|
+
&::after {
|
|
123
|
+
border-color: var(--md-sys-color-on-surface);
|
|
124
|
+
}
|
|
125
|
+
&:checked::after {
|
|
126
|
+
background-color: var(--md-sys-color-on-surface);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
input[type=radio].micl-radio:not(:disabled) + label,
|
|
132
|
+
label:has(+ input[type=radio].micl-radio:not(:disabled)) {
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
}
|
|
135
|
+
input[type=radio].micl-radio + label,
|
|
136
|
+
label + input[type=radio].micl-radio {
|
|
137
|
+
color: var(--md-sys-color-on-surface);
|
|
138
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Select
|
|
2
|
+
This component implements the the [Material Design 3 Expressive Select](https://m3.material.io/components/menus/guidelines#ee2f3664-c926-47ab-acbf-2ab675506932) design.
|
|
3
|
+
|
|
4
|
+
## Basic Usage
|
|
5
|
+
|
|
6
|
+
### HTML
|
|
7
|
+
The Select component is an extension of the [Text field](../textfield/README.md) and the [List](../list/README.md). It can be either `filled` or `outlined`. To create a basic select, use the following HTML and swap the class name to change the style.
|
|
8
|
+
|
|
9
|
+
```HTML
|
|
10
|
+
<div class="micl-textfield-filled">
|
|
11
|
+
<label for="myselect">Country</label>
|
|
12
|
+
<select id="myselect">
|
|
13
|
+
<option class="micl-list-item-one" value="AR">
|
|
14
|
+
<span class="micl-list-item__text">Argentina</span>
|
|
15
|
+
</option>
|
|
16
|
+
<option class="micl-list-item-one" value="BO">
|
|
17
|
+
<span class="micl-list-item__text">Bolivia</span>
|
|
18
|
+
</option>
|
|
19
|
+
</select>
|
|
20
|
+
</div>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### CSS
|
|
24
|
+
The Select component relies on styles from the text field and list components. Be sure to import all three styles into your project.
|
|
25
|
+
|
|
26
|
+
```CSS
|
|
27
|
+
@use "micl/components/list";
|
|
28
|
+
@use "micl/components/textfield";
|
|
29
|
+
@use "micl/components/select";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### TypeScript
|
|
33
|
+
This component requires the **Text field** TypeScript module for functionality. You can import the specific module and handle initialization manually, or use the main MICL library for automatic initialization.
|
|
34
|
+
|
|
35
|
+
To manually initialize the component:
|
|
36
|
+
|
|
37
|
+
```TypeScript
|
|
38
|
+
import miclTextField from 'micl/components/textfield';
|
|
39
|
+
|
|
40
|
+
miclTextField.initialize(document.querySelector('.micl-textfield-filled'));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Demo
|
|
44
|
+
A live example of the [Select component](https://henkpb.github.io/micl/select.html) is available for you to interact with.
|
|
45
|
+
|
|
46
|
+
## Variants
|
|
47
|
+
To display extra information for an option, add the `aria-description` attribute to the `<option>` element. In a two-line list item (`micl-list-item-two`), this displays the attribute's content as supporting text. Do not add a separate text element to the `<option>`, as this will change the text of the selected option.
|
|
48
|
+
|
|
49
|
+
**Example: A select with supporting text**
|
|
50
|
+
|
|
51
|
+
```HTML
|
|
52
|
+
<div class="micl-textfield-outlined">
|
|
53
|
+
<label for="myselect">Country</label>
|
|
54
|
+
<select id="myselect">
|
|
55
|
+
<option class="micl-list-item-two" value="AR">
|
|
56
|
+
<span class="micl-list-item__text" aria-description="Country code: AR">Argentina</span>
|
|
57
|
+
</option>
|
|
58
|
+
<option class="micl-list-item-two" value="BO">
|
|
59
|
+
<span class="micl-list-item__text" aria-description="Country code: BO">Bolivia</span>
|
|
60
|
+
</option>
|
|
61
|
+
</select>
|
|
62
|
+
</div>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The text content of an option can be preceded by various media elements:
|
|
66
|
+
|
|
67
|
+
- **Image**: Use `micl-list-item__image` with a background image.
|
|
68
|
+
```HTML
|
|
69
|
+
<option class="micl-list-item-two" value="AR">
|
|
70
|
+
<span class="micl-list-item__image" style="background-image:url(https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/330px-Flag_of_Argentina.svg.png)"></span>
|
|
71
|
+
<span class="micl-list-item__text" aria-description="Country code: AR">Argentina</span>
|
|
72
|
+
</option>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **Thumbnail**: Use `micl-list-item__thumbnail` for video previews with a background-image.
|
|
76
|
+
```HTML
|
|
77
|
+
<option class="micl-list-item-two" value="AR">
|
|
78
|
+
<span class="micl-list-item__thumbnail" style="background-image:url(https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/330px-Flag_of_Argentina.svg.png)"></span>
|
|
79
|
+
<span class="micl-list-item__text" aria-description="Country code: AR">Argentina</span>
|
|
80
|
+
</option>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Compatibility
|
|
84
|
+
This component uses modern browser features to style the `<select>` element, which may not be fully supported in all browsers. Browsers that do not support these features will display a default select menu. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/::picker#browser_compatibility) for details.
|
|
@@ -0,0 +1,122 @@
|
|
|
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/statelayer';
|
|
25
|
+
@use '../../styles/typography';
|
|
26
|
+
|
|
27
|
+
.micl-textfield-filled > select,
|
|
28
|
+
.micl-textfield-outlined > select {
|
|
29
|
+
--md-sys-option-one-height: var(--md-sys-list-item-one-height);
|
|
30
|
+
--md-sys-option-one-padding: var(--md-sys-list-item-one-padding);
|
|
31
|
+
--md-sys-option-two-padding: 8px;
|
|
32
|
+
--md-sys-option-three-padding: 12px;
|
|
33
|
+
--md-sys-option-space: 16px;
|
|
34
|
+
--md-sys-option-padding-inline: 16px;
|
|
35
|
+
|
|
36
|
+
appearance: base-select;
|
|
37
|
+
|
|
38
|
+
&::picker-icon {
|
|
39
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
40
|
+
transition: 0.4s rotate;
|
|
41
|
+
}
|
|
42
|
+
&:open::picker-icon {
|
|
43
|
+
rotate: 180deg;
|
|
44
|
+
}
|
|
45
|
+
&::picker(select) {
|
|
46
|
+
appearance: base-select;
|
|
47
|
+
min-inline-size: max(anchor-size(self-inline), 112px);
|
|
48
|
+
max-inline-size: 280px;
|
|
49
|
+
padding-block: 8px;
|
|
50
|
+
padding-inline: 0;
|
|
51
|
+
border: none;
|
|
52
|
+
border-radius: var(--md-sys-shape-corner-extra-small);
|
|
53
|
+
background-color: var(--md-sys-color-surface-container);
|
|
54
|
+
box-shadow: var(--md-sys-elevation-level2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
option {
|
|
58
|
+
--md-sys-list-item-one-height: 48px;
|
|
59
|
+
--md-sys-list-item-two-height: 64px;
|
|
60
|
+
--md-sys-list-item-one-padding: 0;
|
|
61
|
+
--md-sys-list-item-two-padding: 0;
|
|
62
|
+
--md-sys-list-item-space: 12px;
|
|
63
|
+
--md-sys-list-item-padding-inline: 16px;
|
|
64
|
+
--md-sys-list-item-background-color: var(--md-sys-color-surface-container);
|
|
65
|
+
--md-sys-motion-duration-long2: 500ms;
|
|
66
|
+
--md-sys-state-hover-state-layer-opacity: #{statelayer.$md-sys-state-hover-state-layer-opacity};
|
|
67
|
+
--md-sys-state-focus-state-layer-opacity: #{statelayer.$md-sys-state-focus-state-layer-opacity};
|
|
68
|
+
--md-sys-state-pressed-state-layer-opacity: #{statelayer.$md-sys-state-pressed-state-layer-opacity};
|
|
69
|
+
--md-sys-state-focus-indicator-thickness: #{statelayer.$md-sys-state-focus-indicator-thickness};
|
|
70
|
+
|
|
71
|
+
border-radius: 0px;
|
|
72
|
+
|
|
73
|
+
&:not(:disabled) {
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
}
|
|
76
|
+
&:checked {
|
|
77
|
+
background-color: var(--md-sys-color-secondary-container);
|
|
78
|
+
|
|
79
|
+
.micl-list-item__text {
|
|
80
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
81
|
+
|
|
82
|
+
&::after {
|
|
83
|
+
color: var(--md-sys-color-on-surface);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
&:focus-visible {
|
|
88
|
+
outline-offset: calc(-1 * var(--md-sys-state-focus-indicator-thickness));
|
|
89
|
+
z-index: 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.micl-list-item__text {
|
|
93
|
+
@include typography.body-large;
|
|
94
|
+
|
|
95
|
+
color: var(--md-sys-color-on-surface);
|
|
96
|
+
}
|
|
97
|
+
.micl-list-item__text::after {
|
|
98
|
+
@include typography.body-medium;
|
|
99
|
+
|
|
100
|
+
content: attr(aria-description);
|
|
101
|
+
display: block;
|
|
102
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
103
|
+
white-space: normal;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
dialog.micl-dialog:has(.micl-textfield-filled > select),
|
|
109
|
+
dialog.micl-dialog:has(.micl-textfield-outlined > select),
|
|
110
|
+
dialog.micl-dialog-fullscreen:has(.micl-textfield-filled > select),
|
|
111
|
+
dialog.micl-dialog-fullscreen:has(.micl-textfield-outlined > select) {
|
|
112
|
+
inset-block-start: 0;
|
|
113
|
+
inset-inline-start: 0;
|
|
114
|
+
margin: auto;
|
|
115
|
+
transform: scale(50%);
|
|
116
|
+
}
|
|
117
|
+
dialog.micl-dialog:has(.micl-textfield-filled > select):popover-open,
|
|
118
|
+
dialog.micl-dialog:has(.micl-textfield-outlined > select):popover-open,
|
|
119
|
+
dialog.micl-dialog-fullscreen:has(.micl-textfield-filled > select):popover-open,
|
|
120
|
+
dialog.micl-dialog-fullscreen:has(.micl-textfield-outlined > select):popover-open {
|
|
121
|
+
transform: scale(100%);
|
|
122
|
+
}
|