@natachah/vanilla-frontend 0.0.2

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 (104) hide show
  1. package/.gitlab-ci.yml +40 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +11 -0
  4. package/docs/index.html +36 -0
  5. package/docs/main.js +32 -0
  6. package/docs/pages/components/badge.html +154 -0
  7. package/docs/pages/components/button.html +186 -0
  8. package/docs/pages/components/card.html +184 -0
  9. package/docs/pages/components/dialog.html +334 -0
  10. package/docs/pages/components/disclosure.html +310 -0
  11. package/docs/pages/components/dropdown.html +255 -0
  12. package/docs/pages/components/form.html +331 -0
  13. package/docs/pages/components/list.html +140 -0
  14. package/docs/pages/components/loading.html +58 -0
  15. package/docs/pages/components/media.html +130 -0
  16. package/docs/pages/components/nav.html +119 -0
  17. package/docs/pages/components/progress.html +47 -0
  18. package/docs/pages/components/slider.html +311 -0
  19. package/docs/pages/components/table.html +168 -0
  20. package/docs/pages/javascript/autofill.html +170 -0
  21. package/docs/pages/javascript/checkall.html +59 -0
  22. package/docs/pages/javascript/comfort.html +134 -0
  23. package/docs/pages/javascript/consent.html +112 -0
  24. package/docs/pages/javascript/cookie.html +81 -0
  25. package/docs/pages/javascript/form.html +199 -0
  26. package/docs/pages/javascript/scroll.html +209 -0
  27. package/docs/pages/javascript/sidebar.html +53 -0
  28. package/docs/pages/javascript/sortable.html +148 -0
  29. package/docs/pages/javascript/toggle.html +191 -0
  30. package/docs/pages/javascript/tree.html +221 -0
  31. package/docs/pages/layout/grid.html +201 -0
  32. package/docs/pages/layout/reset.html +53 -0
  33. package/docs/pages/layout/typography.html +324 -0
  34. package/docs/pages/quick-start/conventions.html +112 -0
  35. package/docs/pages/quick-start/customization.html +187 -0
  36. package/docs/pages/quick-start/installation.html +95 -0
  37. package/docs/pages/quick-start/mixins.html +228 -0
  38. package/docs/pages/test.html +15 -0
  39. package/docs/src/js/demo.js +98 -0
  40. package/docs/src/js/doc-code.js +102 -0
  41. package/docs/src/js/doc-demo.js +14 -0
  42. package/docs/src/js/doc-layout.js +108 -0
  43. package/docs/src/scss/demo.scss +77 -0
  44. package/docs/src/scss/layout.scss +160 -0
  45. package/docs/src/scss/style.scss +278 -0
  46. package/docs/vite.config.mjs +23 -0
  47. package/esbuild.mjs +25 -0
  48. package/js/_autofill.js +131 -0
  49. package/js/_check-all.js +77 -0
  50. package/js/_comfort.js +174 -0
  51. package/js/_consent.js +84 -0
  52. package/js/_dialog.js +164 -0
  53. package/js/_dropdown.js +101 -0
  54. package/js/_scroll.js +184 -0
  55. package/js/_sidebar.js +97 -0
  56. package/js/_slider.js +249 -0
  57. package/js/_sortable.js +143 -0
  58. package/js/_tabpanel.js +88 -0
  59. package/js/_toggle.js +123 -0
  60. package/js/_tree.js +85 -0
  61. package/js/tests/autofill.test.js +157 -0
  62. package/js/tests/base-component.test.js +108 -0
  63. package/js/tests/check-all.test.js +88 -0
  64. package/js/tests/comfort.test.js +219 -0
  65. package/js/tests/consent.test.js +84 -0
  66. package/js/tests/cookie.test.js +102 -0
  67. package/js/tests/dialog.test.js +189 -0
  68. package/js/tests/dropdown.test.js +115 -0
  69. package/js/tests/form-helper.test.js +155 -0
  70. package/js/tests/scroll.test.js +203 -0
  71. package/js/tests/sidebar.test.js +99 -0
  72. package/js/tests/slider.test.js +307 -0
  73. package/js/tests/sortable.test.js +124 -0
  74. package/js/tests/tabpanel.test.js +114 -0
  75. package/js/tests/toggle.test.js +190 -0
  76. package/js/tests/tree.test.js +165 -0
  77. package/js/utilities/_base-component.js +101 -0
  78. package/js/utilities/_cookie.js +98 -0
  79. package/js/utilities/_error.js +80 -0
  80. package/js/utilities/_form-helper.js +101 -0
  81. package/package.json +42 -0
  82. package/scss/_badge.scss +37 -0
  83. package/scss/_button.scss +34 -0
  84. package/scss/_card.scss +122 -0
  85. package/scss/_dialog.scss +116 -0
  86. package/scss/_disclosure.scss +101 -0
  87. package/scss/_dropdown.scss +68 -0
  88. package/scss/_form.scss +197 -0
  89. package/scss/_grid.scss +40 -0
  90. package/scss/_group.scss +57 -0
  91. package/scss/_list.scss +18 -0
  92. package/scss/_loading.scss +49 -0
  93. package/scss/_media.scss +37 -0
  94. package/scss/_nav.scss +72 -0
  95. package/scss/_progress.scss +40 -0
  96. package/scss/_slider.scss +35 -0
  97. package/scss/_table.scss +36 -0
  98. package/scss/utilities/_mixin.scss +322 -0
  99. package/scss/utilities/_reset.scss +145 -0
  100. package/scss/utilities/_typography.scss +107 -0
  101. package/scss/vanilla-frontend.scss +23 -0
  102. package/scss/variables/_root.scss +70 -0
  103. package/scss/variables/_setting.scss +63 -0
  104. package/vitest.config.js +7 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ------------------------------------------------------------------
3
+ * Form helper
4
+ * ------------------------------------------------------------------
5
+ * This class regroup some helper function for forms
6
+ *
7
+ * @author Natacha Herth
8
+ * @version 0.0.1
9
+ * @copyright Natacha Herth, design & web development
10
+ */
11
+
12
+ import ErrorMessage from "./_error"
13
+
14
+ export default class FormHelper {
15
+
16
+ /**
17
+ * Toggle the visibility of a password input
18
+ *
19
+ * @param {HTMLElement} button - A <button> element
20
+ */
21
+ static togglePassword(button) {
22
+
23
+ // Check for errors
24
+ if (!(button instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('button', 'HTMLElement'))
25
+ if (!button.hasAttribute('aria-controls')) throw new Error(ErrorMessage.withAttribute('button', 'aria-controls'))
26
+
27
+ // Define the <input>
28
+ const input = document.getElementById(button.getAttribute('aria-controls'))
29
+
30
+ // If no <input> return error
31
+ if (!input) throw new Error(ErrorMessage.existById('input', button.getAttribute('aria-controls')))
32
+
33
+ // Define the new [type] of the <input>
34
+ const type = input.type === 'password' ? 'text' : 'password'
35
+ input.setAttribute('type', type)
36
+
37
+ // Change the [aria-pressed] attribute on the <button>
38
+ button.setAttribute('aria-pressed', type !== 'password')
39
+
40
+ }
41
+
42
+ /**
43
+ * Toggle the attributes value, disabled and required on multiple fields when is visible
44
+ *
45
+ * @param {Object} fields - Array of fields to toggle
46
+ * @param {boolean} isVisible - Are the fields visible
47
+ * @param {Object} customOptions - Object of options
48
+ */
49
+ static toggleAttributes(fields, isVisible, customOptions = {}) {
50
+
51
+ // Check for errors
52
+ if (!(fields instanceof Object)) throw new Error(ErrorMessage.instanceOf('fields', 'Object'))
53
+ if (typeof isVisible !== 'boolean') throw new Error(ErrorMessage.typeOf('isVisible', 'boolean'))
54
+ if (!(customOptions instanceof Object)) throw new Error(ErrorMessage.instanceOf('customOptions', 'Object'))
55
+ if (customOptions.reset && !['boolean', 'object'].includes(typeof customOptions.reset)) throw new Error(ErrorMessage.typeOf('customOptions.reset', 'boolean|object'))
56
+ if (customOptions.disabled && !['boolean', 'object'].includes(typeof customOptions.disabled)) throw new Error(ErrorMessage.typeOf('customOptions.disabled', 'boolean|object'))
57
+ if (customOptions.required && !['boolean', 'object'].includes(typeof customOptions.required)) throw new Error(ErrorMessage.typeOf('customOptions.required', 'boolean|object'))
58
+ if (customOptions.unchanged && typeof customOptions.unchanged !== 'object') throw new Error(ErrorMessage.typeOf('customOptions.unchanged', 'object'))
59
+
60
+ // Set default options
61
+ const defaultOption = {
62
+ reset: true, // Can be true/false/[name]
63
+ disabled: true, // Can be true/false/[name]
64
+ required: false, // Can be true/false/[name]
65
+ unchanged: [] // Array of names
66
+ }
67
+
68
+ // Merge the options
69
+ const options = { ...defaultOption, ...customOptions }
70
+
71
+ // Do stuff for each field
72
+ fields.forEach(field => {
73
+
74
+ // Get the name
75
+ const name = field.getAttribute('name')
76
+
77
+ // Stop if the field must not change
78
+ if (options.unchanged.includes(name)) return
79
+
80
+ // Get the needs
81
+ let needReset = typeof options.reset === 'boolean' ? options.reset : options.reset.includes(name)
82
+ let needDisabled = typeof options.disabled === 'boolean' ? options.disabled : options.disabled.includes(name)
83
+ let needRequired = typeof options.required === 'boolean' ? options.required : options.required.includes(name)
84
+
85
+ // Reset the field
86
+ if (needReset && !isVisible) {
87
+ field.value = null
88
+ field.checked = null
89
+ }
90
+
91
+ // Disabled field
92
+ if (needDisabled) field.disabled = !isVisible
93
+
94
+ // Required field
95
+ if (needRequired) field.required = isVisible
96
+
97
+ })
98
+
99
+ }
100
+
101
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@natachah/vanilla-frontend",
3
+ "version": "0.0.2",
4
+ "description": "A vanilla frontend framework",
5
+ "keywords": [
6
+ "html5",
7
+ "css3",
8
+ "javascript",
9
+ "vanilla",
10
+ "frontend"
11
+ ],
12
+ "author": {
13
+ "name": "Natacha Herth",
14
+ "email": "info@natachaherth.ch"
15
+ },
16
+ "license": "MIT",
17
+ "scripts": {
18
+ "test": "vitest --coverage",
19
+ "code:build": "node esbuild.mjs",
20
+ "docs:dev": "cd docs && vite",
21
+ "docs:build": "cd docs && vite build",
22
+ "docs:preview": "cd docs && vite preview"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+ssh://git@gitlab.com:packages4913705/vanilla-frontend.git"
27
+ },
28
+ "devDependencies": {
29
+ "@testing-library/dom": "^9.3.4",
30
+ "@vitest/coverage-v8": "^1.3.1",
31
+ "autoprefixer": "^10.4.19",
32
+ "esbuild": "0.21.5",
33
+ "esbuild-sass-plugin": "^3.3.1",
34
+ "fast-glob": "^3.3.2",
35
+ "happy-dom": "^13.6.2",
36
+ "postcss": "^8.4.38",
37
+ "sass": "^1.77.5",
38
+ "shiki": "^1.7.0",
39
+ "vite": "^5.2.0",
40
+ "vitest": "^1.3.1"
41
+ }
42
+ }
@@ -0,0 +1,37 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Badge
4
+ /// ------------------------------------------------------------------
5
+ /// Create the badge component
6
+ ///
7
+ /// @example <span class="badge">...</span>
8
+ ///
9
+ /// @require {mixin} as-item
10
+ /// @group components
11
+ /// @author Natacha Herth
12
+ /// @since 1.0.0
13
+ ///
14
+ ////
15
+
16
+ @use "sass:map";
17
+
18
+ $custom: (
19
+ background: color-mix(in srgb, transparent, var(--hover-color) var(--hover-percent)),
20
+ padding-block: .25em,
21
+ padding-inline: .375em
22
+ );
23
+
24
+ // Change default properties
25
+ $properties: map.merge($default-item-properties, $custom);
26
+
27
+ .badge {
28
+
29
+ // Customization for badge
30
+ display: inline-block;
31
+ font-size: var(--badge-font-size, var(--font-size-small));
32
+ line-height: 1;
33
+
34
+ // Define as an item
35
+ @include as-item('badge', (), $properties);
36
+
37
+ }
@@ -0,0 +1,34 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Button
4
+ /// ------------------------------------------------------------------
5
+ /// Create the button component
6
+ ///
7
+ /// @example <button>Button</button> or <a href="#" role="button">Button</a>
8
+ ///
9
+ /// @require {mixin} as-item
10
+ /// @group components
11
+ /// @author Natacha Herth
12
+ /// @since 1.0.0
13
+ ///
14
+ ////
15
+
16
+ button,
17
+ [type=button],
18
+ [type=reset],
19
+ [type=submit],
20
+ [role=button] {
21
+
22
+ // Customization for button
23
+ display: inline-block;
24
+
25
+ // Define as an item
26
+ @include as-item('button', ('focus', 'hover', 'active', 'disabled'));
27
+
28
+ // Set as :disabled for link without href
29
+ &:is(a):not([href]) {
30
+ pointer-events: none;
31
+ opacity: var(--button-disabled-opacity, var(--disabled-opacity));
32
+ }
33
+
34
+ }
@@ -0,0 +1,122 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Card
4
+ /// ------------------------------------------------------------------
5
+ /// Create the card component
6
+ ///
7
+ /// @example <div class="card">...</div>
8
+ ///
9
+ /// @require {mixin} as-item
10
+ /// @group components
11
+ /// @author Natacha Herth
12
+ /// @since 1.0.0
13
+ ///
14
+ ////
15
+
16
+ @use "sass:map";
17
+
18
+ $custom: (
19
+ padding-block: 1rem,
20
+ padding-inline: 1rem
21
+ );
22
+
23
+ // Change default properties
24
+ $properties: map.merge($default-item-properties, $custom);
25
+
26
+ .card {
27
+
28
+ // Design as item
29
+ @include as-item('card', (), $properties);
30
+
31
+ // Design the layout
32
+ > header,
33
+ > footer,
34
+ > picture,
35
+ > .list,
36
+ > .accordion,
37
+ > .group {
38
+ margin-inline: calc(var(--card-padding-inline, map.get($custom, padding-inline)) * -1);
39
+ }
40
+
41
+ > header,
42
+ > footer {
43
+ padding: calc(var(--card-padding-block, map.get($custom, padding-block)) / 2) var(--card-padding-inline, map.get($custom, padding-inline));
44
+ }
45
+
46
+ > header,
47
+ > :first-child:is(picture, .list, .group) {
48
+ margin-top: calc(var(--card-padding-block, map.get($custom, padding-block)) * -1);
49
+ border-start-start-radius: inherit;
50
+ border-start-end-radius: inherit;
51
+ border-bottom: var(--card-divider-size, var(--card-border-size, var(--border-size))) var(--card-divider-style, var(--card-border-style, var(--border-style))) var(--card-divider-color, var(--card-border-color, transparent));
52
+ }
53
+
54
+ > footer,
55
+ > :last-child:is(picture, .list, .group) {
56
+ margin-bottom: calc(var(--card-padding-block, map.get($custom, padding-block)) * -1);
57
+ border-end-start-radius: inherit;
58
+ border-end-end-radius: inherit;
59
+ border-top: var(--card-divider-size, var(--card-border-size, var(--border-size))) var(--card-divider-style, var(--card-border-style, var(--border-style))) var(--card-divider-color, var(--card-border-color, transparent));
60
+ }
61
+
62
+ > :not(:first-child, :last-child):is(picture, .list, .group) {
63
+ border-top: var(--card-divider-size, var(--card-border-size, var(--border-size))) var(--card-divider-style, var(--card-border-style, var(--border-style))) var(--card-divider-color, var(--card-border-color, transparent));
64
+ border-bottom: var(--card-divider-size, var(--card-border-size, var(--border-size))) var(--card-divider-style, var(--card-border-style, var(--border-style))) var(--card-divider-color, var(--card-border-color, transparent));
65
+ }
66
+
67
+ > picture {
68
+ max-width: inherit;
69
+ overflow: hidden;
70
+
71
+ > img {
72
+ width: 100%;
73
+ }
74
+ }
75
+
76
+ > .list {
77
+
78
+ > *,
79
+ > * > a {
80
+ border-inline: none !important;
81
+ border-radius: inherit !important;
82
+ }
83
+
84
+ > *:first-child,
85
+ > *:first-child > a {
86
+ border-top: none !important;
87
+ }
88
+
89
+ > *:last-child,
90
+ > *:last-child > a {
91
+ border-bottom: none !important;
92
+ }
93
+
94
+ }
95
+
96
+ > .group {
97
+
98
+ > * {
99
+ width: 100%;
100
+ border-radius: inherit;
101
+
102
+ &:first-child {
103
+ border-left: none;
104
+ }
105
+
106
+ &:last-child {
107
+ border-right: none;
108
+ }
109
+
110
+ }
111
+
112
+ &:is(:first-child) > * {
113
+ border-top: none;
114
+ }
115
+
116
+ &:is(:last-child) > * {
117
+ border-bottom: none;
118
+ }
119
+
120
+ }
121
+
122
+ }
@@ -0,0 +1,116 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Dialog
4
+ /// ------------------------------------------------------------------
5
+ /// Create the dialog component
6
+ ///
7
+ /// @example <dialog>My dialog</dialog>
8
+ ///
9
+ /// @group components
10
+ /// @author Natacha Herth
11
+ /// @since 1.0.0
12
+ ///
13
+ ////
14
+
15
+ @use "sass:map";
16
+
17
+ $custom: (
18
+ background: var(--color-body),
19
+ padding-block: 2rem,
20
+ padding-inline: 2rem
21
+ );
22
+
23
+ // Change default properties
24
+ $properties: map.merge($default-item-properties, $custom);
25
+
26
+ dialog {
27
+
28
+ // Position the dialog
29
+ position: fixed;
30
+ inset: var(--dialog-position, 0);
31
+
32
+ // Define the CSS
33
+ margin: auto;
34
+ width: var(--dialog-width, 576px);
35
+ height: var(--dialog-height, fit-content);
36
+ max-width: var(--dialog-max-width, 100dvw);
37
+ max-height: var(--dialog-max-height, 100dvh);
38
+ overflow: auto;
39
+
40
+ // Design as item
41
+ @include as-item('dialog', (), $properties);
42
+
43
+ // Animate the dialog
44
+ @media screen and (prefers-reduced-motion: no-preference) {
45
+ animation: var(--dialog-close-animation, close-dialog 0.25s ease-in-out);
46
+ }
47
+
48
+ // Display dialog when open
49
+ // Because of the [inert] attribute on the body, require the pointer-events
50
+ &[open] {
51
+ z-index: var(--dialog-index, 1);
52
+ pointer-events: auto;
53
+
54
+ @media screen and (prefers-reduced-motion: no-preference) {
55
+ animation: var(--dialog-open-animation, open-dialog 0.25s ease-in-out);
56
+ }
57
+ }
58
+
59
+ // Design the backdrop, can't be updated per dialog
60
+ &::backdrop {
61
+ background: var(--dialog-backdrop-background, var(--backdrop-color, rgba(black, .75)));
62
+ backdrop-filter: var(--dialog-backdrop-filter, var(--backdrop-filter, blur(.5rem)));
63
+ -webkit-backdrop-filter: var(--dialog-backdrop-filter, var(--backdrop-filter, blur(.5rem)));
64
+ }
65
+
66
+ // Design the layout
67
+ > header,
68
+ > footer {
69
+ margin-inline: calc(var(--dialog-padding-inline, map.get($custom, padding-inline)) * -1);
70
+ padding: calc(var(--dialog-padding-block, map.get($custom, padding-block)) / 2) var(--dialog-padding-inline, map.get($custom, padding-inline));
71
+ }
72
+
73
+ > header {
74
+ margin-top: calc(var(--dialog-padding-block, map.get($custom, padding-block)) * -1);
75
+ border-start-start-radius: inherit;
76
+ border-start-end-radius: inherit;
77
+ border-bottom: var(--dialog-divider-size, var(--dialog-border-size, var(--border-size))) var(--dialog-divider-style, var(--dialog-border-style, var(--border-style))) var(--dialog-divider-color, var(--dialog-border-color, transparent));
78
+ }
79
+
80
+ > footer {
81
+ margin-bottom: calc(var(--dialog-padding-block, map.get($custom, padding-block)) * -1);
82
+ border-end-start-radius: inherit;
83
+ border-end-end-radius: inherit;
84
+ border-top: var(--dialog-divider-size, var(--dialog-border-size, var(--border-size))) var(--dialog-divider-style, var(--dialog-border-style, var(--border-style))) var(--dialog-divider-color, var(--dialog-border-color, transparent));
85
+ }
86
+
87
+ }
88
+
89
+ @keyframes open-dialog {
90
+ from {
91
+ opacity: 0;
92
+ transform: var(--dialog-open-transform, translateY(100px));
93
+ display: none;
94
+ }
95
+
96
+ to {
97
+ opacity: 1;
98
+ transform: none;
99
+ display: block;
100
+ }
101
+ }
102
+
103
+ // NOT work on FF
104
+ @keyframes close-dialog {
105
+ from {
106
+ opacity: 1;
107
+ transform: none;
108
+ display: block;
109
+ }
110
+
111
+ to {
112
+ opacity: 0;
113
+ transform: var(--dialog-close-transform, translateY(100px));
114
+ display: none;
115
+ }
116
+ }
@@ -0,0 +1,101 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Disclosure
4
+ /// ------------------------------------------------------------------
5
+ /// Create the disclosure component
6
+ ///
7
+ /// @example <details><summary>Click</summary><div>My disclosure</div></details>
8
+ ///
9
+ /// @require {mixin} as-item
10
+ /// @group components
11
+ /// @author Natacha Herth
12
+ /// @since 1.0.0
13
+ ///
14
+ ////
15
+
16
+ details {
17
+
18
+ > summary {
19
+
20
+ // Define as an item
21
+ @include as-item('disclosure', ('focus', 'hover'));
22
+
23
+ // Animate the SVG
24
+ @media screen and (prefers-reduced-motion: no-preference) {
25
+ > svg {
26
+ transition: var(--disclosure-transition, all .25s ease-in-out);
27
+ }
28
+ }
29
+
30
+ }
31
+
32
+ // Define as an item
33
+ > div {
34
+ @include as-item('disclosure');
35
+ }
36
+
37
+ &[open] summary {
38
+
39
+ // Define manually the :active state
40
+ color: var(--disclosure-active-color, var(--disclosure-color, var(--color-font)));
41
+ background-color: var(--disclosure-active-background, color-mix(in srgb, var(--disclosure-background, transparent), var(--active-color) var(--active-percent)));
42
+ border-color: var(--disclosure-active-border-color, var(--disclosure-border-color, transparent));
43
+
44
+ // Transform the SVG
45
+ > svg {
46
+ transform: var(--disclosure-svg-transform, rotate(90deg));
47
+ }
48
+
49
+ }
50
+
51
+ // Include the color variation on the parent
52
+ @include with-color-variations('disclosure');
53
+
54
+ }
55
+
56
+ .accordion {
57
+
58
+ // Remove extra border
59
+ > details + details > summary,
60
+ > details > div {
61
+ border-top: none !important;
62
+ }
63
+
64
+ // Remove extra radius
65
+ > details:not(:first-child, :last-child) > summary,
66
+ > details:not(:last-child) > div {
67
+ border-radius: 0;
68
+ }
69
+
70
+ // Remove radius on last element
71
+ > details:last-child[open] {
72
+
73
+ > summary {
74
+ border-end-start-radius: 0;
75
+ border-end-end-radius: 0;
76
+ }
77
+
78
+ > div {
79
+ border-start-start-radius: 0;
80
+ border-start-end-radius: 0;
81
+ }
82
+
83
+ }
84
+
85
+ > details:not(:only-child) {
86
+
87
+ // Remove radius bottom on first child
88
+ &:first-child > summary {
89
+ border-end-start-radius: 0;
90
+ border-end-end-radius: 0;
91
+ }
92
+
93
+ // Remove radius top on last child
94
+ &:last-child > summary {
95
+ border-start-start-radius: 0;
96
+ border-start-end-radius: 0;
97
+ }
98
+
99
+ }
100
+
101
+ }
@@ -0,0 +1,68 @@
1
+ ////
2
+ /// ------------------------------------------------------------------
3
+ /// Dropdown
4
+ /// ------------------------------------------------------------------
5
+ /// Create the dropdown component
6
+ ///
7
+ /// @example <div class="dropdown"><button aria-controls="myDropdown" aria-expanded="false" aria-pressed="false">Button</button><ul id="myDropdown" hidden>...</ul></div>
8
+ ///
9
+ /// @require {mixin} as-list
10
+ /// @group components
11
+ /// @author Natacha Herth
12
+ /// @since 1.0.0
13
+ ///
14
+ ////
15
+
16
+ @use "sass:map";
17
+
18
+ $custom: (
19
+ background: var(--color-body)
20
+ );
21
+
22
+ // Change default properties
23
+ $properties: map.merge($default-item-properties, $custom);
24
+
25
+
26
+ .dropdown {
27
+
28
+ position: relative;
29
+ display: inline-block;
30
+
31
+ > [aria-controls] {
32
+
33
+ width: 100%;
34
+ white-space: nowrap;
35
+
36
+ // Animate the SVG
37
+ @media screen and (prefers-reduced-motion: no-preference) {
38
+ > svg {
39
+ transition: var(--dropdown-transition, all .25s ease-in-out);
40
+ }
41
+ }
42
+
43
+ &[aria-expanded=true] > svg {
44
+ transform: var(--dropdown-svg-transform, rotate(-180deg));
45
+ }
46
+
47
+ }
48
+
49
+ > *:not([aria-controls]) {
50
+
51
+ // Make it overflow
52
+ position: absolute;
53
+ z-index: var(--dropdown-index, 1);
54
+ top: calc(100% + var(--dropdown-offset, .5rem));
55
+
56
+ // Simple bloc
57
+ &:not(ul, li) {
58
+ @include as-item('dropdown', (), $properties);
59
+ }
60
+
61
+ // As list
62
+ &:is(ul, ol) {
63
+ @include as-list('dropdown', ('focus', 'hover', 'active', 'disabled'), $properties);
64
+ }
65
+
66
+ }
67
+
68
+ }