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.
Files changed (174) hide show
  1. package/.editorconfig +12 -0
  2. package/.gitattributes +9 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. package/LICENSE +21 -0
  6. package/README.md +99 -0
  7. package/components/README.md +12 -0
  8. package/components/accordion/README.md +94 -0
  9. package/components/bottomsheet/README.md +77 -0
  10. package/components/bottomsheet/bottomsheet.scss +134 -0
  11. package/components/bottomsheet/index.ts +152 -0
  12. package/components/button/README.md +92 -0
  13. package/components/button/button.scss +515 -0
  14. package/components/button/index.ts +73 -0
  15. package/components/card/README.md +125 -0
  16. package/components/card/card.scss +261 -0
  17. package/components/checkbox/README.md +62 -0
  18. package/components/checkbox/checkbox.scss +275 -0
  19. package/components/checkbox/index.ts +48 -0
  20. package/components/dialog/README.md +133 -0
  21. package/components/dialog/dialog.scss +262 -0
  22. package/components/divider/README.md +52 -0
  23. package/components/divider/divider.scss +74 -0
  24. package/components/iconbutton/README.md +86 -0
  25. package/components/iconbutton/iconbutton.scss +461 -0
  26. package/components/iconbutton/index.ts +73 -0
  27. package/components/list/README.md +176 -0
  28. package/components/list/index.ts +108 -0
  29. package/components/list/list.scss +295 -0
  30. package/components/menu/README.md +96 -0
  31. package/components/menu/index.ts +77 -0
  32. package/components/menu/menu.scss +124 -0
  33. package/components/radio/README.md +53 -0
  34. package/components/radio/radio.scss +138 -0
  35. package/components/select/README.md +84 -0
  36. package/components/select/select.scss +122 -0
  37. package/components/sidesheet/README.md +99 -0
  38. package/components/sidesheet/sidesheet.scss +162 -0
  39. package/components/slider/README.md +69 -0
  40. package/components/slider/index.ts +114 -0
  41. package/components/slider/slider.scss +258 -0
  42. package/components/switch/README.md +49 -0
  43. package/components/switch/switch.scss +176 -0
  44. package/components/textfield/README.md +75 -0
  45. package/components/textfield/index.ts +81 -0
  46. package/components/textfield/textfield.scss +387 -0
  47. package/components.ts +169 -0
  48. package/dist/bottomsheet.css +1 -0
  49. package/dist/bottomsheet.js +0 -0
  50. package/dist/button.css +1 -0
  51. package/dist/button.js +0 -0
  52. package/dist/card.css +1 -0
  53. package/dist/card.js +0 -0
  54. package/dist/checkbox.css +1 -0
  55. package/dist/checkbox.js +0 -0
  56. package/dist/dialog.css +1 -0
  57. package/dist/dialog.js +0 -0
  58. package/dist/divider.css +1 -0
  59. package/dist/divider.js +0 -0
  60. package/dist/iconbutton.css +1 -0
  61. package/dist/iconbutton.js +0 -0
  62. package/dist/list.css +1 -0
  63. package/dist/list.js +0 -0
  64. package/dist/menu.css +1 -0
  65. package/dist/menu.js +0 -0
  66. package/dist/micl.css +1 -0
  67. package/dist/micl.js +1 -0
  68. package/dist/radio.css +1 -0
  69. package/dist/radio.js +0 -0
  70. package/dist/select.css +1 -0
  71. package/dist/select.js +0 -0
  72. package/dist/sidesheet.css +1 -0
  73. package/dist/sidesheet.js +0 -0
  74. package/dist/slider.css +1 -0
  75. package/dist/slider.js +0 -0
  76. package/dist/switch.css +1 -0
  77. package/dist/switch.js +0 -0
  78. package/dist/textfield.css +1 -0
  79. package/dist/textfield.js +0 -0
  80. package/docs/accordion.html +285 -0
  81. package/docs/bottomsheet.html +162 -0
  82. package/docs/button.html +206 -0
  83. package/docs/card-awards.webp +0 -0
  84. package/docs/card-cabinet.webp +0 -0
  85. package/docs/card-city.webp +0 -0
  86. package/docs/card-fingerprint.webp +0 -0
  87. package/docs/card-holiday.webp +0 -0
  88. package/docs/card-names.webp +0 -0
  89. package/docs/card.html +91 -0
  90. package/docs/checkbox.html +99 -0
  91. package/docs/dialog.html +153 -0
  92. package/docs/divider.html +103 -0
  93. package/docs/docs.css +34 -0
  94. package/docs/docs.js +69 -0
  95. package/docs/iconbutton.html +197 -0
  96. package/docs/index.html +319 -0
  97. package/docs/list.html +224 -0
  98. package/docs/menu.html +143 -0
  99. package/docs/micl.css +1 -0
  100. package/docs/micl.js +1 -0
  101. package/docs/radio.html +101 -0
  102. package/docs/select.html +205 -0
  103. package/docs/sidesheet.html +115 -0
  104. package/docs/slider.html +72 -0
  105. package/docs/switch.html +151 -0
  106. package/docs/textfield.html +151 -0
  107. package/docs/themes/airblue/dark-hc.css +51 -0
  108. package/docs/themes/airblue/dark-mc.css +51 -0
  109. package/docs/themes/airblue/dark.css +51 -0
  110. package/docs/themes/airblue/light-hc.css +51 -0
  111. package/docs/themes/airblue/light-mc.css +51 -0
  112. package/docs/themes/airblue/light.css +51 -0
  113. package/docs/themes/airblue/theme.css +306 -0
  114. package/docs/themes/barnred/dark-hc.css +51 -0
  115. package/docs/themes/barnred/dark-mc.css +51 -0
  116. package/docs/themes/barnred/dark.css +51 -0
  117. package/docs/themes/barnred/light-hc.css +51 -0
  118. package/docs/themes/barnred/light-mc.css +51 -0
  119. package/docs/themes/barnred/light.css +51 -0
  120. package/docs/themes/barnred/theme.css +306 -0
  121. package/docs/themes/citrine/dark-hc.css +51 -0
  122. package/docs/themes/citrine/dark-mc.css +51 -0
  123. package/docs/themes/citrine/dark.css +51 -0
  124. package/docs/themes/citrine/light-hc.css +51 -0
  125. package/docs/themes/citrine/light-mc.css +51 -0
  126. package/docs/themes/citrine/light.css +51 -0
  127. package/docs/themes/citrine/theme.css +306 -0
  128. package/docs/themes/olivegreen/dark-hc.css +51 -0
  129. package/docs/themes/olivegreen/dark-mc.css +51 -0
  130. package/docs/themes/olivegreen/dark.css +51 -0
  131. package/docs/themes/olivegreen/light-hc.css +51 -0
  132. package/docs/themes/olivegreen/light-mc.css +51 -0
  133. package/docs/themes/olivegreen/light.css +51 -0
  134. package/docs/themes/olivegreen/theme.css +306 -0
  135. package/package.json +62 -0
  136. package/styles/README.md +99 -0
  137. package/styles/elevation.scss +36 -0
  138. package/styles/motion.scss +124 -0
  139. package/styles/ripple.scss +50 -0
  140. package/styles/shapes.scss +46 -0
  141. package/styles/statelayer.scss +42 -0
  142. package/styles/typography.scss +568 -0
  143. package/styles.scss +43 -0
  144. package/themes/README.md +57 -0
  145. package/themes/airblue/dark-hc.css +51 -0
  146. package/themes/airblue/dark-mc.css +51 -0
  147. package/themes/airblue/dark.css +51 -0
  148. package/themes/airblue/light-hc.css +51 -0
  149. package/themes/airblue/light-mc.css +51 -0
  150. package/themes/airblue/light.css +51 -0
  151. package/themes/airblue/theme.css +306 -0
  152. package/themes/barnred/dark-hc.css +51 -0
  153. package/themes/barnred/dark-mc.css +51 -0
  154. package/themes/barnred/dark.css +51 -0
  155. package/themes/barnred/light-hc.css +51 -0
  156. package/themes/barnred/light-mc.css +51 -0
  157. package/themes/barnred/light.css +51 -0
  158. package/themes/barnred/theme.css +306 -0
  159. package/themes/citrine/dark-hc.css +51 -0
  160. package/themes/citrine/dark-mc.css +51 -0
  161. package/themes/citrine/dark.css +51 -0
  162. package/themes/citrine/light-hc.css +51 -0
  163. package/themes/citrine/light-mc.css +51 -0
  164. package/themes/citrine/light.css +51 -0
  165. package/themes/citrine/theme.css +306 -0
  166. package/themes/olivegreen/dark-hc.css +51 -0
  167. package/themes/olivegreen/dark-mc.css +51 -0
  168. package/themes/olivegreen/dark.css +51 -0
  169. package/themes/olivegreen/light-hc.css +51 -0
  170. package/themes/olivegreen/light-mc.css +51 -0
  171. package/themes/olivegreen/light.css +51 -0
  172. package/themes/olivegreen/theme.css +306 -0
  173. package/tsconfig.json +110 -0
  174. 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
+ }