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,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
|
+
})();
|