material-inspired-component-library 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/.editorconfig +12 -0
  2. package/.gitattributes +9 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. package/LICENSE +21 -0
  6. package/README.md +99 -0
  7. package/components/README.md +12 -0
  8. package/components/accordion/README.md +94 -0
  9. package/components/bottomsheet/README.md +77 -0
  10. package/components/bottomsheet/bottomsheet.scss +134 -0
  11. package/components/bottomsheet/index.ts +152 -0
  12. package/components/button/README.md +92 -0
  13. package/components/button/button.scss +515 -0
  14. package/components/button/index.ts +73 -0
  15. package/components/card/README.md +125 -0
  16. package/components/card/card.scss +261 -0
  17. package/components/checkbox/README.md +62 -0
  18. package/components/checkbox/checkbox.scss +275 -0
  19. package/components/checkbox/index.ts +48 -0
  20. package/components/dialog/README.md +133 -0
  21. package/components/dialog/dialog.scss +262 -0
  22. package/components/divider/README.md +52 -0
  23. package/components/divider/divider.scss +74 -0
  24. package/components/iconbutton/README.md +86 -0
  25. package/components/iconbutton/iconbutton.scss +461 -0
  26. package/components/iconbutton/index.ts +73 -0
  27. package/components/list/README.md +176 -0
  28. package/components/list/index.ts +108 -0
  29. package/components/list/list.scss +295 -0
  30. package/components/menu/README.md +96 -0
  31. package/components/menu/index.ts +77 -0
  32. package/components/menu/menu.scss +124 -0
  33. package/components/radio/README.md +53 -0
  34. package/components/radio/radio.scss +138 -0
  35. package/components/select/README.md +84 -0
  36. package/components/select/select.scss +122 -0
  37. package/components/sidesheet/README.md +99 -0
  38. package/components/sidesheet/sidesheet.scss +162 -0
  39. package/components/slider/README.md +69 -0
  40. package/components/slider/index.ts +114 -0
  41. package/components/slider/slider.scss +258 -0
  42. package/components/switch/README.md +49 -0
  43. package/components/switch/switch.scss +176 -0
  44. package/components/textfield/README.md +75 -0
  45. package/components/textfield/index.ts +81 -0
  46. package/components/textfield/textfield.scss +387 -0
  47. package/components.ts +169 -0
  48. package/dist/bottomsheet.css +1 -0
  49. package/dist/bottomsheet.js +0 -0
  50. package/dist/button.css +1 -0
  51. package/dist/button.js +0 -0
  52. package/dist/card.css +1 -0
  53. package/dist/card.js +0 -0
  54. package/dist/checkbox.css +1 -0
  55. package/dist/checkbox.js +0 -0
  56. package/dist/dialog.css +1 -0
  57. package/dist/dialog.js +0 -0
  58. package/dist/divider.css +1 -0
  59. package/dist/divider.js +0 -0
  60. package/dist/iconbutton.css +1 -0
  61. package/dist/iconbutton.js +0 -0
  62. package/dist/list.css +1 -0
  63. package/dist/list.js +0 -0
  64. package/dist/menu.css +1 -0
  65. package/dist/menu.js +0 -0
  66. package/dist/micl.css +1 -0
  67. package/dist/micl.js +1 -0
  68. package/dist/radio.css +1 -0
  69. package/dist/radio.js +0 -0
  70. package/dist/select.css +1 -0
  71. package/dist/select.js +0 -0
  72. package/dist/sidesheet.css +1 -0
  73. package/dist/sidesheet.js +0 -0
  74. package/dist/slider.css +1 -0
  75. package/dist/slider.js +0 -0
  76. package/dist/switch.css +1 -0
  77. package/dist/switch.js +0 -0
  78. package/dist/textfield.css +1 -0
  79. package/dist/textfield.js +0 -0
  80. package/docs/accordion.html +285 -0
  81. package/docs/bottomsheet.html +162 -0
  82. package/docs/button.html +206 -0
  83. package/docs/card-awards.webp +0 -0
  84. package/docs/card-cabinet.webp +0 -0
  85. package/docs/card-city.webp +0 -0
  86. package/docs/card-fingerprint.webp +0 -0
  87. package/docs/card-holiday.webp +0 -0
  88. package/docs/card-names.webp +0 -0
  89. package/docs/card.html +91 -0
  90. package/docs/checkbox.html +99 -0
  91. package/docs/dialog.html +153 -0
  92. package/docs/divider.html +103 -0
  93. package/docs/docs.css +34 -0
  94. package/docs/docs.js +69 -0
  95. package/docs/iconbutton.html +197 -0
  96. package/docs/index.html +319 -0
  97. package/docs/list.html +224 -0
  98. package/docs/menu.html +143 -0
  99. package/docs/micl.css +1 -0
  100. package/docs/micl.js +1 -0
  101. package/docs/radio.html +101 -0
  102. package/docs/select.html +205 -0
  103. package/docs/sidesheet.html +115 -0
  104. package/docs/slider.html +72 -0
  105. package/docs/switch.html +151 -0
  106. package/docs/textfield.html +151 -0
  107. package/docs/themes/airblue/dark-hc.css +51 -0
  108. package/docs/themes/airblue/dark-mc.css +51 -0
  109. package/docs/themes/airblue/dark.css +51 -0
  110. package/docs/themes/airblue/light-hc.css +51 -0
  111. package/docs/themes/airblue/light-mc.css +51 -0
  112. package/docs/themes/airblue/light.css +51 -0
  113. package/docs/themes/airblue/theme.css +306 -0
  114. package/docs/themes/barnred/dark-hc.css +51 -0
  115. package/docs/themes/barnred/dark-mc.css +51 -0
  116. package/docs/themes/barnred/dark.css +51 -0
  117. package/docs/themes/barnred/light-hc.css +51 -0
  118. package/docs/themes/barnred/light-mc.css +51 -0
  119. package/docs/themes/barnred/light.css +51 -0
  120. package/docs/themes/barnred/theme.css +306 -0
  121. package/docs/themes/citrine/dark-hc.css +51 -0
  122. package/docs/themes/citrine/dark-mc.css +51 -0
  123. package/docs/themes/citrine/dark.css +51 -0
  124. package/docs/themes/citrine/light-hc.css +51 -0
  125. package/docs/themes/citrine/light-mc.css +51 -0
  126. package/docs/themes/citrine/light.css +51 -0
  127. package/docs/themes/citrine/theme.css +306 -0
  128. package/docs/themes/olivegreen/dark-hc.css +51 -0
  129. package/docs/themes/olivegreen/dark-mc.css +51 -0
  130. package/docs/themes/olivegreen/dark.css +51 -0
  131. package/docs/themes/olivegreen/light-hc.css +51 -0
  132. package/docs/themes/olivegreen/light-mc.css +51 -0
  133. package/docs/themes/olivegreen/light.css +51 -0
  134. package/docs/themes/olivegreen/theme.css +306 -0
  135. package/package.json +62 -0
  136. package/styles/README.md +99 -0
  137. package/styles/elevation.scss +36 -0
  138. package/styles/motion.scss +124 -0
  139. package/styles/ripple.scss +50 -0
  140. package/styles/shapes.scss +46 -0
  141. package/styles/statelayer.scss +42 -0
  142. package/styles/typography.scss +568 -0
  143. package/styles.scss +43 -0
  144. package/themes/README.md +57 -0
  145. package/themes/airblue/dark-hc.css +51 -0
  146. package/themes/airblue/dark-mc.css +51 -0
  147. package/themes/airblue/dark.css +51 -0
  148. package/themes/airblue/light-hc.css +51 -0
  149. package/themes/airblue/light-mc.css +51 -0
  150. package/themes/airblue/light.css +51 -0
  151. package/themes/airblue/theme.css +306 -0
  152. package/themes/barnred/dark-hc.css +51 -0
  153. package/themes/barnred/dark-mc.css +51 -0
  154. package/themes/barnred/dark.css +51 -0
  155. package/themes/barnred/light-hc.css +51 -0
  156. package/themes/barnred/light-mc.css +51 -0
  157. package/themes/barnred/light.css +51 -0
  158. package/themes/barnred/theme.css +306 -0
  159. package/themes/citrine/dark-hc.css +51 -0
  160. package/themes/citrine/dark-mc.css +51 -0
  161. package/themes/citrine/dark.css +51 -0
  162. package/themes/citrine/light-hc.css +51 -0
  163. package/themes/citrine/light-mc.css +51 -0
  164. package/themes/citrine/light.css +51 -0
  165. package/themes/citrine/theme.css +306 -0
  166. package/themes/olivegreen/dark-hc.css +51 -0
  167. package/themes/olivegreen/dark-mc.css +51 -0
  168. package/themes/olivegreen/dark.css +51 -0
  169. package/themes/olivegreen/light-hc.css +51 -0
  170. package/themes/olivegreen/light-mc.css +51 -0
  171. package/themes/olivegreen/light.css +51 -0
  172. package/themes/olivegreen/theme.css +306 -0
  173. package/tsconfig.json +110 -0
  174. package/webpack.config.js +49 -0
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # For GitHub
2
+
3
+ root = true
4
+
5
+ [*.{sass,css,ts,js}]
6
+ charset = utf-8
7
+ indent_style = space
8
+ indent_size = 4
9
+ tab_width = 4
10
+ end_of_line = lf
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
package/.gitattributes ADDED
@@ -0,0 +1,9 @@
1
+ * text=auto
2
+
3
+ *.html text eol=lf
4
+ *.scss text eol=lf
5
+ *.css text eol=lf
6
+ *.ts text eol=lf
7
+ *.js text eol=lf
8
+ *.md text eol=lf
9
+ *.json text eol=lf
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - Browser [e.g. chrome, safari]
28
+ - Version [e.g. 22]
29
+
30
+ **Smartphone (please complete the following information):**
31
+ - Browser [e.g. chrome, safari]
32
+ - Version [e.g. 22]
33
+
34
+ **Additional context**
35
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hermana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Material-Inspired Component Library (MICL)
2
+ The Material-Inspired Component Library (MICL) offers a free and open-source collection of beautifully crafted components leveraging native HTML markup, designed to align with the [Material Design 3](https://m3.material.io/) guidelines. MICL prioritizes minimal HTML markup and often requires no JavaScript, making it a lightweight and efficient choice for your projects.
3
+
4
+ ## Why Choose MICL?
5
+ - **Embrace Material Design 3:** Seamlessly integrate the aesthetic and user experience principles of Material Design into your web pages and applications.
6
+ - **Lightweight & Efficient:** Benefit from components built with straightforward HTML and minimal to no JavaScript, ensuring fast loading times and a streamlined development process.
7
+ - **Effortless Dark Mode:** Enjoy out-of-the-box support for both light and dark modes, automatically adapting to your defined color scheme.
8
+
9
+ ## Why Might MICL Not Be for You?
10
+ - **Heavy JavaScript Dependencies:** If your project heavily relies on a JavaScript framework for component interaction, MICL's minimalist approach might not be the best fit.
11
+ - **Bootstrap Preference:** Users deeply integrated with or preferring the Bootstrap CSS framework may find MICL's distinct design philosophy less suitable.
12
+ - **Legacy Browser Support:** MICL is designed for modern browsers, and comprehensive support for older browser versions is not a primary focus.
13
+
14
+ ## Demo & Documentation 📖
15
+ Explore the components in a live environment and see how they work.
16
+
17
+ - **Live Demo:** [The MICL Showcase](https://henkpb.github.io/micl/)
18
+ - **Component Documentation:** [View all component documentation](components/README.md)
19
+
20
+ ## Quick start 🚀
21
+
22
+ ### 1. Install via NPM
23
+ ```shell
24
+ npm install micl
25
+ ```
26
+
27
+ ### 2. Add the CSS
28
+
29
+ **Sass/SCSS**
30
+
31
+ To import the styles for a single component:
32
+ ```SCSS
33
+ @use "micl/components/card";
34
+ ```
35
+ To import all component styles:
36
+ ```SCSS
37
+ @use "micl/styles";
38
+ ```
39
+
40
+ **Plain CSS**
41
+
42
+ Add the main CSS file to your project:
43
+ ```HTML
44
+ <link rel="stylesheet" href="node_modules/micl/dist/micl.css">
45
+ ```
46
+
47
+ ### 3. Add the HTML & JavaScript
48
+ Here is a simple example of a **Card** component.
49
+
50
+ ```HTML
51
+ <div class="micl-card-outlined">
52
+ <img src="your-image.jpg" alt="A beautiful image" class="micl-card__image">
53
+ <div class="micl-card__headline-s">
54
+ <h2>Card Title</h2>
55
+ </div>
56
+ <p class="micl-card__supporting-text">This is a simple card component.</p>
57
+ </div>
58
+ ```
59
+
60
+ Some components, list the **List**, require a small amount of JavaScript to handle interactive behaviour.
61
+
62
+ ```TypeScript
63
+ // For components with interactive behaviour
64
+ import list from "micl/components/lists";
65
+
66
+ // To import TypeScript for all components
67
+ import components from "micl/components";
68
+ ```
69
+
70
+ To import all the TypeScript into your project:
71
+ ```TypeScript
72
+ import components from "micl/components";
73
+ ```
74
+
75
+ **Plain JavaScript**
76
+
77
+ Add the main JavaScript file to your project:
78
+ ```HTML
79
+ <script src="node_modules/micl/dist/micl.js"></script>
80
+ ```
81
+
82
+ ## Available components ✅
83
+ The library currently consists of the following components:
84
+ - [x] [Accordion](components/accordion/README.md)
85
+ - [x] [Bottom sheet](components/bottomsheet/README.md)
86
+ - [x] [Button](components/button/README.md)
87
+ - [x] [Card](components/card/README.md)
88
+ - [x] [Checkbox](components/checkbox/README.md)
89
+ - [x] [Dialog](components/dialog/README.md)
90
+ - [x] [Divider](components/divider/README.md)
91
+ - [x] [Icon button](components/iconbutton/README.md)
92
+ - [x] [List](components/list/README.md)
93
+ - [x] [Menu](components/menu/README.md)
94
+ - [x] [Radio button](components/radio/README.md)
95
+ - [x] [Select](components/select/README.md)
96
+ - [x] [Side sheet](components/sidesheet/README.md)
97
+ - [x] [Slider](components/slider/README.md)
98
+ - [x] [Switch](components/switch/README.md)
99
+ - [x] [Text field](components/textfield/README.md)
@@ -0,0 +1,12 @@
1
+ # MICL Components
2
+ Welcome to the MICL component library, a collection of components that implement the [Material Design 3](https://m3.material.io/) specification.
3
+
4
+ Each component is self-contained in a separate folder, making it easy to find what you need. You'll typically find:
5
+
6
+ - A [Sass stylesheet](https://sass-lang.com/) for component-specific styling.
7
+
8
+ - If required, a [TypeScript module](https://www.typescriptlang.org/) for interactive features.
9
+
10
+ - A README.md file for detailed documentation.
11
+
12
+ Most components are standalone, but some are built on top of others. For example, the [Menu component](./menu/README.md) extends the [List component](./list/README.md), so it requires the styles and functionality of both. Always check the documentation for each component to see which dependencies you need to import. This ensures everything works as expected.
@@ -0,0 +1,94 @@
1
+ # Accordion
2
+ This component implements the the [Material Design 3 Expressive Expandable Lists](https://m3.material.io/components/lists/overview) design. Accordions are vertically stacked lists that allow you to show and hide sections of content.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ The Accordion component is an extension of the [**List** component](../list/README.md), using `<details>` and `<summary>` elements for its interactive behaviour. To create a basic accordion, use a `<div>` with the `micl-list` class and nest individual `<details>` elements for each collapsible item. Apply the appropriate `micl-list-item-` class to the `summary` element.
8
+
9
+ ```HTML
10
+ <div class="micl-list" role="listbox">
11
+ <details>
12
+ <summary class="micl-list-item-one">
13
+ <span class="micl-list-item__text">
14
+ <span class="micl-list-item__headline">A single-line accordion item</span>
15
+ </span>
16
+ </summary>
17
+ <div class="micl-list-item__content">
18
+ <p class="md-sys-typescale-body-medium">
19
+ This is the content that is revealed when the accordion item is expanded.
20
+ </p>
21
+ </div>
22
+ </details>
23
+ </div>
24
+ ```
25
+
26
+ - The `role="listbox"` is used for accessibility, indicating a selectable list.
27
+
28
+ - The `micl-list-item__content` class styles the collapsible area of the accordion item.
29
+
30
+ ### CSS
31
+ Import the list styles into your project:
32
+
33
+ ```CSS
34
+ @use "micl/components/list";
35
+ ```
36
+
37
+ ### TypeScript
38
+ This component requires a TypeScript module to support keyboard navigation. You can import the specific module or use the main MICL TypeScript library, which handles initialization automatically.
39
+
40
+ To manually initialize the component:
41
+
42
+ ```TypeScript
43
+ import miclAccordion from 'micl/components/list';
44
+
45
+ miclAccordion.initialize(document.querySelector('.micl-list'));
46
+ ```
47
+
48
+ ### Demo
49
+ A live example of the [Accordion component](https://henkpb.github.io/micl/accordion.html) is available for you to interact with.
50
+
51
+ ## Variants
52
+ To ensure that only one accordion item within a group can be open at a time, add a matching `name` attribute to all the `<details>` elements you want to group together.
53
+
54
+ ```HTML
55
+ <div class="micl-list" role="listbox">
56
+ <details name="mygroup">
57
+ <summary class="micl-list-item-two">
58
+ <span class="micl-list-item__text">
59
+ <span class="micl-list-item__headline">Marie Curie</span>
60
+ <span class="micl-list-item__supporting-text">The name of the employee.</span>
61
+ </span>
62
+ </summary>
63
+ <div class="micl-list-item__content">
64
+ <div class="micl-textfield-filled">
65
+ <label for="tf1">Name</label>
66
+ <input type="text" id="tf1" value="Marie Curie">
67
+ </div>
68
+ </div>
69
+ </details>
70
+ <details name="mygroup">
71
+ <summary class="micl-list-item-two" tabindex="-1">
72
+ <span class="micl-list-item__text">
73
+ <span class="micl-list-item__headline">Country</span>
74
+ <span class="micl-list-item__supporting-text">The country of residence.</span>
75
+ </span>
76
+ </summary>
77
+ <div class="micl-list-item__content">
78
+ <div class="micl-textfield-filled">
79
+ <label for="tf2">Country</label>
80
+ <input type="text" id="tf2" value="France">
81
+ </div>
82
+ </div>
83
+ </details>
84
+ </div>
85
+ ```
86
+
87
+ Adding the `micl-list-item--disabled` class to the `<summary>` element causes the accordion item to be displayed in a disabled state.
88
+
89
+ Add the `micl-list__divider` class to the `<div class="micl-list">` element to automatically place a divider between each accordion item.
90
+
91
+ Since the Accordion is based on the List component, you can use the same utility classes for content structure and styling. Refer to the [List component documentation](../list/README.md) for details on how to add icons, avatars, images, and other features to your accordion items.
92
+
93
+ ## Compatibility
94
+ The Card component uses the `interpolate-size` CSS property to smoothly open and close the detail area of a Details disclosure element, which might not be supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size#browser_compatibility) for details.
@@ -0,0 +1,77 @@
1
+ # Bottom sheet
2
+ This component implements the the [Material Design 3 Expressive Bottom sheet](https://m3.material.io/components/bottom-sheets/overview) design.
3
+
4
+ ## Basic Usage
5
+
6
+ ### HTML
7
+ To create a standard bottom sheet, use the `<dialog>` element with the `popover` attribute. The `closedby="any"` attribute allows the user to dismiss the bottom sheet by clicking anywhere outside of it.
8
+
9
+ ```HTML
10
+ <dialog id="mybottomsheet" class="micl-bottomsheet" closedby="any" popover>
11
+ <div class="micl-bottomsheet__content">
12
+ ...your content...
13
+ </div>
14
+ </dialog>
15
+ ```
16
+
17
+ ### CSS
18
+ Import the bottom sheet styles into your project:
19
+
20
+ ```CSS
21
+ @use "micl/components/bottomsheet";
22
+ ```
23
+
24
+ ### TypeScript
25
+ This component requires a TypeScript module to support **modal** and **resizable** bottom sheets. You can import the specific bottom sheet module or use the main MICL TypeScript library, which handles initialization automatically.
26
+
27
+ To manually initialize the component:
28
+
29
+ ```TypeScript
30
+ import miclBottomSheet from 'micl/components/bottomsheet';
31
+
32
+ miclBottomSheet.initialize(document.querySelector('.micl-bottomsheet'));
33
+ ```
34
+
35
+ ### Demo
36
+ A live example of the [Bottom sheet component](https://henkpb.github.io/micl/bottomsheet.html) is available for you to interact with.
37
+
38
+ ## Variants
39
+ A **modal** bottom sheet blocks access to the rest of the page and must be dismissed explicitly by the user. This is suitable for critical tasks or information that requires a user's full attention.
40
+
41
+ To create a modal bottom sheet, use the `<dialog>` element without the `popover` attribute. Use `closedby="closerequest"` to prevent the bottom sheet from being dismissed by clicking outside of it. You'll also need a button or other control with popovertarget to close it.
42
+
43
+ ```HTML
44
+ <dialog id="mybottomsheet" class="micl-bottomsheet" closedby="closerequest">
45
+ <div class="micl-bottomsheet__content">
46
+ ...your content...
47
+ </div>
48
+ </dialog>
49
+ ```
50
+
51
+ Include the bottom sheet heading (and optionally a drag-handle) to create a **resizable** bottom sheet.
52
+
53
+ ```HTML
54
+ <dialog id="mybottomsheet" class="micl-bottomsheet" closedby="any" popover>
55
+ <div class="micl-bottomsheet__headline">
56
+ <button type="button" class="micl-bottomsheet__draghandle" aria-label="Drag handle"></button>
57
+ </div>
58
+ <div class="micl-bottomsheet__content">
59
+ ...your content...
60
+ </div>
61
+ </dialog>
62
+ ```
63
+
64
+ The height of a bottom sheet is determined by its contents, initially capped at 50% of the screen height. You can set multiple preset heights for the bottom sheet by adding the `data-snapheights` attribute:
65
+
66
+ ```HTML
67
+ <dialog data-snapheights="200,420" ...>
68
+ ```
69
+
70
+ To open or close a bottom sheet, add the markup of a button that is linked to the bottom sheet using the `popovertarget` attribute:
71
+
72
+ ```HTML
73
+ <button type="button" popovertarget="mybottomsheet">Open Bottom Sheet</button>
74
+ ```
75
+
76
+ ## Compatibility
77
+ This component uses the Popover API, which might not be supported in your browser. Please check [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API#api.htmlelement.popover) for details.
@@ -0,0 +1,134 @@
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
+ dialog.micl-bottomsheet {
25
+ --md-sys-bottomsheet-height: max-content;
26
+ --md-sys-bottomsheet-margin: 56px;
27
+ --md-sys-bottomsheet-padding: 24px;
28
+ --md-sys-bottomsheet-handle-width: 32px;
29
+ --md-sys-bottomsheet-handle-height: 4px;
30
+
31
+ box-sizing: border-box;
32
+ position: fixed;
33
+ top: auto;
34
+ bottom: 0;
35
+ height: 0;
36
+ max-height: 50vh;
37
+ width: 100%;
38
+ min-width: 100%;
39
+ margin: 72px 0 0 0 ;
40
+ padding: 0;
41
+ border: none;
42
+ border-radius: var(--md-sys-shape-corner-extra-large) var(--md-sys-shape-corner-extra-large) 0 0;
43
+ background-color: var(--md-sys-color-surface-container-low);
44
+ box-shadow: var(--md-sys-elevation-level1);
45
+ opacity: 0;
46
+ overflow-y: hidden;
47
+ interpolate-size: allow-keywords;
48
+
49
+ &:not(.micl-bottomsheet--resizing) {
50
+ transition:
51
+ display var(--md-sys-motion-duration-medium1) allow-discrete,
52
+ overlay var(--md-sys-motion-duration-medium1) allow-discrete,
53
+ height var(--md-sys-motion-duration-medium1) linear(motion.$md-sys-motion-spring-default-spatial),
54
+ opacity var(--md-sys-motion-duration-medium1) motion.$md-sys-motion-easing-emphasized-accelerate;
55
+ }
56
+ &.micl-bottomsheet--resizing .micl-bottomsheet__headline {
57
+ cursor: grabbing;
58
+ }
59
+ .micl-bottomsheet__headline {
60
+ box-sizing: border-box;
61
+ display: flex;
62
+ align-items: center;
63
+ width: 100%;
64
+ height: var(--md-sys-target-size);
65
+ justify-content: center;
66
+ cursor: grab;
67
+
68
+ .micl-bottomsheet__draghandle {
69
+ box-sizing: content-box;
70
+ width: var(--md-sys-bottomsheet-handle-width);
71
+ height: var(--md-sys-bottomsheet-handle-height);
72
+ padding: 16px calc((var(--md-sys-target-size) - var(--md-sys-bottomsheet-handle-width)) / 2);
73
+ border: none;
74
+ border-radius: var(--md-sys-shape-corner-small);
75
+ background-color: var(--md-sys-color-on-surface-variant);
76
+ background-clip: content-box;
77
+ cursor: pointer;
78
+
79
+ &:focus-visible {
80
+ outline: var(--md-sys-state-focus-indicator-thickness) solid var(--md-sys-color-secondary);
81
+ outline-offset: var(--md-sys-state-focus-indicator-outer-offset);
82
+ }
83
+ }
84
+ }
85
+
86
+ .micl-bottomsheet__content {
87
+ padding: 0 var(--md-sys-bottomsheet-padding) var(--md-sys-bottomsheet-padding) var(--md-sys-bottomsheet-padding);
88
+ }
89
+
90
+ &::backdrop {
91
+ background-color: rgba(0, 0, 0, 0);
92
+ transition:
93
+ display var(--md-sys-motion-duration-long2) linear allow-discrete,
94
+ overlay var(--md-sys-motion-duration-long2) linear allow-discrete,
95
+ background-color var(--md-sys-motion-duration-long2) linear;
96
+ }
97
+ &:popover-open,
98
+ &[open] {
99
+ height: var(--md-sys-bottomsheet-height);
100
+ opacity: 1;
101
+ // overflow-y: auto;
102
+
103
+ &:not(.micl-bottomsheet--resizing) {
104
+ transition:
105
+ display var(--md-sys-motion-duration-long2) linear allow-discrete,
106
+ overlay var(--md-sys-motion-duration-long2) linear allow-discrete,
107
+ height var(--md-sys-motion-duration-long2) linear(motion.$md-sys-motion-spring-default-spatial),
108
+ opacity var(--md-sys-motion-duration-long2) motion.$md-sys-motion-easing-emphasized-decelerate;
109
+ }
110
+ @starting-style {
111
+ height: 0;
112
+ opacity: 0;
113
+ }
114
+ &::backdrop {
115
+ @starting-style {
116
+ background-color: rgba(0, 0, 0, 0);
117
+ }
118
+ }
119
+ }
120
+ &[open]::backdrop {
121
+ background-color: rgba(0, 0, 0, 0.2);
122
+ }
123
+ }
124
+
125
+ @media (min-width: 641px) {
126
+ dialog.micl-bottomsheet {
127
+ width: min(100vw - (2 * var(--md-sys-bottomsheet-margin)), 640px);
128
+ max-width: 640px;
129
+ min-width: min(100vw - (2 * var(--md-sys-bottomsheet-margin)), 640px);
130
+ inset-inline-start: calc(((100vw - min(100vw - (2 * var(--md-sys-bottomsheet-margin)), 640px)) / 2) - var(--md-sys-bottomsheet-margin));
131
+ margin: var(--md-sys-bottomsheet-margin);
132
+ margin-bottom: 0;
133
+ }
134
+ }
@@ -0,0 +1,152 @@
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 bottomsheetSelector = 'dialog.micl-bottomsheet';
23
+
24
+ export default (() =>
25
+ {
26
+ var fitHeight = 0;
27
+
28
+ const getSnapHeights = (dialog: HTMLDialogElement): number[] =>
29
+ {
30
+ let maxHeight = parseInt(window.getComputedStyle(dialog).getPropertyValue('max-height')),
31
+ snapHeights = (dialog.dataset.snapheights || '').split(',').map(Number).filter(
32
+ n => !isNaN(n) && (n > 0) && (n <= maxHeight)
33
+ );
34
+
35
+ if (!fitHeight) {
36
+ fitHeight = dialog.getBoundingClientRect().height;
37
+ }
38
+ return [...new Set(snapHeights.concat([fitHeight, maxHeight]).sort())];
39
+ };
40
+
41
+ const getNextSnapHeight = (dialog: HTMLDialogElement, isResizing: boolean): number =>
42
+ {
43
+ let currentHeight = dialog.getBoundingClientRect().height,
44
+ snapHeights = getSnapHeights(dialog),
45
+ largerSnaps = snapHeights.filter(height => height > currentHeight + 4);
46
+
47
+ return largerSnaps[0] || snapHeights[isResizing ? snapHeights.length - 1 : 0];
48
+ };
49
+
50
+ const getPreviousSnapHeight = (dialog: HTMLDialogElement): number =>
51
+ {
52
+ let currentHeight = dialog.getBoundingClientRect().height,
53
+ snapHeights = getSnapHeights(dialog),
54
+ smallerSnaps = snapHeights.filter(height => height < currentHeight - 4);
55
+
56
+ return smallerSnaps[smallerSnaps.length - 1] || snapHeights[0];
57
+ };
58
+
59
+ const setHeight = (dialog: HTMLDialogElement, value: number): void =>
60
+ {
61
+ dialog.style.setProperty('--md-sys-bottomsheet-height', `${value}px`);
62
+ }
63
+
64
+ return {
65
+ initialize: (element: HTMLDialogElement) =>
66
+ {
67
+ if (
68
+ !element.matches('dialog.micl-bottomsheet')
69
+ || element.dataset.miclinitialized
70
+ ) {
71
+ return;
72
+ }
73
+ element.dataset.miclinitialized = '1';
74
+
75
+ const headline = element.querySelector('.micl-bottomsheet__headline') as HTMLElement;
76
+ if (!headline) {
77
+ return;
78
+ }
79
+ const draghandle = headline.querySelector('.micl-bottomsheet__draghandle') as HTMLElement;
80
+
81
+ draghandle?.addEventListener('click', () =>
82
+ {
83
+ const nextSnapHeight = getNextSnapHeight(element, false);
84
+ if (nextSnapHeight > 4) {
85
+ setHeight(element, nextSnapHeight);
86
+ }
87
+ });
88
+
89
+ let isPreparing = false,
90
+ isResizing = false,
91
+ initialMouseY: number,
92
+ initialHeight: number;
93
+
94
+ headline.addEventListener('mousedown', (event: Event) =>
95
+ {
96
+ if (event.eventPhase === Event.AT_TARGET) {
97
+ isPreparing = true;
98
+ event.preventDefault();
99
+ initialMouseY = (event as MouseEvent).clientY;
100
+ initialHeight = element.getBoundingClientRect().height;
101
+ document.addEventListener('mousemove', onMouseMove);
102
+ document.addEventListener('mouseup', onMouseUp);
103
+ }
104
+ });
105
+
106
+ function onMouseMove(event: Event)
107
+ {
108
+ const currentMouseY = (event as MouseEvent).clientY;
109
+ if (isPreparing && (Math.abs(initialMouseY - currentMouseY) > 4)) {
110
+ isPreparing = false;
111
+ isResizing = true;
112
+ element.classList.add('micl-bottomsheet--resizing');
113
+ }
114
+ if (isResizing) {
115
+ setHeight(element, initialHeight + initialMouseY - currentMouseY);
116
+ }
117
+ }
118
+ function onMouseUp(event: Event)
119
+ {
120
+ isPreparing = false;
121
+ element.classList.remove('micl-bottomsheet--resizing');
122
+ if (isResizing) {
123
+ isResizing = false;
124
+
125
+ const currentMouseY = (event as MouseEvent).clientY;
126
+ if (currentMouseY < initialMouseY) {
127
+ setHeight(element, getNextSnapHeight(element, true));
128
+ }
129
+ else {
130
+ if (element.getBoundingClientRect().height < 48) {
131
+ if (!!element.popover) {
132
+ element.hidePopover();
133
+ }
134
+ setHeight(element, initialHeight);
135
+ }
136
+ else {
137
+ setHeight(element, getPreviousSnapHeight(element));
138
+ }
139
+ }
140
+ document.removeEventListener('mousemove', onMouseMove);
141
+ document.removeEventListener('mouseup', onMouseUp);
142
+ }
143
+ }
144
+ },
145
+ cleanup: (element: HTMLDialogElement) =>
146
+ {
147
+ if (element.matches('dialog.micl-bottomsheet')) {
148
+ delete element.dataset.miclinitialized;
149
+ }
150
+ }
151
+ };
152
+ })();