material-inspired-component-library 8.0.0 → 8.0.4

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 (84) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/components/alert/index.scss +5 -0
  3. package/components/appbar/index.scss +12 -0
  4. package/components/badge/index.scss +2 -0
  5. package/components/bottomsheet/index.scss +9 -0
  6. package/components/button/index.scss +106 -92
  7. package/components/card/index.scss +30 -0
  8. package/components/checkbox/index.scss +17 -0
  9. package/components/datepicker/index.scss +13 -0
  10. package/components/dialog/index.scss +16 -0
  11. package/components/iconbutton/index.scss +18 -0
  12. package/components/list/index.scss +32 -5
  13. package/components/menu/index.scss +18 -0
  14. package/components/navigationrail/index.scss +17 -0
  15. package/components/progressindicator/README.md +88 -0
  16. package/components/progressindicator/index.scss +225 -0
  17. package/components/progressindicator/index.ts +77 -0
  18. package/components/radio/index.scss +13 -0
  19. package/components/select/index.scss +8 -0
  20. package/components/shape/README.md +103 -0
  21. package/components/shape/_paths.generated.scss +64 -0
  22. package/components/shape/index.scss +66 -0
  23. package/components/shape/master.scss +28 -0
  24. package/components/sidesheet/index.scss +11 -0
  25. package/components/slider/index.scss +13 -0
  26. package/components/snackbar/index.scss +12 -0
  27. package/components/stepper/index.scss +2 -0
  28. package/components/switch/index.scss +9 -0
  29. package/components/textfield/index.scss +9 -0
  30. package/components/timepicker/index.scss +16 -0
  31. package/dist/alert.css +1 -1
  32. package/dist/appbar.css +1 -1
  33. package/dist/badge.css +1 -1
  34. package/dist/bottomsheet.css +1 -1
  35. package/dist/button.css +1 -1
  36. package/dist/card.css +1 -1
  37. package/dist/checkbox.css +1 -1
  38. package/dist/components/progressindicator/index.d.ts +6 -0
  39. package/dist/datepicker.css +1 -1
  40. package/dist/dialog.css +1 -1
  41. package/dist/foundations/form/index.js +1 -0
  42. package/dist/iconbutton.css +1 -1
  43. package/dist/layout.css +1 -1
  44. package/dist/list.css +1 -1
  45. package/dist/menu.css +1 -1
  46. package/dist/micl.css +1 -1
  47. package/dist/micl.js +1 -1
  48. package/dist/navigationrail.css +1 -1
  49. package/dist/progressindicator.css +1 -0
  50. package/dist/progressindicator.js +1 -0
  51. package/dist/radio.css +1 -1
  52. package/dist/select.css +1 -1
  53. package/dist/shape.css +1 -0
  54. package/dist/shape.js +1 -0
  55. package/dist/sidesheet.css +1 -1
  56. package/dist/slider.css +1 -1
  57. package/dist/snackbar.css +1 -1
  58. package/dist/stepper.css +1 -1
  59. package/dist/switch.css +1 -1
  60. package/dist/textfield.css +1 -1
  61. package/dist/timepicker.css +1 -1
  62. package/docs/datepicker.html +21 -21
  63. package/docs/index.html +1 -0
  64. package/docs/micl.css +1 -1
  65. package/docs/micl.js +1 -1
  66. package/docs/progressindicator.html +288 -0
  67. package/docs/shape.css +1 -0
  68. package/docs/shape.js +1 -0
  69. package/docs/shapes.html +86 -21
  70. package/foundations/layout/README.md +1 -1
  71. package/foundations/layout/index.scss +3 -0
  72. package/micl.ts +2 -0
  73. package/package.json +4 -2
  74. package/styles/README.md +86 -8
  75. package/styles/elevation.scss +46 -13
  76. package/styles/motion.scss +51 -47
  77. package/styles/shapes.scss +38 -104
  78. package/styles/statelayer.scss +88 -41
  79. package/styles/typography.scss +120 -322
  80. package/styles.scss +10 -6
  81. package/tools/shapes/check.mjs +42 -0
  82. package/tools/shapes/generate.mjs +834 -0
  83. package/webpack.config.js +16 -1
  84. package/CLAUDE.md +0 -42
@@ -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
+ }
@@ -0,0 +1,77 @@
1
+ //
2
+ // Copyright © 2025 Hermana AS
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+
22
+ export const progressindicatorSelector = 'progress.micl-progress-linear,progress.micl-progress-circular';
23
+
24
+ export default (() =>
25
+ {
26
+ // <progress> emits no input/change events when its value/max change, so a
27
+ // per-instance MutationObserver mirrors the determinate attributes into the
28
+ // CSS custom properties the SCSS reads. Indeterminate progress (no value
29
+ // attribute) is handled entirely in CSS via the :indeterminate pseudo-class.
30
+ const observers = new WeakMap<HTMLProgressElement, MutationObserver>();
31
+
32
+ const setVars = (element: HTMLProgressElement): void =>
33
+ {
34
+ // position is value/max for determinate, or -1 when indeterminate.
35
+ const fraction = element.position;
36
+
37
+ if (fraction < 0) {
38
+ element.style.removeProperty('--md-comp-progress-value');
39
+ element.style.removeProperty('--md-comp-progress-max');
40
+ element.style.removeProperty('--md-comp-progress-fraction');
41
+ element.style.removeProperty('--md-comp-progress-amplitude-scale');
42
+ return;
43
+ }
44
+
45
+ // The Expressive wave amplitude eases to zero over the final 10% so the
46
+ // active indicator settles into a straight line as it completes.
47
+ const scale = Math.max(0, Math.min(1, (1 - fraction) / 0.1));
48
+
49
+ element.style.setProperty('--md-comp-progress-value', String(element.value));
50
+ element.style.setProperty('--md-comp-progress-max', String(element.max || 1));
51
+ element.style.setProperty('--md-comp-progress-fraction', String(fraction));
52
+ element.style.setProperty('--md-comp-progress-amplitude-scale', String(scale));
53
+ };
54
+
55
+ return {
56
+ initialize: (element: HTMLProgressElement): void =>
57
+ {
58
+ if (!element.matches(progressindicatorSelector)) {
59
+ return;
60
+ }
61
+
62
+ setVars(element);
63
+
64
+ const observer = new MutationObserver(() => setVars(element));
65
+ observer.observe(element, { attributes: true, attributeFilter: ['value', 'max'] });
66
+ observers.set(element, observer);
67
+ },
68
+ cleanup: (element: HTMLProgressElement): void =>
69
+ {
70
+ const observer = observers.get(element);
71
+ if (observer) {
72
+ observer.disconnect();
73
+ observers.delete(element);
74
+ }
75
+ }
76
+ };
77
+ })();
@@ -24,6 +24,19 @@
24
24
  @use '../../styles/shapes';
25
25
  @use '../../styles/statelayer';
26
26
 
27
+ @include shapes.corner('full');
28
+
29
+ @include statelayer.token('layer-size');
30
+ @include statelayer.token('hover-state-layer-opacity');
31
+ @include statelayer.token('focus-state-layer-opacity');
32
+ @include statelayer.token('pressed-state-layer-opacity');
33
+ @include statelayer.token('disabled-state-layer-opacity');
34
+ @include statelayer.token('focus-indicator-thickness');
35
+ @include statelayer.token('ripple-opacity-factor');
36
+ @include statelayer.token('ripple-duration');
37
+ @include statelayer.property;
38
+ @include statelayer.keyframes;
39
+
27
40
  :root {
28
41
  --md-sys-radio-border-width: 2px;
29
42
  --md-sys-radio-container-size: 20px;
@@ -26,6 +26,12 @@
26
26
  @use '../../styles/statelayer';
27
27
  @use '../../styles/typography';
28
28
 
29
+ @include elevation.level(2);
30
+
31
+ @include shapes.corner('large');
32
+
33
+ @include statelayer.token('disabled-state-layer-opacity');
34
+
29
35
  .micl-textfield-filled > select {
30
36
  --md-comp-select-line-height: calc(var(--md-sys-textfield-height) - 18px - 3px);
31
37
  }
@@ -107,6 +113,8 @@
107
113
 
108
114
  .micl-list-item__text::after {
109
115
  content: attr(aria-description);
116
+ overflow: hidden;
117
+ text-overflow: ellipsis;
110
118
  }
111
119
  &::checkmark {
112
120
  color: var(--md-sys-color-on-surface-variant);
@@ -0,0 +1,103 @@
1
+ # Shape
2
+ The Shape component renders Material Design 3 [Expressive Shapes](https://m3.material.io/styles/shape/overview-principles) — circle, square, pill, oval, heart, cookie, clover, sunny, and so on — as inline SVGs whose `d` attribute is supplied by CSS. Because the path data is set with `d:` rather than baked into the SVG markup, swapping a shape's class triggers a smooth `transition: d` morph between the old and new outline.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ A shape is an empty `<svg>` with the base class `micl-shape` plus one of the shape modifier classes:
8
+
9
+ ```HTML
10
+ <svg class="micl-shape micl-shape-heart" viewBox="0 0 100 100"><path /></svg>
11
+ ```
12
+
13
+ Optional modifier classes:
14
+
15
+ | Class | Effect |
16
+ | --- | --- |
17
+ | `micl-shape--outlined` | Renders the shape as a stroked outline instead of a filled fill |
18
+ | `micl-shape--shadowed` | Adds a soft drop-shadow filter |
19
+
20
+ ### CSS
21
+
22
+ The Shape component is **opt-in** — it is not included in the master `micl.css` bundle, because most apps don't need 35 decorative shapes. Pick one of three integration paths:
23
+
24
+ **1. Import only the shapes you actually use (recommended for production):**
25
+
26
+ ```SCSS
27
+ @use "material-inspired-component-library/components/shape" as shape;
28
+
29
+ @include shape.base;
30
+ @include shape.use("circle", "heart", "pill");
31
+ ```
32
+
33
+ `shape.base` emits the shared `.micl-shape`, `.micl-shape--outlined`, and `.micl-shape--shadowed` rules. `shape.use(<names>...)` emits one `.micl-shape-<name> > path { d: … }` rule per name. Unknown names raise a Sass error at compile time.
34
+
35
+ **2. Import the whole gallery via Sass:**
36
+
37
+ ```SCSS
38
+ @use "material-inspired-component-library/components/shape" with ($master: true);
39
+ ```
40
+
41
+ Equivalent to calling `shape.base` plus `shape.use(…)` with every shape.
42
+
43
+ **3. Drop in the prebuilt CSS:**
44
+
45
+ ```HTML
46
+ <link rel="stylesheet" href="material-inspired-component-library/dist/shape.css">
47
+ ```
48
+
49
+ The prebuilt bundle contains all 35 shapes plus the base styles.
50
+
51
+ ### JavaScript
52
+ No JavaScript is required. Morphing between shapes is a pure CSS animation: when you replace `micl-shape-heart` with `micl-shape-pill` on the element, the browser interpolates the `d:` value automatically. The morph timing can be customised via two CSS custom properties on the element:
53
+
54
+ | Custom property | Default | Description |
55
+ | --- | --- | --- |
56
+ | `--morph-duration` | `0ms` | Length of the morph transition |
57
+ | `--morph-easing` | `ease-in-out` | Easing curve of the transition |
58
+
59
+ ### Live Demo
60
+ A live example of the [Shape component](https://henkpb.github.io/micl/shapes.html) is available, with a button per shape that morphs the demo SVG.
61
+
62
+ ## Variants
63
+ The available shape names (use these as the `micl-shape-<name>` class and as arguments to `shape.use(...)`):
64
+
65
+ ```
66
+ circle, square, slanted, arch, semicircle, oval, pill,
67
+ triangle, arrow, fan, diamond, clamshell, pentagon, gem,
68
+ very-sunny, sunny, cookie-4, cookie-6, cookie-7, cookie-9,
69
+ cookie-12, clover-4, clover-8, burst, soft-burst, boom,
70
+ soft-boom, flower, puffy, puffy-diamond, ghost-ish,
71
+ pixel-circle, pixel-triangle, bun, heart
72
+ ```
73
+
74
+ ## Customizations
75
+ The base styles size the SVG to 100 × 100 px, fill it with `green`, and let it overflow its viewBox horizontally (so wide shapes like *pill* and *fan* are not clipped). Override these on the element or on a parent:
76
+
77
+ ```HTML
78
+ <svg class="micl-shape micl-shape-heart"
79
+ viewBox="0 0 100 100"
80
+ style="inline-size:64px;block-size:64px;fill:var(--md-sys-color-primary)">
81
+ <path />
82
+ </svg>
83
+ ```
84
+
85
+ For `micl-shape--outlined`, the stroke colour and width are controlled by the standard SVG `stroke` and `stroke-width` properties:
86
+
87
+ ```HTML
88
+ <svg class="micl-shape micl-shape--outlined micl-shape-pill"
89
+ viewBox="0 0 100 100"
90
+ style="stroke:var(--md-sys-color-outline);stroke-width:3"><path /></svg>
91
+ ```
92
+
93
+ ## Contributing — regenerating the path data
94
+ The 35 SVG path strings live in [`_paths.generated.scss`](./_paths.generated.scss) and are produced by [`tools/shapes/generate.mjs`](../../tools/shapes/generate.mjs). The Sass partial does no math at compile time — it only looks shapes up by name in the generated map. **Do not edit `_paths.generated.scss` by hand.**
95
+
96
+ When you change a shape's vertex coordinates, repeat count, mirror flag, vertex count, or any of the underlying math:
97
+
98
+ ```bash
99
+ npm run gen:shapes # regenerate components/shape/_paths.generated.scss
100
+ npm run check:shapes # verify the file is up to date (CI runs this too)
101
+ ```
102
+
103
+ Commit the regenerated `_paths.generated.scss` together with the change to the generator. CI fails if a PR modifies the generator without also updating the committed paths.