material-inspired-component-library 7.0.2 → 8.0.1
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/.claude/settings.local.json +14 -0
- package/CLAUDE.md +53 -0
- package/README.md +6 -0
- package/components/accordion/README.md +6 -3
- package/components/alert/index.scss +5 -0
- package/components/appbar/index.scss +12 -0
- package/components/badge/index.scss +2 -0
- package/components/bottomsheet/index.scss +9 -0
- package/components/button/index.scss +33 -6
- package/components/card/README.md +4 -0
- package/components/card/index.scss +182 -150
- package/components/checkbox/index.scss +28 -6
- package/components/datepicker/index.scss +13 -0
- package/components/datepicker/index.ts +9 -9
- package/components/dialog/index.scss +21 -6
- package/components/iconbutton/index.scss +28 -6
- package/components/list/README.md +191 -32
- package/components/list/index.scss +281 -190
- package/components/list/index.ts +100 -100
- package/components/menu/README.md +199 -10
- package/components/menu/index.scss +242 -47
- package/components/menu/index.ts +74 -37
- package/components/navigationrail/index.scss +91 -68
- package/components/progressindicator/README.md +88 -0
- package/components/progressindicator/index.scss +225 -0
- package/components/progressindicator/index.ts +77 -0
- package/components/radio/index.scss +24 -6
- package/components/select/README.md +42 -5
- package/components/select/index.scss +45 -79
- package/components/shape/README.md +103 -0
- package/components/shape/_paths.generated.scss +64 -0
- package/components/shape/index.scss +66 -0
- package/components/shape/master.scss +28 -0
- package/components/sidesheet/index.scss +11 -0
- package/components/slider/index.scss +13 -0
- package/components/snackbar/index.scss +12 -0
- package/components/stepper/index.scss +3 -5
- package/components/switch/index.scss +9 -0
- package/components/textfield/index.scss +10 -1
- package/components/textfield/index.ts +2 -2
- package/components/timepicker/index.scss +16 -0
- package/dist/alert.css +1 -1
- package/dist/appbar.css +1 -1
- package/dist/badge.css +1 -1
- package/dist/bottomsheet.css +1 -1
- package/dist/button.css +1 -1
- package/dist/card.css +1 -1
- package/dist/checkbox.css +1 -1
- package/dist/components/list/index.d.ts +2 -2
- package/dist/components/progressindicator/index.d.ts +6 -0
- package/dist/datepicker.css +1 -1
- package/dist/dialog.css +1 -1
- package/dist/divider.css +1 -1
- package/dist/foundations/form/index.js +1 -0
- package/dist/foundations.css +1 -1
- package/dist/iconbutton.css +1 -1
- package/dist/layout.css +1 -1
- package/dist/list.css +1 -1
- package/dist/menu.css +1 -1
- package/dist/micl.css +1 -1
- package/dist/micl.js +1 -1
- package/dist/navigationrail.css +1 -1
- package/dist/progressindicator.css +1 -0
- package/dist/progressindicator.js +1 -0
- package/dist/radio.css +1 -1
- package/dist/select.css +1 -1
- package/dist/shape.css +1 -0
- package/dist/shape.js +1 -0
- package/dist/sidesheet.css +1 -1
- package/dist/slider.css +1 -1
- package/dist/snackbar.css +1 -1
- package/dist/stepper.css +1 -1
- package/dist/switch.css +1 -1
- package/dist/textfield.css +1 -1
- package/dist/timepicker.css +1 -1
- package/docs/accordion.html +24 -24
- package/docs/bottomsheet.html +1 -4
- package/docs/datepicker.html +21 -21
- package/docs/dialog.html +1 -1
- package/docs/index.html +5 -4
- package/docs/list.html +38 -22
- package/docs/menu.html +246 -41
- package/docs/micl.css +1 -1
- package/docs/micl.js +1 -1
- package/docs/progressindicator.html +288 -0
- package/docs/select.html +68 -19
- package/docs/shape.css +1 -0
- package/docs/shape.js +1 -0
- package/docs/shapes.html +150 -0
- package/foundations/index.scss +0 -1
- package/foundations/layout/README.md +1 -1
- package/foundations/layout/index.scss +3 -0
- package/micl.ts +8 -1
- package/package.json +6 -4
- package/styles/README.md +90 -12
- package/styles/elevation.scss +46 -13
- package/styles/motion.scss +51 -47
- package/styles/shapes.scss +41 -26
- package/styles/statelayer.scss +93 -36
- package/styles/typography.scss +120 -322
- package/styles.scss +10 -6
- package/tools/shapes/check.mjs +42 -0
- package/tools/shapes/generate.mjs +834 -0
- package/webpack.config.js +16 -1
|
@@ -27,6 +27,23 @@
|
|
|
27
27
|
@use '../../styles/statelayer';
|
|
28
28
|
@use '../../styles/typography';
|
|
29
29
|
|
|
30
|
+
@include elevation.level(0);
|
|
31
|
+
@include elevation.level(2);
|
|
32
|
+
|
|
33
|
+
@include shapes.corner('large');
|
|
34
|
+
|
|
35
|
+
@include statelayer.token('hover-state-layer-opacity');
|
|
36
|
+
@include statelayer.token('focus-state-layer-opacity');
|
|
37
|
+
@include statelayer.token('pressed-state-layer-opacity');
|
|
38
|
+
@include statelayer.token('backdrop-opacity');
|
|
39
|
+
@include statelayer.token('ripple-opacity-factor');
|
|
40
|
+
@include statelayer.token('ripple-duration');
|
|
41
|
+
@include statelayer.property;
|
|
42
|
+
@include statelayer.keyframes;
|
|
43
|
+
|
|
44
|
+
@include typography.scale('label-large');
|
|
45
|
+
@include typography.scale('label-medium');
|
|
46
|
+
|
|
30
47
|
.micl-navigationrail {
|
|
31
48
|
--md-comp-nav-rail-spring-buffer: 100px;
|
|
32
49
|
--md-comp-nav-rail-divider-thickness: 0px;
|
|
@@ -36,19 +53,18 @@
|
|
|
36
53
|
--md-comp-nav-rail-morph-duration: #{motion.$md-sys-motion-expressive-fast-spatial-duration};
|
|
37
54
|
--md-comp-nav-rail-morph-duration-reverse: #{motion.$md-sys-motion-expressive-fast-spatial-duration};
|
|
38
55
|
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
|
|
44
|
-
--_item-target-height: var(--_item-base-height);
|
|
56
|
+
--_navigationrail-current-max-width: var(--md-comp-nav-rail-collapsed-container-width, 96px);
|
|
57
|
+
--_navigationrail-current-min-width: var(--md-comp-nav-rail-collapsed-container-width, 96px);
|
|
58
|
+
--_navigationrail-item-direction: column;
|
|
59
|
+
--_navigationrail-item-width: var(--md-comp-nav-rail-item-short-container-height, 56px);
|
|
60
|
+
--_navigationrail-item-target-height: var(--_navigationrail-item-base-height);
|
|
45
61
|
|
|
46
|
-
--
|
|
47
|
-
--
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
--
|
|
51
|
-
--
|
|
62
|
+
--_navigationrail-content-gap: var(--md-comp-nav-rail-item-container-vertical-space, 6px);
|
|
63
|
+
--_navigationrail-morph-speed: var(--md-comp-nav-rail-morph-duration-reverse);
|
|
64
|
+
--_navigationrail-container-delay: calc(var(--_navigationrail-morph-speed) / 2);
|
|
65
|
+
--_navigationrail-item-delay: 0ms;
|
|
66
|
+
--_navigationrail-text-animation: none;
|
|
67
|
+
--_navigationrail-expanded-text-margin: calc(var(--md-comp-nav-rail-item-active-indicator-icon-label-space, 8px) - 16px);
|
|
52
68
|
|
|
53
69
|
box-sizing: border-box;
|
|
54
70
|
display: flex;
|
|
@@ -70,11 +86,11 @@
|
|
|
70
86
|
interpolate-size: allow-keywords;
|
|
71
87
|
|
|
72
88
|
transition:
|
|
73
|
-
min-inline-size var(--
|
|
74
|
-
max-inline-size var(--
|
|
75
|
-
padding-block-start var(--
|
|
76
|
-
min-inline-size: var(--
|
|
77
|
-
max-inline-size: var(--
|
|
89
|
+
min-inline-size var(--_navigationrail-morph-speed) var(--_navigationrail-container-delay) linear,
|
|
90
|
+
max-inline-size var(--_navigationrail-morph-speed) var(--_navigationrail-container-delay) linear,
|
|
91
|
+
padding-block-start var(--_navigationrail-morph-speed) var(--_navigationrail-container-delay) linear;
|
|
92
|
+
min-inline-size: var(--_navigationrail-current-min-width);
|
|
93
|
+
max-inline-size: var(--_navigationrail-current-max-width);
|
|
78
94
|
|
|
79
95
|
&> .micl-navigationrail__headline {
|
|
80
96
|
padding-inline-start: 28px;
|
|
@@ -87,12 +103,12 @@
|
|
|
87
103
|
flex: 1 1 auto;
|
|
88
104
|
align-items: flex-start;
|
|
89
105
|
inline-size: 100%;
|
|
90
|
-
row-gap: var(--
|
|
106
|
+
row-gap: var(--_navigationrail-content-gap);
|
|
91
107
|
padding-block: var(--md-comp-nav-rail-item-header-space-minimum, 40px) 16px;
|
|
92
108
|
padding-inline: 20px 0;
|
|
93
109
|
|
|
94
110
|
overflow: hidden auto;
|
|
95
|
-
transition: row-gap var(--
|
|
111
|
+
transition: row-gap var(--_navigationrail-morph-speed) linear;
|
|
96
112
|
|
|
97
113
|
&> a.micl-navigationrail__item:focus-visible {
|
|
98
114
|
--statelayer-opacity: var(--md-sys-state-focus-state-layer-opacity, 10%);
|
|
@@ -101,9 +117,9 @@
|
|
|
101
117
|
&> a.micl-navigationrail__item {
|
|
102
118
|
--micl-ripple: 1;
|
|
103
119
|
--statelayer-color: var(--md-sys-color-on-secondary-container);
|
|
104
|
-
--
|
|
105
|
-
--
|
|
106
|
-
--
|
|
120
|
+
--_navigationrail-item-base-height: var(--md-comp-nav-rail-item-short-container-height, 56px);
|
|
121
|
+
--_navigationrail-item-margin-bottom: 0px;
|
|
122
|
+
--_navigationrail-item-padding-block: 12px;
|
|
107
123
|
|
|
108
124
|
box-sizing: border-box;
|
|
109
125
|
display: flex;
|
|
@@ -117,20 +133,25 @@
|
|
|
117
133
|
overflow: visible;
|
|
118
134
|
background-color: transparent;
|
|
119
135
|
background-image:
|
|
120
|
-
radial-gradient(
|
|
136
|
+
radial-gradient(
|
|
137
|
+
circle at var(--micl-x, center) var(--micl-y, center),
|
|
138
|
+
transparent 0%,
|
|
139
|
+
rgb(from var(--statelayer-color) r g b / calc(var(--statelayer-opacity) * var(--md-sys-state-ripple-opacity-factor))) 10%,
|
|
140
|
+
transparent 10%
|
|
141
|
+
),
|
|
121
142
|
linear-gradient(rgb(from var(--statelayer-color) r g b / var(--statelayer-opacity)));
|
|
122
143
|
background-repeat: no-repeat;
|
|
123
|
-
background-size:
|
|
144
|
+
background-size: 0%, 100%;
|
|
124
145
|
|
|
125
146
|
&:has(.micl-navigationrail__text) {
|
|
126
|
-
--
|
|
127
|
-
--
|
|
128
|
-
--
|
|
147
|
+
--_navigationrail-item-base-height: var(--md-comp-nav-rail-item-vertical-active-indicator-height, 32px);
|
|
148
|
+
--_navigationrail-item-margin-bottom: 40px;
|
|
149
|
+
--_navigationrail-item-padding-block: 0px;
|
|
129
150
|
}
|
|
130
151
|
&:not(:has(.micl-navigationrail__icon)) {
|
|
131
|
-
--
|
|
132
|
-
--
|
|
133
|
-
--
|
|
152
|
+
--_navigationrail-item-base-height: var(--md-comp-nav-rail-item-short-container-height, 56px);
|
|
153
|
+
--_navigationrail-item-margin-bottom: 0px;
|
|
154
|
+
--_navigationrail-item-padding-block: 0px;
|
|
134
155
|
|
|
135
156
|
justify-content: center;
|
|
136
157
|
|
|
@@ -146,28 +167,28 @@
|
|
|
146
167
|
animation: none !important;
|
|
147
168
|
|
|
148
169
|
transition:
|
|
149
|
-
padding-inline calc(var(--
|
|
150
|
-
margin-inline calc(var(--
|
|
170
|
+
padding-inline calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
171
|
+
margin-inline calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear;
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
174
|
|
|
154
|
-
inline-size: var(--
|
|
155
|
-
block-size: var(--
|
|
156
|
-
margin-block-end: var(--
|
|
157
|
-
padding-block: var(--
|
|
158
|
-
flex-direction: var(--
|
|
175
|
+
inline-size: var(--_navigationrail-item-width);
|
|
176
|
+
block-size: var(--_navigationrail-item-target-height, var(--_navigationrail-item-base-height));
|
|
177
|
+
margin-block-end: var(--_navigationrail-item-target-margin, var(--_navigationrail-item-margin-bottom));
|
|
178
|
+
padding-block: var(--_navigationrail-item-target-padding, var(--_navigationrail-item-padding-block));
|
|
179
|
+
flex-direction: var(--_navigationrail-item-direction);
|
|
159
180
|
|
|
160
|
-
border-radius: calc(var(--
|
|
181
|
+
border-radius: calc(var(--_navigationrail-item-target-height, var(--_navigationrail-item-base-height)) / 2);
|
|
161
182
|
|
|
162
183
|
transition:
|
|
163
|
-
inline-size calc(var(--
|
|
164
|
-
block-size calc(var(--
|
|
165
|
-
margin-block-end calc(var(--
|
|
166
|
-
padding-block calc(var(--
|
|
167
|
-
border-radius calc(var(--
|
|
168
|
-
flex-direction 0s calc(var(--
|
|
169
|
-
background-color calc(var(--
|
|
170
|
-
background-size
|
|
184
|
+
inline-size calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
185
|
+
block-size calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
186
|
+
margin-block-end calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
187
|
+
padding-block calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
188
|
+
border-radius calc(var(--_navigationrail-morph-speed) / 2) var(--_navigationrail-item-delay) linear,
|
|
189
|
+
flex-direction 0s calc(var(--_navigationrail-morph-speed) / 2) linear allow-discrete,
|
|
190
|
+
background-color calc(var(--_navigationrail-morph-speed) / 2) linear,
|
|
191
|
+
background-size 0ms,
|
|
171
192
|
--statelayer-opacity var(--md-comp-nav-rail-motion-duration) linear;
|
|
172
193
|
|
|
173
194
|
.micl-navigationrail__icon {
|
|
@@ -185,7 +206,7 @@
|
|
|
185
206
|
margin: 4px 0px 0px 0px;
|
|
186
207
|
color: var(--md-sys-color-on-surface-variant);
|
|
187
208
|
overflow: hidden;
|
|
188
|
-
animation: var(--
|
|
209
|
+
animation: var(--_navigationrail-text-animation);
|
|
189
210
|
}
|
|
190
211
|
|
|
191
212
|
&:hover {
|
|
@@ -193,8 +214,10 @@
|
|
|
193
214
|
}
|
|
194
215
|
&:active {
|
|
195
216
|
--statelayer-opacity: var(--md-sys-state-pressed-state-layer-opacity, 10%);
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
transition-duration: 0ms, 0ms, 0ms, calc(var(--_navigationrail-morph-speed) / 2), 0ms, var(--md-comp-nav-rail-motion-duration);
|
|
218
|
+
}
|
|
219
|
+
&.micl-rippling {
|
|
220
|
+
animation: micl-ripple var(--md-sys-state-ripple-duration) motion.$md-sys-motion-easing-standard;
|
|
198
221
|
}
|
|
199
222
|
}
|
|
200
223
|
|
|
@@ -239,20 +262,20 @@
|
|
|
239
262
|
|
|
240
263
|
dialog.micl-navigationrail,
|
|
241
264
|
nav.micl-navigationrail:has(> .micl-navigationrail__headline .micl-button--toggle.micl-button--selected) {
|
|
242
|
-
--
|
|
243
|
-
--
|
|
244
|
-
--
|
|
245
|
-
--
|
|
265
|
+
--_navigationrail-current-max-width: var(--md-comp-nav-rail-expanded-container-width-maximum, 360px);
|
|
266
|
+
--_navigationrail-current-min-width: var(--md-comp-nav-rail-expanded-container-width-minimum, 220px);
|
|
267
|
+
--_navigationrail-item-direction: row;
|
|
268
|
+
--_navigationrail-item-width: fit-content;
|
|
246
269
|
|
|
247
|
-
--
|
|
248
|
-
--
|
|
249
|
-
--
|
|
270
|
+
--_navigationrail-item-target-height: var(--md-comp-nav-rail-item-short-container-height, 56px);
|
|
271
|
+
--_navigationrail-item-target-margin: 0px;
|
|
272
|
+
--_navigationrail-item-target-padding: 16px;
|
|
250
273
|
|
|
251
|
-
--
|
|
252
|
-
--
|
|
253
|
-
--
|
|
254
|
-
--
|
|
255
|
-
--
|
|
274
|
+
--_navigationrail-content-gap: 0px;
|
|
275
|
+
--_navigationrail-morph-speed: var(--md-comp-nav-rail-morph-duration);
|
|
276
|
+
--_navigationrail-container-delay: 0ms;
|
|
277
|
+
--_navigationrail-item-delay: calc(var(--_navigationrail-morph-speed) / 2);
|
|
278
|
+
--_navigationrail-text-animation: var(--_navigationrail-morph-speed) linear forwards navigationrail-text-to-expanded;
|
|
256
279
|
|
|
257
280
|
padding-block: var(--md-comp-nav-rail-expanded-top-space, 44px) 0;
|
|
258
281
|
border-radius: var(--md-comp-nav-rail-expanded-container-shape, 0px);
|
|
@@ -269,7 +292,7 @@ nav.micl-navigationrail:has(> .micl-navigationrail__headline .micl-button--toggl
|
|
|
269
292
|
}
|
|
270
293
|
.micl-navigationrail__text {
|
|
271
294
|
@include typography.label-large;
|
|
272
|
-
margin-inline-start: var(--
|
|
295
|
+
margin-inline-start: var(--_navigationrail-expanded-text-margin);
|
|
273
296
|
padding-inline-end: 16px;
|
|
274
297
|
}
|
|
275
298
|
|
|
@@ -281,8 +304,8 @@ nav.micl-navigationrail:has(> .micl-navigationrail__headline .micl-button--toggl
|
|
|
281
304
|
}
|
|
282
305
|
|
|
283
306
|
nav.micl-navigationrail:has(> .micl-navigationrail__headline .micl-button--toggle.micl-button--toggled:not(.micl-button--selected)) {
|
|
284
|
-
--
|
|
285
|
-
--
|
|
307
|
+
--_navigationrail-morph-speed: var(--md-comp-nav-rail-morph-duration-reverse);
|
|
308
|
+
--_navigationrail-text-animation: var(--_navigationrail-morph-speed) linear forwards navigationrail-text-to-collapsed;
|
|
286
309
|
}
|
|
287
310
|
|
|
288
311
|
nav.micl-navigationrail {
|
|
@@ -324,14 +347,14 @@ dialog.micl-navigationrail {
|
|
|
324
347
|
0% {
|
|
325
348
|
@include typography.label-large;
|
|
326
349
|
margin-block-start: 0;
|
|
327
|
-
margin-inline-start: var(--
|
|
350
|
+
margin-inline-start: var(--_navigationrail-expanded-text-margin);
|
|
328
351
|
padding-inline-end: 16px;
|
|
329
352
|
opacity: 100%
|
|
330
353
|
}
|
|
331
354
|
49.9% {
|
|
332
355
|
@include typography.label-large;
|
|
333
356
|
margin-block-start: 0;
|
|
334
|
-
margin-inline-start: var(--
|
|
357
|
+
margin-inline-start: var(--_navigationrail-expanded-text-margin);
|
|
335
358
|
padding-inline-end: 16px;
|
|
336
359
|
opacity: 0%;
|
|
337
360
|
}
|
|
@@ -373,14 +396,14 @@ dialog.micl-navigationrail {
|
|
|
373
396
|
50.1% {
|
|
374
397
|
@include typography.label-large;
|
|
375
398
|
margin-block-start: 0px;
|
|
376
|
-
margin-inline-start: var(--
|
|
399
|
+
margin-inline-start: var(--_navigationrail-expanded-text-margin);
|
|
377
400
|
padding-inline-end: 16px;
|
|
378
401
|
opacity: 0%;
|
|
379
402
|
}
|
|
380
403
|
100% {
|
|
381
404
|
@include typography.label-large;
|
|
382
405
|
margin-block-start: 0px;
|
|
383
|
-
margin-inline-start: var(--
|
|
406
|
+
margin-inline-start: var(--_navigationrail-expanded-text-margin);
|
|
384
407
|
padding-inline-end: 16px;
|
|
385
408
|
opacity: 100%;
|
|
386
409
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Progress Indicator
|
|
2
|
+
This component implements the [Material Design 3 Expressive Progress indicators](https://m3.material.io/components/progress-indicators/overview) design. Progress indicators show the status of a process in real time, either as a determinate value or as an indeterminate, looping animation.
|
|
3
|
+
|
|
4
|
+
## Basic Usage
|
|
5
|
+
|
|
6
|
+
### HTML
|
|
7
|
+
Progress indicators use the native `<progress>` element. Add the `micl-progress-linear` class for the linear (bar) variant or `micl-progress-circular` for the circular (ring) variant.
|
|
8
|
+
|
|
9
|
+
A **determinate** indicator has a known value. Provide the `value` and (optionally) `max` attributes:
|
|
10
|
+
|
|
11
|
+
```HTML
|
|
12
|
+
<progress class="micl-progress-linear" value="0.6" max="1"></progress>
|
|
13
|
+
<progress class="micl-progress-circular" value="60" max="100"></progress>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
An **indeterminate** indicator has an unknown value. Omit the `value` attribute entirely:
|
|
17
|
+
|
|
18
|
+
```HTML
|
|
19
|
+
<progress class="micl-progress-linear"></progress>
|
|
20
|
+
<progress class="micl-progress-circular"></progress>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### CSS
|
|
24
|
+
Import the progress indicator styles into your project:
|
|
25
|
+
|
|
26
|
+
```CSS
|
|
27
|
+
@use "material-inspired-component-library/dist/progressindicator";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or import all MICL styles:
|
|
31
|
+
```CSS
|
|
32
|
+
@use "material-inspired-component-library/styles";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### JavaScript
|
|
36
|
+
No custom JavaScript is required for indeterminate indicators — the looping animation is driven entirely by CSS via the native `:indeterminate` state.
|
|
37
|
+
|
|
38
|
+
For determinate indicators, the bundled handler mirrors the `value` and `max` attributes into the CSS custom properties that drive the fill, the active/track gap and the Expressive wave amplitude. It also flattens the wave into a straight line over the final 10% of progress.
|
|
39
|
+
|
|
40
|
+
> **Updating progress at runtime:** the handler observes the `value` and `max` *attributes*. When you update progress dynamically, set the attribute (`element.setAttribute('value', 0.7)`) rather than only the IDL property (`element.value = 0.7`), so the indicator stays in sync. Alternatively, you may set the `--md-comp-progress-fraction` custom property (a number between `0` and `1`) directly for pure-CSS control without the handler.
|
|
41
|
+
|
|
42
|
+
### Live Demo
|
|
43
|
+
A live example of the [Progress Indicator component](https://henkpb.github.io/micl/progressindicator.html) is available to interact with.
|
|
44
|
+
|
|
45
|
+
## Variants
|
|
46
|
+
The Progress Indicator component offers the following variants:
|
|
47
|
+
|
|
48
|
+
| CSS class | Description |
|
|
49
|
+
| --------- | ----------- |
|
|
50
|
+
| micl-progress-linear | A horizontal linear progress bar |
|
|
51
|
+
| micl-progress-circular | A circular progress ring |
|
|
52
|
+
| micl-progress-circular--s | Small circular ring (28px) |
|
|
53
|
+
| micl-progress-circular--m | Medium circular ring (48px, default) |
|
|
54
|
+
| micl-progress-circular--l | Large circular ring (64px) |
|
|
55
|
+
|
|
56
|
+
Each variant is **determinate** when a `value` attribute is present and **indeterminate** when it is omitted.
|
|
57
|
+
|
|
58
|
+
The signature Expressive *wavy active indicator* is applied to the linear variant as a static shape (rasterised once, so any number of determinate indicators cost nothing per frame). Its amplitude eases to zero as the indicator reaches completion, and setting `--md-comp-progress-wave-amplitude` to `0` produces a flat (pre-Expressive) active indicator. Only the transient loading states animate: the linear indeterminate comet, and the circular indeterminate spinner (a GPU-composited rotation). The circular variant renders a smooth ring.
|
|
59
|
+
|
|
60
|
+
The component respects the `dir` global attribute, automatically mirroring the linear sweep direction for right-to-left (RTL) languages when `dir="rtl"` is applied to an ancestor element. It also honours the user's `prefers-reduced-motion` setting by disabling the travelling wave and looping animations.
|
|
61
|
+
|
|
62
|
+
## Customizations
|
|
63
|
+
You can customize the appearance of the Progress Indicator component by overriding its global CSS variables. Following the MICL convention, these `--md-comp-progress-*` variables can be set on `:root` or on any appropriate parent element to affect its child progress indicators. The colour roles default to the Material Design system colour tokens per the M3 progress-indicator specification.
|
|
64
|
+
|
|
65
|
+
| Variable name | Default Value | Description |
|
|
66
|
+
| ------------- | ------------- | ----------- |
|
|
67
|
+
| --md-comp-progress-active-color | var(--md-sys-color-primary) | Colour of the active (filled) indicator |
|
|
68
|
+
| --md-comp-progress-track-color | var(--md-sys-color-secondary-container) | Colour of the remaining track |
|
|
69
|
+
| --md-comp-progress-stop-color | var(--md-sys-color-primary) | Colour of the linear end stop indicator dot |
|
|
70
|
+
| --md-comp-progress-thickness | 4px | Thickness of the linear track and circular ring stroke |
|
|
71
|
+
| --md-comp-progress-track-gap | 4px | Gap between the active indicator and the remaining track (linear) |
|
|
72
|
+
| --md-comp-progress-stop-size | 4px | Diameter of the linear end stop indicator dot |
|
|
73
|
+
| --md-comp-progress-linear-width | 100% | Default width of the linear indicator |
|
|
74
|
+
| --md-comp-progress-wave-wavelength | 40px | Wavelength of the Expressive active wave |
|
|
75
|
+
| --md-comp-progress-wave-amplitude | 3px | Amplitude of the Expressive active wave (set to `0` to disable) |
|
|
76
|
+
| --md-comp-progress-indeterminate-duration | var(--md-sys-motion-duration-extra-long4) | Period of the indeterminate loop |
|
|
77
|
+
| --md-comp-progress-circular-size | 48px | Diameter of the circular indicator |
|
|
78
|
+
| --md-comp-progress-wave-image | (inline SVG) | The sine-ribbon mask used for the wavy active indicator |
|
|
79
|
+
|
|
80
|
+
**Example: A thicker linear indicator with a calmer wave**
|
|
81
|
+
|
|
82
|
+
```HTML
|
|
83
|
+
<progress class="micl-progress-linear" value="0.4"
|
|
84
|
+
style="--md-comp-progress-thickness:8px;--md-comp-progress-wave-amplitude:2px"></progress>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Compatibility
|
|
88
|
+
This component utilizes relative RGB color values, CSS `mask`, `conic-gradient` and registered `@property` custom properties, 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,225 @@
|
|
|
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 '../../foundations';
|
|
23
|
+
@use '../../styles/motion';
|
|
24
|
+
|
|
25
|
+
@include motion.duration('long2');
|
|
26
|
+
@include motion.duration('extra-long4');
|
|
27
|
+
|
|
28
|
+
// Registered so determinate updates from the JS handler interpolate smoothly.
|
|
29
|
+
// Never keyframe-animated.
|
|
30
|
+
@property --md-comp-progress-fraction {
|
|
31
|
+
syntax: '<number>';
|
|
32
|
+
initial-value: 0;
|
|
33
|
+
inherits: true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Non-themed defaults. Declared on :root so they can be overridden on :root or
|
|
37
|
+
// any parent element (consistent with the other MICL components). The colour
|
|
38
|
+
// roles are resolved per the M3 progress-indicator spec at the point of use
|
|
39
|
+
// (see $active/$track/$stop) because the theme palette is scoped to the
|
|
40
|
+
// .light/.dark window class and is therefore NOT in scope at :root.
|
|
41
|
+
:root {
|
|
42
|
+
--md-comp-progress-thickness: 4px;
|
|
43
|
+
--md-comp-progress-track-gap: 4px;
|
|
44
|
+
--md-comp-progress-stop-size: 4px;
|
|
45
|
+
--md-comp-progress-linear-width: 100%;
|
|
46
|
+
--md-comp-progress-wave-wavelength: 40px;
|
|
47
|
+
--md-comp-progress-wave-amplitude: 3px;
|
|
48
|
+
--md-comp-progress-indeterminate-duration: var(--md-sys-motion-duration-extra-long4);
|
|
49
|
+
--md-comp-progress-circular-size: 48px;
|
|
50
|
+
--md-comp-progress-amplitude-scale: 1;
|
|
51
|
+
|
|
52
|
+
// One wavelength of a sine ribbon, stroked at a constant width, used as a
|
|
53
|
+
// static mask so the active indicator takes a wavy shape. Painted once —
|
|
54
|
+
// no animation, no per-frame cost. The stroke is WHITE so it reveals
|
|
55
|
+
// correctly whether the browser treats the SVG mask as alpha (white =
|
|
56
|
+
// opaque) or luminance (white = 1).
|
|
57
|
+
--md-comp-progress-wave-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 10' preserveAspectRatio='none'%3E%3Cpath d='M0 5 Q10 -1 20 5 T40 5' fill='none' stroke='white' stroke-width='4' stroke-linecap='round'/%3E%3C/svg%3E");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// M3 progress-indicator colour mapping. Resolved on the component element so
|
|
61
|
+
// the system palette (scoped to the themed window ancestor) is in scope; a
|
|
62
|
+
// consumer-supplied --md-comp-progress-*-color on any ancestor still wins.
|
|
63
|
+
$active: var(--md-comp-progress-active-color, var(--md-sys-color-primary));
|
|
64
|
+
$track: var(--md-comp-progress-track-color, var(--md-sys-color-secondary-container));
|
|
65
|
+
$stop: var(--md-comp-progress-stop-color, var(--md-sys-color-primary));
|
|
66
|
+
|
|
67
|
+
%progress-reset {
|
|
68
|
+
-webkit-appearance: none;
|
|
69
|
+
appearance: none;
|
|
70
|
+
border: none;
|
|
71
|
+
margin: 0;
|
|
72
|
+
background-color: transparent;
|
|
73
|
+
color: $active;
|
|
74
|
+
vertical-align: middle;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Static wavy active indicator. No animation: the mask is rasterised once and
|
|
78
|
+
// never sampled again, so a screen full of determinate indicators costs
|
|
79
|
+
// nothing per frame.
|
|
80
|
+
@mixin wave {
|
|
81
|
+
background-color: $active;
|
|
82
|
+
-webkit-mask-image: var(--md-comp-progress-wave-image);
|
|
83
|
+
mask-image: var(--md-comp-progress-wave-image);
|
|
84
|
+
-webkit-mask-repeat: repeat-x;
|
|
85
|
+
mask-repeat: repeat-x;
|
|
86
|
+
-webkit-mask-position: 0 center;
|
|
87
|
+
mask-position: 0 center;
|
|
88
|
+
-webkit-mask-size: var(--md-comp-progress-wave-wavelength)
|
|
89
|
+
calc(var(--md-comp-progress-thickness) + 2 * var(--md-comp-progress-wave-amplitude) * var(--md-comp-progress-amplitude-scale, 1));
|
|
90
|
+
mask-size: var(--md-comp-progress-wave-wavelength)
|
|
91
|
+
calc(var(--md-comp-progress-thickness) + 2 * var(--md-comp-progress-wave-amplitude) * var(--md-comp-progress-amplitude-scale, 1));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//
|
|
95
|
+
// Linear progress indicator
|
|
96
|
+
//
|
|
97
|
+
progress.micl-progress-linear {
|
|
98
|
+
@extend %progress-reset;
|
|
99
|
+
|
|
100
|
+
display: inline-block;
|
|
101
|
+
inline-size: var(--md-comp-progress-linear-width);
|
|
102
|
+
block-size: calc(var(--md-comp-progress-thickness) + 2 * var(--md-comp-progress-wave-amplitude));
|
|
103
|
+
|
|
104
|
+
// Track (remaining portion) + end stop indicator. Starts one track-gap
|
|
105
|
+
// after the active indicator, clipped to --thickness tall and centred.
|
|
106
|
+
@mixin track {
|
|
107
|
+
background-color: transparent;
|
|
108
|
+
background-image:
|
|
109
|
+
radial-gradient(circle, #{$stop} 0 50%, transparent 50%),
|
|
110
|
+
linear-gradient(90deg,
|
|
111
|
+
transparent 0 calc(var(--md-comp-progress-fraction, 0) * 100% + var(--md-comp-progress-track-gap)),
|
|
112
|
+
#{$track} calc(var(--md-comp-progress-fraction, 0) * 100% + var(--md-comp-progress-track-gap)) 100%);
|
|
113
|
+
background-repeat: no-repeat;
|
|
114
|
+
background-position: right center, left center;
|
|
115
|
+
background-size:
|
|
116
|
+
var(--md-comp-progress-stop-size) var(--md-comp-progress-stop-size),
|
|
117
|
+
100% var(--md-comp-progress-thickness);
|
|
118
|
+
border-radius: calc(var(--md-comp-progress-thickness) / 2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
&::-webkit-progress-bar { @include track; }
|
|
122
|
+
&::-webkit-progress-value { @include wave; }
|
|
123
|
+
&::-moz-progress-bar { @include wave; }
|
|
124
|
+
|
|
125
|
+
// Firefox renders the track on the element itself.
|
|
126
|
+
@-moz-document url-prefix() {
|
|
127
|
+
@include track;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Indeterminate: a comet sweeping a transparent track. Only this transient
|
|
131
|
+
// state animates; the move is a single small repainted strip.
|
|
132
|
+
&:indeterminate {
|
|
133
|
+
background-image:
|
|
134
|
+
linear-gradient(90deg, transparent, #{$active}, transparent),
|
|
135
|
+
linear-gradient(#{$track}, #{$track});
|
|
136
|
+
background-repeat: no-repeat;
|
|
137
|
+
background-position: -40% center, left center;
|
|
138
|
+
background-size: 40% var(--md-comp-progress-thickness), 100% var(--md-comp-progress-thickness);
|
|
139
|
+
border-radius: calc(var(--md-comp-progress-thickness) / 2);
|
|
140
|
+
animation: micl-progress-linear-indeterminate var(--md-comp-progress-indeterminate-duration) var(--md-sys-motion-easing-standard, ease-in-out) infinite;
|
|
141
|
+
|
|
142
|
+
&::-webkit-progress-bar,
|
|
143
|
+
&::-webkit-progress-value { background: transparent; }
|
|
144
|
+
&::-moz-progress-bar { background: transparent; }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@keyframes micl-progress-linear-indeterminate {
|
|
149
|
+
0% { background-position: -40% center, left center; }
|
|
150
|
+
100% { background-position: 140% center, left center; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//
|
|
154
|
+
// Circular progress indicator
|
|
155
|
+
//
|
|
156
|
+
// Determinate: a static conic-gradient ring punched by a radial mask (white =
|
|
157
|
+
// kept under both alpha and luminance masking). It is painted once and never
|
|
158
|
+
// animated, so it costs nothing per frame.
|
|
159
|
+
//
|
|
160
|
+
progress.micl-progress-circular {
|
|
161
|
+
@extend %progress-reset;
|
|
162
|
+
|
|
163
|
+
--md-comp-progress-circular-gap-deg: 12deg;
|
|
164
|
+
|
|
165
|
+
display: inline-block;
|
|
166
|
+
box-sizing: border-box;
|
|
167
|
+
inline-size: var(--md-comp-progress-circular-size);
|
|
168
|
+
block-size: var(--md-comp-progress-circular-size);
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
background-image: conic-gradient(
|
|
171
|
+
#{$active} 0
|
|
172
|
+
max(0deg, calc(var(--md-comp-progress-fraction, 0) * 360deg - var(--md-comp-progress-circular-gap-deg))),
|
|
173
|
+
transparent
|
|
174
|
+
max(0deg, calc(var(--md-comp-progress-fraction, 0) * 360deg - var(--md-comp-progress-circular-gap-deg)))
|
|
175
|
+
calc(var(--md-comp-progress-fraction, 0) * 360deg),
|
|
176
|
+
#{$track} calc(var(--md-comp-progress-fraction, 0) * 360deg) 360deg);
|
|
177
|
+
-webkit-mask: radial-gradient(farthest-side,
|
|
178
|
+
transparent calc(100% - var(--md-comp-progress-thickness)),
|
|
179
|
+
#fff calc(100% - var(--md-comp-progress-thickness)));
|
|
180
|
+
mask: radial-gradient(farthest-side,
|
|
181
|
+
transparent calc(100% - var(--md-comp-progress-thickness)),
|
|
182
|
+
#fff calc(100% - var(--md-comp-progress-thickness)));
|
|
183
|
+
|
|
184
|
+
&::-webkit-progress-bar,
|
|
185
|
+
&::-webkit-progress-value { background: transparent; }
|
|
186
|
+
&::-moz-progress-bar { background: transparent; }
|
|
187
|
+
|
|
188
|
+
&.micl-progress-circular--s { --md-comp-progress-circular-size: 28px; }
|
|
189
|
+
&.micl-progress-circular--m { --md-comp-progress-circular-size: 48px; }
|
|
190
|
+
&.micl-progress-circular--l { --md-comp-progress-circular-size: 64px; }
|
|
191
|
+
|
|
192
|
+
// Indeterminate: the canonical lightweight CSS spinner — a plain bordered
|
|
193
|
+
// ring (no gradient, no mask) rotated by transform. Chrome promotes this to
|
|
194
|
+
// a compositor layer, rasterises it once, and spins the texture on the GPU,
|
|
195
|
+
// so it is essentially free per frame regardless of how many are visible.
|
|
196
|
+
&:indeterminate {
|
|
197
|
+
background-image: none;
|
|
198
|
+
-webkit-mask: none;
|
|
199
|
+
mask: none;
|
|
200
|
+
border: var(--md-comp-progress-thickness) solid #{$track};
|
|
201
|
+
border-block-start-color: $active;
|
|
202
|
+
animation: micl-progress-circular-spin var(--md-comp-progress-indeterminate-duration) linear infinite;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@keyframes micl-progress-circular-spin {
|
|
207
|
+
to { transform: rotate(360deg); }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//
|
|
211
|
+
// Right-to-left: mirror the linear sweep direction.
|
|
212
|
+
//
|
|
213
|
+
[dir=rtl] progress.micl-progress-linear {
|
|
214
|
+
transform: scaleX(-1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
//
|
|
218
|
+
// Honour reduced-motion: stop the only two animated (loading) states.
|
|
219
|
+
//
|
|
220
|
+
@media (prefers-reduced-motion: reduce) {
|
|
221
|
+
progress.micl-progress-linear:indeterminate,
|
|
222
|
+
progress.micl-progress-circular:indeterminate {
|
|
223
|
+
animation: none;
|
|
224
|
+
}
|
|
225
|
+
}
|