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,258 @@
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/typography';
24
+
25
+ @mixin slider-track() {
26
+ block-size: var(--md-sys-slider-track-height);
27
+ border-radius: var(--md-sys-slider-track-radius);
28
+ background-image: linear-gradient(
29
+ var(--md-sys-slider-track-direction),
30
+ var(--md-sys-color-primary) calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
31
+ transparent calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
32
+ var(--md-sys-color-secondary-container) calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
33
+ var(--md-sys-color-secondary-container) 100%,
34
+ transparent
35
+ );
36
+ }
37
+ @mixin slider-track-disabled() {
38
+ background-image: linear-gradient(
39
+ var(--md-sys-slider-track-direction),
40
+ color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent) calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
41
+ transparent calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
42
+ color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent) calc(100% * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min))),
43
+ color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent) 100%,
44
+ transparent
45
+ );
46
+ }
47
+ @mixin slider-thumb() {
48
+ appearance: none;
49
+ box-sizing: content-box;
50
+ position: relative;
51
+ inline-size: 4px;
52
+ block-size: var(--md-sys-slider-handle-height);
53
+ inset: 0;
54
+ inset-block-start: calc(-1 * (var(--md-sys-slider-handle-height) + 2 * var(--md-sys-slider-thumb-space) - var(--md-sys-slider-track-height)) / 2);
55
+ border: var(--md-sys-slider-thumb-space) solid currentColor;
56
+ border-radius: 8px;
57
+ background-color: var(--md-sys-color-primary);
58
+ cursor: pointer;
59
+ z-index: 2;
60
+ }
61
+ @mixin slider-thumb-disabled() {
62
+ background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, var(--md-sys-color-surface));
63
+ cursor: default;
64
+ }
65
+ @mixin slider-progress() {
66
+ block-size: var(--md-sys-slider-track-height);
67
+ }
68
+
69
+ input[type=range].micl-slider-xs {
70
+ --md-sys-slider-handle-height: 44px;
71
+ --md-sys-slider-track-height: 16px;
72
+ --md-sys-slider-track-radius: var(--md-sys-shape-corner-small);
73
+ }
74
+ input[type=range].micl-slider-s {
75
+ --md-sys-slider-handle-height: 44px;
76
+ --md-sys-slider-track-height: 24px;
77
+ --md-sys-slider-track-radius: var(--md-sys-shape-corner-small);
78
+ }
79
+ input[type=range].micl-slider-m {
80
+ --md-sys-slider-handle-height: 52px;
81
+ --md-sys-slider-track-height: 40px;
82
+ --md-sys-slider-track-radius: var(--md-sys-shape-corner-medium);
83
+ }
84
+ input[type=range].micl-slider-l {
85
+ --md-sys-slider-handle-height: 68px;
86
+ --md-sys-slider-track-height: 56px;
87
+ --md-sys-slider-track-radius: var(--md-sys-shape-corner-large);
88
+ }
89
+ input[type=range].micl-slider-xl {
90
+ --md-sys-slider-handle-height: 108px;
91
+ --md-sys-slider-track-height: 96px;
92
+ --md-sys-slider-track-radius: var(--md-sys-shape-corner-extra-large);
93
+ }
94
+
95
+ input[type=range].micl-slider-xs,
96
+ input[type=range].micl-slider-s,
97
+ input[type=range].micl-slider-m,
98
+ input[type=range].micl-slider-l,
99
+ input[type=range].micl-slider-xl {
100
+ --md-sys-slider-thumb-space: 6px;
101
+ --md-sys-slider-track-direction: to right;
102
+
103
+ appearance: none;
104
+ position: relative;
105
+ margin: 0;
106
+ outline: none;
107
+ color: var(--md-sys-color-surface-container-low);
108
+ background-color: transparent;
109
+ accent-color: var(--md-sys-color-primary);
110
+
111
+ &::before {
112
+ content: attr(data-miclsliderticks);
113
+ box-sizing: border-box;
114
+ position: absolute;
115
+ inline-size: 100%;
116
+ max-inline-size: 100%;
117
+ block-size: var(--md-sys-slider-track-height);
118
+ line-height: var(--md-sys-slider-track-height);
119
+ padding: 0 4px;
120
+ inset: 0;
121
+ color: var(--md-sys-color-on-primary);
122
+ mix-blend-mode: difference;
123
+ text-align: end;
124
+ text-wrap-mode: nowrap;
125
+ }
126
+ &::after {
127
+ --md-sys-slider-tip-space: 4px;
128
+ @include typography.label-medium;
129
+ content: var(--md-sys-slider-tip);
130
+ box-sizing: border-box;
131
+ position: absolute;
132
+ inline-size: fit-content;
133
+ min-inline-size: 48px;
134
+ block-size: 40px;
135
+ inset: 0;
136
+ inset-block-start: calc(-1 * ((var(--md-sys-slider-handle-height) - var(--md-sys-slider-track-height)) / 2) - 40px - var(--md-sys-slider-tip-space));
137
+ inset-inline-start: calc(-16px + (100% - 16px) * (var(--md-sys-slider-value) - var(--md-sys-slider-min)) / (var(--md-sys-slider-max) - var(--md-sys-slider-min)));
138
+ padding-block: 12px;
139
+ padding-inline: 16px;
140
+ border-radius: 20px;
141
+ background-color: var(--md-sys-color-inverse-surface);
142
+ color: var(--md-sys-color-inverse-on-surface);
143
+ text-align: center;
144
+ opacity: 0;
145
+ z-index: 3;
146
+ transition: opacity var(--md-sys-motion-duration-long2) motion.$md-sys-motion-easing-emphasized-decelerate;
147
+ }
148
+ &::-webkit-slider-runnable-track {
149
+ @include slider-track;
150
+ }
151
+ &::-moz-range-track {
152
+ @include slider-track;
153
+ }
154
+ &::-webkit-slider-thumb {
155
+ @include slider-thumb;
156
+ }
157
+ &::-moz-range-thumb {
158
+ @include slider-thumb;
159
+ }
160
+ &::-moz-range-progress {
161
+ @include slider-progress;
162
+ }
163
+ &:disabled {
164
+ &::-webkit-slider-runnable-track {
165
+ @include slider-track-disabled;
166
+ }
167
+ &::-moz-range-track {
168
+ @include slider-track-disabled;
169
+ }
170
+ &::-webkit-slider-thumb {
171
+ @include slider-thumb-disabled;
172
+ }
173
+ &::-moz-range-thumb {
174
+ @include slider-thumb-disabled;
175
+ }
176
+ }
177
+ &.micl-slider--vertical {
178
+ --md-sys-slider-track-direction: to top;
179
+ writing-mode: sideways-lr;
180
+ &::after {
181
+ --md-sys-slider-tip-space: 8px;
182
+ transform: rotate(90deg);
183
+ }
184
+ }
185
+ &:not(:disabled):focus-visible {
186
+ &::after {
187
+ opacity: 1;
188
+ }
189
+ &::-webkit-slider-thumb {
190
+ outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
191
+ }
192
+ &::-moz-range-thumb {
193
+ outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
194
+ }
195
+ }
196
+ &:not(:disabled):active {
197
+ &::-webkit-slider-thumb {
198
+ inline-size: 2px;
199
+ outline: none;
200
+ cursor: grabbing;
201
+ }
202
+ &::-moz-range-thumb {
203
+ inline-size: 2px;
204
+ outline: none;
205
+ cursor: grabbing;
206
+ }
207
+ &::after {
208
+ opacity: 1;
209
+ }
210
+ }
211
+ }
212
+
213
+ .micl-slider__container:has(.micl-slider-m,.micl-slider-l,.micl-slider-xl) {
214
+ display: grid;
215
+ grid-template-areas: "slidericon";
216
+ flex-shrink: 0;
217
+ align-items: center;
218
+ justify-items: flex-start;
219
+
220
+ .micl-slider__icon {
221
+ grid-area: slidericon;
222
+ inset: 0;
223
+ margin: 6px;
224
+ font-size: 24px;
225
+ color: var(--md-sys-color-on-primary);
226
+ z-index: 1;
227
+
228
+ }
229
+ input[type=range].micl-slider-m,
230
+ input[type=range].micl-slider-l,
231
+ input[type=range].micl-slider-xl {
232
+ grid-area: slidericon;
233
+ }
234
+ .micl-slider__icon:has(+ input[type=range].micl-slider-xl),
235
+ input[type=range].micl-slider-xl + .micl-slider__icon {
236
+ margin: 8px;
237
+ font-size: 32px;
238
+ }
239
+ }
240
+
241
+ [dir=rtl] input[type=range].micl-slider-xs.micl-slider--vertical,
242
+ [dir=rtl] input[type=range].micl-slider-s.micl-slider--vertical,
243
+ [dir=rtl] input[type=range].micl-slider-m.micl-slider--vertical,
244
+ [dir=rtl] input[type=range].micl-slider-l.micl-slider--vertical,
245
+ [dir=rtl] input[type=range].micl-slider-xl.micl-slider--vertical {
246
+ writing-mode: sideways-rl;
247
+ &::after {
248
+ transform: rotate(-90deg);
249
+ }
250
+ }
251
+
252
+ [dir=rtl] input[type=range].micl-slider-xs:not(.micl-slider--vertical),
253
+ [dir=rtl] input[type=range].micl-slider-s:not(.micl-slider--vertical),
254
+ [dir=rtl] input[type=range].micl-slider-m:not(.micl-slider--vertical),
255
+ [dir=rtl] input[type=range].micl-slider-l:not(.micl-slider--vertical),
256
+ [dir=rtl] input[type=range].micl-slider-xl:not(.micl-slider--vertical) {
257
+ --md-sys-slider-track-direction: to left;
258
+ }
@@ -0,0 +1,49 @@
1
+ # Switch
2
+ This component implements the the [Material Design 3 Expressive Switch](https://m3.material.io/components/switch/overview) design.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ To add a basic switch, use the `<input type="checkbox">` element with the `micl-switch` class, paired with a `<label>` element:
8
+
9
+ ```HTML
10
+ <input type="checkbox" id="myswitch" class="micl-switch" role="switch" value="foo">
11
+ <label for="myswitch">My choice</label>
12
+ ```
13
+
14
+ ### CSS
15
+ Import the switch styles into your project:
16
+
17
+ ```CSS
18
+ @use "micl/components/switch";
19
+ ```
20
+
21
+ ### TypeScript
22
+ No custom TypeScript is required for the core functionality of this component.
23
+
24
+ ### Demo
25
+ A live example of the [Switch component](https://henkpb.github.io/micl/switch.html) is available for you to interact with.
26
+
27
+ ## Variants
28
+ By default, the component displays an icon on the switch handle in both the selected and unselected state. To remove the icon in the unselected state, assign an empty string to the following CSS variable:
29
+
30
+ ```CSS
31
+ #myswitch {
32
+ --md-sys-switch-unselected-icon: "";
33
+ }
34
+ ```
35
+ To remove the icon in the selected state:
36
+ ```CSS
37
+ #myswitch {
38
+ --md-sys-switch-selected-icon: "";
39
+ }
40
+ ```
41
+
42
+ A switch can be disabled by adding the `disabled` attribute to the `<input>` element.
43
+
44
+ The switch component is aware of the `dir` global attribute that indicates the directionality of text.
45
+
46
+ Note that the component assigns a default color and `cursor: pointer` to the `<label>` element immediately preceding or immediately following the `<input>` element. Of course, you may change these CSS settings to something more appropriate.
47
+
48
+ ## Compatibility
49
+ This component uses the `color-mix` CSS functional notation, which might not be supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix#browser_compatibility) for details.
@@ -0,0 +1,176 @@
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
+
24
+ input[type=checkbox].micl-switch {
25
+ --md-sys-switch-target-height: 32px;
26
+ --md-sys-switch-target-width: 52px;
27
+ --md-sys-switch-state-layer-size: 40px;
28
+ --md-sys-switch-handle-size: 16px;
29
+ --md-sys-switch-handle-selected-size: 24px;
30
+ --md-sys-switch-handle-pressed-size: 28px;
31
+ --md-sys-switch-unselected-icon: "+";
32
+ --md-sys-switch-selected-icon: "\AC";
33
+ --md-sys-switch-outline-width: 2px;
34
+
35
+ appearance: none;
36
+ position: relative;
37
+ width: var(--md-sys-switch-target-width);
38
+ height: var(--md-sys-target-size);
39
+ margin: 0;
40
+ border-radius: calc(var(--md-sys-switch-target-height) / 2);
41
+ outline: none;
42
+
43
+ &::before {
44
+ content: "";
45
+ box-sizing: border-box;
46
+ display: block;
47
+ width: var(--md-sys-switch-target-width);
48
+ height: var(--md-sys-switch-target-height);
49
+ margin-block: calc((var(--md-sys-target-size) - var(--md-sys-switch-target-height)) / 2);
50
+ border: var(--md-sys-switch-outline-width) solid var(--md-sys-color-outline);
51
+ border-radius: inherit;
52
+ background-color: var(--md-sys-color-surface-container-highest);
53
+ transition: background-color var(--md-sys-motion-duration-long4) motion.$md-sys-motion-easing-emphasized;
54
+ }
55
+ &::after {
56
+ content: var(--md-sys-switch-unselected-icon);
57
+ box-sizing: border-box;
58
+ position: absolute;
59
+ width: var(--md-sys-switch-state-layer-size);
60
+ height: var(--md-sys-switch-state-layer-size);
61
+ inset: 0;
62
+ inset-inline-start: -4px;
63
+ margin: auto 0;
64
+ font: 300 1rem/1rem var(--md-ref-typeface-plain);
65
+ color: var(--md-sys-color-surface-container-highest);
66
+ text-align: center;
67
+ background-color: var(--md-sys-color-outline);
68
+ background-clip: content-box;
69
+ border: calc((var(--md-sys-switch-state-layer-size) - var(--md-sys-switch-handle-size)) / 2) solid transparent;
70
+ border-radius: var(--md-sys-shape-corner-full);
71
+ transform: rotate(135deg);
72
+ transition:
73
+ inset-inline-start var(--md-sys-motion-duration-long4) linear(motion.$md-sys-motion-spring-slow-spatial),
74
+ border-width var(--md-sys-motion-duration-long4) linear(motion.$md-sys-motion-spring-slow-spatial),
75
+ font-size var(--md-sys-motion-duration-long4) linear(motion.$md-sys-motion-spring-slow-spatial),
76
+ line-height var(--md-sys-motion-duration-long4) linear(motion.$md-sys-motion-spring-slow-spatial),
77
+ color var(--md-sys-motion-duration-long4) motion.$md-sys-motion-easing-emphasized,
78
+ border-color var(--md-sys-motion-duration-long4) motion.$md-sys-motion-easing-emphasized,
79
+ background-color var(--md-sys-motion-duration-long4) motion.$md-sys-motion-easing-emphasized;
80
+ }
81
+ &:checked {
82
+ &::before {
83
+ background-color: var(--md-sys-color-primary);
84
+ }
85
+ &::after {
86
+ content: var(--md-sys-switch-selected-icon);
87
+ inset-inline-start: 16px;
88
+ font-size: 2rem;
89
+ line-height: 1.4rem;
90
+ border: calc((var(--md-sys-switch-state-layer-size) - var(--md-sys-switch-handle-selected-size)) / 2) solid transparent;
91
+ color: var(--md-sys-color-on-primary-container);
92
+ background-color: var(--md-sys-color-on-primary);
93
+ }
94
+ }
95
+ &:not(:disabled) {
96
+ cursor: pointer;
97
+
98
+ &:hover {
99
+ &::after {
100
+ border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-hover-state-layer-opacity), transparent);
101
+ background-color: var(--md-sys-color-on-surface-variant);
102
+ }
103
+ &:checked::after {
104
+ border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-hover-state-layer-opacity), transparent);
105
+ background-color: var(--md-sys-color-primary-container);
106
+ }
107
+ }
108
+ &:focus-visible {
109
+ &::after {
110
+ border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-focus-state-layer-opacity), transparent);
111
+ }
112
+ &::before {
113
+ outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
114
+ outline-offset: 2px;
115
+ }
116
+ &:checked::after {
117
+ border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-focus-state-layer-opacity), transparent);
118
+ background-color: var(--md-sys-color-primary-container);
119
+ }
120
+ &:not(:checked)::after {
121
+ background-color: var(--md-sys-color-on-surface-variant);
122
+ }
123
+ }
124
+ &:active {
125
+ &::after {
126
+ font-size: 2rem;
127
+ line-height: 1.7rem;
128
+ border: calc((var(--md-sys-switch-state-layer-size) - var(--md-sys-switch-handle-pressed-size)) / 2) solid transparent;
129
+ background-color: var(--md-sys-color-on-surface-variant);
130
+ }
131
+ &:checked::after {
132
+ line-height: 1.7rem;
133
+ border-width: calc((var(--md-sys-switch-state-layer-size) - var(--md-sys-switch-handle-pressed-size)) / 2);
134
+ border-color: color-mix(in srgb, var(--md-sys-color-primary) var(--md-sys-state-pressed-state-layer-opacity), transparent);
135
+ background-color: var(--md-sys-color-primary-container);
136
+ }
137
+ &:not(:checked)::after {
138
+ inset-inline-start: -4px;
139
+ border-color: color-mix(in srgb, var(--md-sys-color-on-surface) var(--md-sys-state-pressed-state-layer-opacity), transparent);
140
+ }
141
+ }
142
+ }
143
+ &:disabled {
144
+ &::before {
145
+ border-color: var(--md-sys-color-on-surface);
146
+ background-color: var(--md-sys-color-surface-container-highest);
147
+ opacity: 12%;
148
+ }
149
+ &:checked::before {
150
+ background-color: var(--md-sys-color-on-surface);
151
+ opacity: 12%;
152
+ }
153
+ &::after {
154
+ color: color-mix(in srgb, var(--md-sys-color-surface-container-highest) 38%, transparent);
155
+ background-color: var(--md-sys-color-on-surface);
156
+ opacity: 38%;
157
+ }
158
+ &:checked::after {
159
+ color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
160
+ background-color: var(--md-sys-color-surface);
161
+ opacity: 100%;
162
+ }
163
+ }
164
+ }
165
+
166
+ input[type=checkbox].micl-switch:not(:disabled) + label,
167
+ label:has(+ input[type=checkbox].micl-switch:not(:disabled)) {
168
+ cursor: pointer;
169
+ }
170
+ input[type=checkbox].micl-switch + label,
171
+ label:has(+ input[type=checkbox].micl-switch) {
172
+ color: var(--md-sys-color-on-surface);
173
+ }
174
+ [dir=rtl] input[type=checkbox].micl-switch::after {
175
+ transform: rotate(45deg) scaleY(-1);
176
+ }
@@ -0,0 +1,75 @@
1
+ # Text field
2
+ This component implements the the [Material Design 3 Expressive Text field](https://m3.material.io/components/text-fields/overview) design.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ A basic text field can be either `filled` or `outlined`. To create one, use the following HTML and simply swap the class name to change the style.
8
+
9
+ ```HTML
10
+ <div class="micl-textfield-filled">
11
+ <label for="mytextfield">Label text</label>
12
+ <input type="text" id="mytextfield">
13
+ </div>
14
+ ```
15
+
16
+ ### CSS
17
+ Import the text field styles into your project:
18
+
19
+ ```CSS
20
+ @use "micl/components/textfield";
21
+ ```
22
+
23
+ ### TypeScript
24
+ This component requires a TypeScript module for interactive features like the **character counter**. You can import the specific module and handle initialization manually, or use the main MICL library for automatic initialization.
25
+
26
+ To manually initialize the component and attach an event listener for the `input` event:
27
+
28
+ ```TypeScript
29
+ import miclTextField from 'micl/components/textfield';
30
+
31
+ miclTextField.initialize(document.querySelector('.micl-textfield-filled'));
32
+
33
+ document.querySelector('.micl-textfield-outlined').addEventListener('input', miclTextField.input);
34
+ ```
35
+
36
+ ### Demo
37
+ A live example of the [Text field component](https://henkpb.github.io/micl/textfield.html) is available for you to interact with.
38
+
39
+ ## Variants
40
+ The following example shows a text field with every available feature. You can include any combination of these elements. The order of elements inside the `<div>` does not change the layout.
41
+
42
+ ```HTML
43
+ <div class="micl-textfield-filled">
44
+ <span class="micl-textfield__icon-leading material-symbols-outlined" aria-hidden="true">search</span>
45
+ <label for="mytextfield">Label text</label>
46
+ <span class="micl-textfield__prefix">$</span>
47
+ <input type="text" id="mytextfield" maxlength="20">
48
+ <span class="micl-textfield__suffix">kg</span>
49
+ <span class="micl-textfield__icon-trailing material-symbols-outlined" aria-hidden="true">cancel</span>
50
+ <span class="micl-textfield__supporting-text">Supporting text</span>
51
+ <span class="micl-textfield__character-counter"></span>
52
+ </div>
53
+ ```
54
+
55
+ The `<input>` element can have the following types: `text`, `date`, `datetime-local`, `email`, `month`, `number`, `password`, `tel`, `time`, `url` and `week`.
56
+
57
+ Two icons may be included in the layout: a **leading icon** (an element containing the `micl-textfield__icon-leading` class), and a **trailing icon** (an element containing the `micl-textfield__icon-trailing` class).
58
+
59
+ A **prefix** (e.g., "$") and a **suffix** (e.g., "kg") can be included to provide additional context. You can customize the spacing by overriding CSS variables:
60
+
61
+ ```HTML
62
+ <span class="micl-textfield__prefix" style="--md-sys-textfield-prefix-space:20px">USD</span>
63
+ <span class="micl-textfield__suffix" style="--md-sys-textfield-suffix-space:10em">@gmail.com</span>
64
+ ```
65
+
66
+ Use an element with the `micl-textfield__supporting-text` class to add extra information about the text field.
67
+
68
+ If the `<input>` element includes the `maxlength` attribute, the **character counter** will display automatically in the element with the `micl-textfield__character-counter` class.
69
+
70
+ Adding the `disabled` boolean attribute to the `<input>` element causes the text field to be displayed in a disabled state.
71
+
72
+ Adding the `micl-textfield--error` class to the text field displays it in an error state.
73
+
74
+ ## Compatibility
75
+ This component uses relative RGB colors, which might not be 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,81 @@
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 textfieldSelector = '.micl-textfield-outlined > input,.micl-textfield-filled > input';
23
+ export const selectSelector = '.micl-textfield-outlined > select,.micl-textfield-filled > select';
24
+
25
+ export default (() =>
26
+ {
27
+ const counterSelector = '.micl-textfield-character-counter';
28
+
29
+ return {
30
+ initialize: (element: HTMLInputElement | HTMLSelectElement): void =>
31
+ {
32
+ if (element.dataset.miclinitialized) {
33
+ return;
34
+ }
35
+ element.dataset.miclinitialized = '1';
36
+
37
+ if (element.value) {
38
+ element.dataset.miclvalue = '1';
39
+ }
40
+
41
+ if (
42
+ (element instanceof HTMLInputElement)
43
+ && !!element.maxLength
44
+ ) {
45
+ const counter = element.parentElement?.querySelector(counterSelector);
46
+ if (counter) {
47
+ counter.textContent = `${element.value.length}/${element.maxLength}`;
48
+ }
49
+ }
50
+ },
51
+
52
+ input: (event: Event): void =>
53
+ {
54
+ if (
55
+ !(event.target as Element).matches(`${textfieldSelector},${selectSelector}`)
56
+ || !((event.target instanceof HTMLInputElement) || (event.target instanceof HTMLSelectElement))
57
+ || !event.target.dataset.miclinitialized
58
+ || event.target.disabled
59
+ ) {
60
+ return;
61
+ }
62
+
63
+ if (event.target.value) {
64
+ event.target.dataset.miclvalue = '1';
65
+ }
66
+ else {
67
+ delete event.target.dataset.miclvalue;
68
+ }
69
+
70
+ if (
71
+ (event.target instanceof HTMLInputElement)
72
+ && !!event.target.maxLength
73
+ ) {
74
+ const counter = event.target.parentElement?.querySelector(counterSelector);
75
+ if (counter) {
76
+ counter.textContent = `${event.target.value.length}/${event.target.maxLength}`;
77
+ }
78
+ }
79
+ }
80
+ };
81
+ })();