material-inspired-component-library 4.0.2 → 5.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/README.md CHANGED
@@ -106,9 +106,13 @@ The library currently consists of the following components:
106
106
  - [x] [Stepper](components/stepper/README.md)
107
107
  - [x] [Switch](components/switch/README.md)
108
108
  - [x] [Text field](components/textfield/README.md)
109
+ - [x] [Time picker](components/timepicker/README.md)
109
110
 
110
111
  ## Change Log ↪️
111
112
 
113
+ ### 5.0.0 (02.12.2025)
114
+ - **Time picker**: New component.
115
+
112
116
  ### 4.0.0 (27.10.2025)
113
117
  - **BREAKING**: Moved layout.scss til sub-folder.
114
118
  - **Alert**: New component.
@@ -62,6 +62,7 @@ button.micl-button-outlined-xl {
62
62
 
63
63
  position: relative;
64
64
  display: inline-flex;
65
+ flex-shrink: 0;
65
66
  align-items: center;
66
67
  justify-content: center;
67
68
  padding: 0;
@@ -1,5 +1,5 @@
1
1
  # Dialog
2
- This component implements the [Material Design 3 Expressive Dialog](https://m3.material.io/components/dialogs/overview) design.
2
+ This component implements the [Material Design 3 Expressive Dialog](https://m3.material.io/components/dialogs/overview) design. A dialog is a small window that prompts the user to make a decision or enter additional information.
3
3
 
4
4
  ## Basic Usage
5
5
 
@@ -7,7 +7,7 @@ This component implements the [Material Design 3 Expressive Dialog](https://m3.m
7
7
  To create a basic dialog, use the `<dialog>` element with the `micl-dialog` class. You can open and close the dialog using JavaScript, or you can use a control element, such as a button, to open and close the dialog.
8
8
 
9
9
  ```HTML
10
- <dialog id="mydialog" class="micl-dialog" closedby="any" popover aria-labelledby="mytitle" aria-describedby="mydesc">
10
+ <dialog id="mydialog" class="micl-dialog" popover closedby="any" role="alertdialog" aria-labelledby="mytitle" aria-describedby="mydesc">
11
11
  <div class="micl-dialog__headline">
12
12
  <h2 id="mytitle">Basic dialog</h2>
13
13
  <span id="mydesc" class="micl-dialog__supporting-text">An example of a basic dialog</span>
@@ -45,7 +45,7 @@ When dialogs with the `popover` attribute are opened, they animate from the cont
45
45
  Removing the `popover` attribute creates a more intrusive **modal** dialog. This type of dialog requires the user to interact with its buttons or press the <kbd>Esc</kbd> key to close it.
46
46
 
47
47
  ```HTML
48
- <dialog id="mydialog" class="micl-dialog" closedby="closerequest" aria-labelledby="mytitle" aria-describedby="mydesc">
48
+ <dialog id="mydialog" class="micl-dialog" closedby="closerequest" role="alertdialog" aria-labelledby="mytitle" aria-describedby="mydesc">
49
49
  <div class="micl-dialog__headline">
50
50
  <span class="micl-dialog__icon material-symbols-outlined" aria-hidden="true">info</span>
51
51
  <h2 id="mytitle">Modal dialog</h2>
@@ -67,14 +67,13 @@ Removing the `popover` attribute creates a more intrusive **modal** dialog. This
67
67
  By default, modal dialogs open in the center of the screen. You can anchor a modal dialog to a control element, causing it to open relative to that element:
68
68
 
69
69
  ```HTML
70
- <dialog id="mydialog" class="micl-dialog" closedby="closerequest" style="position-anchor:--myanchor">
70
+ <dialog id="mydialog" class="micl-dialog" style="position-anchor:--myanchor">
71
71
  </dialog>
72
72
 
73
73
  <button type="button" popovertarget="mydialog" style="anchor-name:--myanchor">Open Modal Dialog</button>
74
74
  ```
75
75
 
76
76
  ### Dialog Structure Sections
77
-
78
77
  A dialog typically consists of three main sections to organize its content:
79
78
 
80
79
  - `micl-dialog__headline`: The header of the dialog. It usually contains:
@@ -92,10 +91,10 @@ A dialog typically consists of three main sections to organize its content:
92
91
  - `micl-dialog__actions`: A container for action buttons that allow the user to perform actions related to the dialog or close it. Actions are typically placed in a `<form method="dialog">` for native HTML dialog closing.
93
92
 
94
93
  ### Full-screen dialog
95
- A full-screen dialog covers the entire viewport, primarily on smaller screens. On screens wider than 560 pixels, a full-screen dialog behaves like a basic dialog. Use the `micl-dialog-fullscreen` class for this variant:
94
+ A full-screen dialog covers the entire viewport, primarily on smaller screens. On screens wider than 560 pixels, a full-screen dialog behaves like a basic dialog. Use the `micl-dialog--fullscreen` modifier class for this variant:
96
95
 
97
96
  ```HTML
98
- <dialog id="mydialog" class="micl-dialog-fullscreen" closedby="none" popover aria-labelledby="mytitle" aria-describedby="mydesc">
97
+ <dialog id="mydialog" class="micl-dialog micl-dialog--fullscreen" closedby="none" popover aria-labelledby="mytitle" aria-describedby="mydesc">
99
98
  <form method="dialog" class="micl-dialog__headline">
100
99
  <button type="button" class="micl-iconbutton-s material-symbols-outlined" popovertarget="mydialog" aria-label="Close">close</button>
101
100
  <span class="micl-dialog__icon material-symbols-outlined" aria-hidden="true">person</span>
@@ -123,7 +122,10 @@ You can customize the appearance of the Dialog component by overriding its globa
123
122
 
124
123
  | Variable name | Default Value | Description |
125
124
  | ------------- | ----- | ----------- |
125
+ | --md-sys-dialog-min-width | 280px | The minimum width of a dialog |
126
+ | --md-sys-dialog-max-width | 560px | The maximum width of a dialog |
126
127
  | --md-sys-dialog-padding | 24px | The inner padding between the dialog's edge and its content |
128
+ | --md-sys-dialog-headline-space | 16px | The vertical spacing between the elements in the header |
127
129
 
128
130
  **Example: Changing the dialog padding**
129
131
 
@@ -27,7 +27,10 @@
27
27
  @use '../../styles/typography';
28
28
 
29
29
  :root {
30
+ --md-sys-dialog-min-width: 280px;
31
+ --md-sys-dialog-max-width: 560px;
30
32
  --md-sys-dialog-padding: 24px;
33
+ --md-sys-dialog-headline-space: 16px;
31
34
  --md-sys-dialog-dir-factor: 1;
32
35
  }
33
36
 
@@ -35,8 +38,7 @@
35
38
  --md-sys-dialog-dir-factor: -1;
36
39
  }
37
40
 
38
- dialog.micl-dialog,
39
- dialog.micl-dialog-fullscreen {
41
+ dialog.micl-dialog {
40
42
  --statelayer-color: var(--md-sys-color-primary);
41
43
  --md-sys-dialog-motion-spatial: #{motion.$md-sys-motion-expressive-fast-spatial};
42
44
  --md-sys-dialog-motion-duration: #{motion.$md-sys-motion-expressive-fast-spatial-duration};
@@ -45,12 +47,12 @@ dialog.micl-dialog-fullscreen {
45
47
  box-sizing: border-box;
46
48
  display: none;
47
49
  flex-direction: column;
48
- min-width: 280px;
49
- max-width: 560px;
50
- max-height: 100vh;
50
+ min-inline-size: var(--md-sys-dialog-min-width, 280px);
51
+ max-inline-size: var(--md-sys-dialog-max-width, 560px);
52
+ max-block-size: 100vh;
51
53
  inset-block-start: anchor(start);
52
54
  inset-inline-start: anchor(start);
53
- transform: translate(calc(var(--md-sys-dialog-dir-factor) * -50%), -50%) scale(50%);
55
+ transform: translate(calc(var(--md-sys-dialog-dir-factor, 1) * -50%), -50%) scale(50%);
54
56
  padding: 0;
55
57
  margin: 0;
56
58
  border: none;
@@ -65,8 +67,8 @@ dialog.micl-dialog-fullscreen {
65
67
  transition:
66
68
  inset-block-start var(--md-sys-dialog-motion-duration-reverse) linear,
67
69
  inset-inline-start var(--md-sys-dialog-motion-duration-reverse) linear,
68
- width var(--md-sys-dialog-motion-duration-reverse) linear,
69
- height var(--md-sys-dialog-motion-duration-reverse) linear,
70
+ inline-size var(--md-sys-dialog-motion-duration-reverse) linear,
71
+ block-size var(--md-sys-dialog-motion-duration-reverse) linear,
70
72
  transform var(--md-sys-dialog-motion-duration-reverse) linear,
71
73
  opacity var(--md-sys-dialog-motion-duration-reverse) motion.$md-sys-motion-easing-emphasized-decelerate,
72
74
  overlay var(--md-sys-dialog-motion-duration-reverse) linear allow-discrete,
@@ -74,12 +76,12 @@ dialog.micl-dialog-fullscreen {
74
76
  --statelayer-opacity var(--md-sys-dialog-motion-duration) linear;
75
77
 
76
78
  @starting-style {
77
- height: fit-content;
78
- width: fit-content;
79
+ block-size: fit-content;
80
+ inline-size: fit-content;
79
81
  inset-block-start: anchor(start);
80
82
  inset-inline-start: anchor(start);
81
83
  opacity: 0;
82
- transform: translate(calc(var(--md-sys-dialog-dir-factor) * -50%), -50%) scale(50%);
84
+ transform: translate(calc(var(--md-sys-dialog-dir-factor, 1) * -50%), -50%) scale(50%);
83
85
  }
84
86
 
85
87
  &:popover-open,
@@ -88,12 +90,12 @@ dialog.micl-dialog-fullscreen {
88
90
  inset-block-start: 50%;
89
91
  inset-inline-start: 50%;
90
92
  opacity: 1;
91
- transform: translate(calc(var(--md-sys-dialog-dir-factor) * -50%), -50%) scale(100%);
93
+ transform: translate(calc(var(--md-sys-dialog-dir-factor, 1) * -50%), -50%) scale(100%);
92
94
  transition:
93
95
  inset-block-start var(--md-sys-dialog-motion-duration) linear,
94
96
  inset-inline-start var(--md-sys-dialog-motion-duration) linear,
95
- width var(--md-sys-dialog-motion-duration) linear,
96
- height var(--md-sys-dialog-motion-duration) linear,
97
+ inline-size var(--md-sys-dialog-motion-duration) linear,
98
+ block-size var(--md-sys-dialog-motion-duration) linear,
97
99
  transform var(--md-sys-dialog-motion-duration) linear,
98
100
  opacity var(--md-sys-dialog-motion-duration) motion.$md-sys-motion-easing-emphasized-accelerate,
99
101
  overlay var(--md-sys-dialog-motion-duration) linear allow-discrete,
@@ -103,7 +105,7 @@ dialog.micl-dialog-fullscreen {
103
105
  inset-block-start: anchor(start);
104
106
  inset-inline-start: anchor(start);
105
107
  opacity: 0;
106
- transform: translate(calc(var(--md-sys-dialog-dir-factor) * -50%), -50%) scale(50%);
108
+ transform: translate(calc(var(--md-sys-dialog-dir-factor, 1) * -50%), -50%) scale(50%);
107
109
  }
108
110
  }
109
111
  &:not([popover]) {
@@ -148,43 +150,41 @@ dialog.micl-dialog-fullscreen {
148
150
  flex-shrink: 0;
149
151
  align-items: flex-start;
150
152
  gap: 16px;
151
- padding: var(--md-sys-dialog-padding);
153
+ padding-block: var(--md-sys-dialog-padding, 24px) var(--md-sys-dialog-headline-space, 16px);
154
+ padding-inline: var(--md-sys-dialog-padding, 24px);
152
155
  background-color: transparent;
153
156
 
157
+ &:has(> .micl-dialog__icon) {
158
+ align-items: center;
159
+ }
160
+ .micl-dialog__icon {
161
+ inline-size: var(--md-sys-layout-icon-size, 24px);
162
+ block-size: var(--md-sys-layout-icon-size, 24px);
163
+ font-size: var(--md-sys-layout-icon-size, 24px);
164
+ color: var(--md-sys-color-secondary);
165
+ }
154
166
  h1, h2, h3, h4, h5, h6, .micl-heading {
155
167
  @include typography.headline-small;
156
168
 
157
169
  flex: 1 1 100%;
158
170
  margin: 0;
159
- overflow: hidden;
160
- text-overflow: ellipsis;
161
- white-space: nowrap;
162
171
  color: var(--md-sys-color-on-surface);
163
172
  }
164
- .micl-dialog__icon {
165
- width: var(--md-sys-layout-icon-size, 24px);
166
- height: var(--md-sys-layout-icon-size, 24px);
167
- font-size: var(--md-sys-layout-icon-size, 24px);
168
- color: var(--md-sys-color-secondary);
169
- }
170
173
  button {
171
174
  display: none;
172
175
  }
173
- &+ .micl-dialog__actions {
174
- padding-block-start: 0;
175
- }
176
- }
177
- .micl-dialog__headline:has(> .micl-dialog__icon) {
178
- align-items: center;
179
- }
180
- .micl-dialog__subhead {
181
- @include typography.title-medium;
176
+ .micl-dialog__subhead {
177
+ @include typography.title-medium;
182
178
 
183
- padding-inline: var(--md-sys-dialog-padding);
184
- overflow: hidden;
185
- text-overflow: ellipsis;
186
- white-space: nowrap;
187
- color: var(--md-sys-color-on-surface)
179
+ padding-inline: var(--md-sys-dialog-padding, 24px);
180
+ overflow: hidden;
181
+ text-overflow: ellipsis;
182
+ white-space: nowrap;
183
+ color: var(--md-sys-color-on-surface)
184
+ }
185
+ &:has(+ .micl-dialog__actions) {
186
+ padding-block-end: 0;
187
+ }
188
188
  }
189
189
  .micl-dialog__supporting-text {
190
190
  @include typography.body-medium;
@@ -193,7 +193,7 @@ dialog.micl-dialog-fullscreen {
193
193
  }
194
194
  .micl-dialog__content {
195
195
  flex-shrink: 1;
196
- padding-inline: var(--md-sys-dialog-padding);
196
+ padding-inline: var(--md-sys-dialog-padding, 24px);
197
197
  background-color: inherit;
198
198
  overflow-y: auto;
199
199
  }
@@ -202,18 +202,21 @@ dialog.micl-dialog-fullscreen {
202
202
  flex-shrink: 0;
203
203
  justify-content: flex-end;
204
204
  column-gap: 8px;
205
- padding: var(--md-sys-dialog-padding);
205
+ padding: var(--md-sys-dialog-padding, 24px);
206
206
  opacity: 1;
207
- transition: opacity var(--md-sys-dialog-motion-duration) linear;
207
+ transition:
208
+ opacity var(--md-sys-dialog-motion-duration) linear allow-discrete,
209
+ overlay var(--md-sys-dialog-motion-duration) linear allow-discrete,
210
+ display var(--md-sys-dialog-motion-duration) linear allow-discrete;
208
211
  }
209
212
  }
210
213
 
211
- dialog.micl-dialog-fullscreen {
214
+ dialog.micl-dialog.micl-dialog--fullscreen {
212
215
  @media (max-width: 560px) {
213
- width: 100vw;
214
- height: 100vh;
215
- max-width: 100vw;
216
- border-radius: var(--md-sys-shape-corner-none);
216
+ inline-size: 100vw;
217
+ block-size: 100vh;
218
+ max-inline-size: 100vw;
219
+ border-radius: var(--md-sys-shape-corner-none, 0px);
217
220
  background-color: var(--md-sys-color-surface);
218
221
  box-shadow: var(--md-sys-elevation-level0);
219
222
  timeline-scope: --headlineTimeline;
@@ -222,11 +225,12 @@ dialog.micl-dialog-fullscreen {
222
225
  // --statelayer-opacity: var(--md-sys-state-hover-state-layer-opacity);
223
226
  // }
224
227
  .micl-dialog__headline {
225
- height: 56px;
228
+ block-size: 56px;
226
229
  flex-direction: row;
227
230
  align-items: center;
228
- gap: 4px;
229
- padding: 4px;
231
+ gap: 8px;
232
+ padding-block: 4px;
233
+ padding-inline: 8px 16px;
230
234
  animation-name: headlineScroll;
231
235
  animation-duration: 1ms;
232
236
  animation-timeline: --headlineTimeline;
@@ -246,10 +250,10 @@ dialog.micl-dialog-fullscreen {
246
250
  display:none;
247
251
  }
248
252
  button {
249
- display: inline-block;
253
+ display: block;
250
254
 
251
255
  &.micl-button {
252
- margin-right: 16px;
256
+ margin-inline-end: 16px;
253
257
  }
254
258
  }
255
259
  }
@@ -258,11 +262,18 @@ dialog.micl-dialog-fullscreen {
258
262
  scroll-timeline: --headlineTimeline vertical;
259
263
  }
260
264
  .micl-dialog__actions {
265
+ display: none;
261
266
  opacity: 0;
262
267
  }
263
268
  }
264
269
  }
265
270
 
271
+ @media (max-width: 560px) {
272
+ body:has(dialog.micl-dialog.micl-dialog--fullscreen:popover-open) {
273
+ overflow-y: hidden;
274
+ }
275
+ }
276
+
266
277
  @keyframes headlineScroll {
267
278
  from {
268
279
  background-color: transparent;
@@ -0,0 +1,135 @@
1
+ # Time picker
2
+ This component implements the [Material Design 3 Expressive Time picker](https://m3.material.io/components/time-pickers/overview) design. It allows users to select a specific time of day using either a text input or an analog dial interface.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ The Time picker component is an extension of the [**Dialog** component](../dialog/README.md). To create a basic time picker, use a `<dialog>` element with both `micl-dialog` and `micl-timepicker` classes.
8
+
9
+ ```HTML
10
+ <dialog id="mytimepicker" class="micl-dialog micl-timepicker" closedby="closerequest" aria-labelledby="mytitle">
11
+ <form method="dialog">
12
+ <div class="micl-dialog__headline">
13
+ <h2 id="mytitle">Enter time</h2>
14
+ </div>
15
+
16
+ <div class="micl-dialog__content">
17
+ <input type="number" name="hour" value="00" aria-labelledby="myhour">
18
+ <span class="micl-timepicker__separator">:</span>
19
+ <input type="number" name="minute" value="00" aria-labelledby="myminute">
20
+ <div class="micl-timepicker__period"></div>
21
+ <span id="myhour" class="micl-timepicker__supporting-text-hour">Hour</span>
22
+ <span id="myminute" class="micl-timepicker__supporting-text-minute">Minute</span>
23
+ <div class="micl-timepicker__dial"></div>
24
+ </div>
25
+
26
+ <div class="micl-dialog__actions">
27
+ <button
28
+ type="button"
29
+ class="micl-timepicker__inputmode micl-iconbutton-standard-s material-symbols-outlined"
30
+ data-alticon="schedule"
31
+ aria-label="Switch input mode"
32
+ >keyboard</button>
33
+ <div>
34
+ <button class="micl-button-text-s" value="">Cancel</button>
35
+ <button class="micl-button-text-s" value="OK">OK</button>
36
+ </div>
37
+ </div>
38
+ </form>
39
+ </dialog>
40
+ ```
41
+
42
+ ### CSS
43
+ Import both the time picker and the dialog styles into your project:
44
+
45
+ ```CSS
46
+ @use "material-inspired-component-library/dist/dialog";
47
+ @use "material-inspired-component-library/dist/timepicker";
48
+ ```
49
+
50
+ Or import all MICL styles:
51
+ ```CSS
52
+ @use "material-inspired-component-library/styles";
53
+ ```
54
+
55
+ ### JavaScript
56
+ This component requires JavaScript to function:
57
+
58
+ ```JavaScript
59
+ import micl from "material-inspired-component-library/dist/micl";
60
+ ```
61
+
62
+ This will initialize any Time picker component, including those that will be added to the DOM later on.
63
+
64
+ ### Live Demo
65
+ A live example of the [Time picker component](https://henkpb.github.io/micl/timepicker.html) is available to interact with.
66
+
67
+ ## Variants
68
+ Because the Time picker component relies on the Dialog component, it utilizes the same utility classes for content structure. Refer to the [Dialog component documentation](../dialog/README.md) for structural details.
69
+
70
+ ### Time Picker Structure
71
+ For the picker to function correctly, the `micl-dialog__content` area must contain:
72
+
73
+ - Hour input: `<input type="number" name="hour">`
74
+ - Minute input: `<input type="number" name="minute">`
75
+ - Separator: A text element with class `micl-timepicker__separator` (e.g., a colon).
76
+ - AM/PM Container: An empty `<div>` with class `micl-timepicker__period`. The component logic will populate this selector if the user's locale uses a 12-hour format.
77
+ - Dial Container: An optional empty `<div>` with class `micl-timepicker__dial` for the analog clock interface.
78
+ - Optional elements for "Hour" and "Minute" supporting text.
79
+
80
+ By default, the layout is **vertical**. To switch to a **horizontal** layout (side-by-side inputs and dial), add the modifier class `micl-timepicker--horizontal` to the `<dialog>`.
81
+
82
+ #### Input Mode Switching
83
+ To allow users to toggle between the text inputs and the analog dial, add a button to the `micl-dialog__actions` container:
84
+
85
+ - Class: `micl-timepicker__inputmode`
86
+ - Data Attribute: `data-alticon="schedule"` (defines the icon to show when toggled).
87
+
88
+ ### Integration
89
+ You can trigger the Time picker component from standard input fields or buttons.
90
+
91
+ #### Connecting to an Input Field
92
+ To replace the browser's native time picker, add the `data-timepicker` attribute to an `<input>` element. The value of this attribute must match the `id` of your Time picker dialog.
93
+
94
+ ```HTML
95
+ <input type="time" data-timepicker="mytimepicker" value="09:41">
96
+ ```
97
+
98
+ - **Behavior**: When the input is clicked, the picker opens with the input's current value.
99
+ - **Reusability**: Multiple input fields can target the same Time picker component ID.
100
+
101
+ You may use the same time picker component for different time-input fields. When the user engages with the input field, the time picker is opened showing the time specified in the `value`-attribute.
102
+
103
+ #### Connecting to a Button
104
+ You can use a button to trigger the picker using the standard `popovertarget` attribute.
105
+
106
+ ```HTML
107
+ <button type="button" class="micl-button-text-m" popovertarget="mytimepicker">09:41</button>
108
+ ```
109
+
110
+ - **Behavior**: The Time picker treats the button's text content as the time value to read from and write to.
111
+
112
+ ## Customizations
113
+ You can customize the appearance of the Time picker component by overriding its global CSS variables. These variables are declared on the `:root` pseudo-class and can be changed on any appropriate parent element to affect its child time pickers.
114
+
115
+ | Variable name | Default Value | Description |
116
+ | ------------- | ------------- | ----------- |
117
+ | --md-sys-timepicker-input-height | 72px | Height of the hour a minute input boxes |
118
+ | --md-sys-timepicker-input-width | 96px | Width of the input boxes in 12-hour mode |
119
+ | --md-sys-timepicker-input-width-24h | 114px | Width of the input boxes in 24-hour mode |
120
+ | --md-sys-timepicker-separator-width | 24px | Width of the space containing the colon separator |
121
+ | --md-sys-timepicker-period-height | 72px | Total height of the AM/PM selector toggle |
122
+ | --md-sys-timepicker-period-width | 52px | Width of the AM/PM selector toggle |
123
+ | --md-sys-timepicker-dial-size | 256px | Diameter of the analog clock face |
124
+ | --md-sys-timepicker-dial-center-size | 8px | Diameter of the center dot in the analog dial |
125
+ | --md-sys-timepicker-dial-track-width | 2px | Thickness of the circular track line on the dial |
126
+
127
+ **Example: Changing the width of the dial track**
128
+
129
+ ```HTML
130
+ <div style="--md-sys-timepicker-dial-track-width:3px">
131
+ <dialog class="micl-dialog micl-timepicker" closedby="closerequest">
132
+ ...
133
+ </dialog>
134
+ </div>
135
+ ```