@madgex/design-system 5.2.2 → 5.4.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/dist/_tokens/css/_tokens.css +1 -1
- package/dist/_tokens/js/_tokens-module.js +1 -1
- package/dist/_tokens/scss/_tokens.scss +1 -1
- package/dist/assets/icons.json +1 -1
- package/dist/css/index.css +1 -1
- package/dist/js/index.js +1 -1
- package/package.json +2 -2
- package/src/components/accordion/README.md +5 -0
- package/src/components/accordion/_template.njk +36 -16
- package/src/components/accordion/accordion.config.js +37 -4
- package/src/components/accordion/accordion.js +123 -36
- package/src/components/accordion/accordion.njk +6 -2
- package/src/components/inputs/input-number/README.md +37 -0
- package/src/components/inputs/input-number/input-number.config.js +5 -0
- package/src/components/inputs/input-number/input-number.njk +68 -0
- package/src/components/inputs/input-number/input-number.scss +60 -0
- package/src/scss/components/__index.scss +1 -0
|
@@ -5,6 +5,7 @@ const accordionTriggerExpandedClass = 'mds-accordion-trigger--expanded';
|
|
|
5
5
|
const accordionContentExpandedClass = 'mds-accordion-content--expanded';
|
|
6
6
|
const accordionLabelInverseData = 'data-labelinverse';
|
|
7
7
|
const accordionNonClosingClass = 'mds-accordion-trigger--non-closing';
|
|
8
|
+
const accordionContentFirstClass = 'mds-accordion-trigger--content-first';
|
|
8
9
|
|
|
9
10
|
const accordion = {
|
|
10
11
|
init: () => {
|
|
@@ -23,6 +24,7 @@ const accordion = {
|
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
},
|
|
27
|
+
|
|
26
28
|
checkBreakpoint: (element, screenWidth) => {
|
|
27
29
|
let breakpoint = 0;
|
|
28
30
|
|
|
@@ -62,32 +64,53 @@ const accordion = {
|
|
|
62
64
|
|
|
63
65
|
return true;
|
|
64
66
|
},
|
|
67
|
+
|
|
65
68
|
setAccordion: (accordionTrigger, accordionContent) => {
|
|
66
69
|
const triggerContainer = accordionTrigger;
|
|
67
|
-
|
|
68
|
-
const labelText = label.innerText;
|
|
69
|
-
const isNonClosing = accordionTrigger.classList.contains(accordionNonClosingClass);
|
|
70
|
+
|
|
70
71
|
const triggerButton = accordion.createButton(triggerContainer);
|
|
72
|
+
const triggerButtonLabel = triggerButton.querySelector(`.${accordionTriggerLabelClass}`);
|
|
73
|
+
|
|
74
|
+
const labelText = triggerButtonLabel.innerText;
|
|
75
|
+
const ariaLabel = triggerButton.getAttribute('aria-label');
|
|
76
|
+
const isNonClosing = accordionTrigger.classList.contains(accordionNonClosingClass);
|
|
77
|
+
|
|
78
|
+
// If accordion is non closing content first is redundant
|
|
79
|
+
const isContentFirst = accordionTrigger.classList.contains(accordionContentFirstClass) && !isNonClosing;
|
|
71
80
|
|
|
72
81
|
triggerContainer.classList.remove('mds-display-none');
|
|
73
82
|
if (accordionContent.nodeName.toLowerCase() !== 'fieldset') {
|
|
74
|
-
|
|
83
|
+
if (ariaLabel) {
|
|
84
|
+
accordionContent.setAttribute('aria-label', ariaLabel);
|
|
85
|
+
} else {
|
|
86
|
+
accordionContent.setAttribute('aria-label', labelText);
|
|
87
|
+
}
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
triggerContainer.innerHTML = '';
|
|
78
91
|
triggerContainer.appendChild(triggerButton);
|
|
79
92
|
|
|
93
|
+
const accordionProperties = {
|
|
94
|
+
triggerContainer,
|
|
95
|
+
accordionContent,
|
|
96
|
+
isNonClosing,
|
|
97
|
+
isContentFirst,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const toggleEvent = (e) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
accordion.toggleExpand(accordionProperties, e, toggleEvent);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
triggerButton.addEventListener('click', toggleEvent);
|
|
106
|
+
|
|
80
107
|
if (triggerContainer.dataset.expanded) {
|
|
81
|
-
accordion.toggleExpand(
|
|
108
|
+
accordion.toggleExpand(accordionProperties, toggleEvent);
|
|
82
109
|
}
|
|
83
|
-
|
|
84
|
-
triggerButton.addEventListener('click', (elem) => {
|
|
85
|
-
elem.preventDefault();
|
|
86
|
-
accordion.toggleExpand(triggerButton, triggerContainer, accordionContent, isNonClosing);
|
|
87
|
-
});
|
|
88
110
|
},
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
|
|
112
|
+
createButton: (triggerContainer) => {
|
|
113
|
+
const accordionTriggerSpan = triggerContainer.querySelector(`.${accordionTriggerClass} > span`);
|
|
91
114
|
const accordionTriggerButton = document.createElement('button');
|
|
92
115
|
|
|
93
116
|
accordionTriggerButton.className = accordionTriggerButtonClass;
|
|
@@ -97,50 +120,114 @@ const accordion = {
|
|
|
97
120
|
accordionTriggerButton.classList.add('mds-padding-x-b0');
|
|
98
121
|
accordionTriggerButton.setAttribute('aria-expanded', false);
|
|
99
122
|
accordionTriggerButton.setAttribute('type', 'button');
|
|
123
|
+
|
|
124
|
+
if (triggerContainer.dataset.arialabel) {
|
|
125
|
+
accordionTriggerButton.setAttribute('aria-label', triggerContainer.dataset.arialabel);
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
if (accordionTriggerSpan) {
|
|
101
129
|
accordionTriggerButton.appendChild(accordionTriggerSpan);
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
return accordionTriggerButton;
|
|
105
133
|
},
|
|
106
|
-
switchLabel: (e) => {
|
|
107
|
-
const element = e;
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
cloneUnderButton: (triggerButton, triggerContainer, accordionContent, toggleEvent) => {
|
|
136
|
+
const underButton = triggerButton.cloneNode(true);
|
|
137
|
+
const triggerButtonLabel = underButton.querySelector(`.${accordionTriggerLabelClass}`);
|
|
138
|
+
|
|
139
|
+
if (triggerContainer.dataset.arialabeactive) {
|
|
140
|
+
underButton.setAttribute('aria-label', triggerContainer.dataset.arialabeactive);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
underButton.setAttribute('aria-expanded', true);
|
|
144
|
+
underButton.addEventListener('click', toggleEvent);
|
|
145
|
+
accordion.switchAriaLabel(underButton, triggerContainer);
|
|
146
|
+
accordion.switchLabel(triggerButtonLabel);
|
|
147
|
+
accordionContent.after(underButton);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
switchLabel: (triggerButtonLabel) => {
|
|
151
|
+
if (triggerButtonLabel.dataset.labelinverse) {
|
|
152
|
+
const labelToInverse = triggerButtonLabel.textContent;
|
|
111
153
|
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
// eslint-disable-next-line no-param-reassign
|
|
155
|
+
triggerButtonLabel.textContent = triggerButtonLabel.dataset.labelinverse;
|
|
156
|
+
triggerButtonLabel.setAttribute(accordionLabelInverseData, labelToInverse);
|
|
114
157
|
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
switchAriaLabel: (triggerButton, triggerContainer) => {
|
|
161
|
+
if (triggerContainer.dataset.arialabelactive) {
|
|
162
|
+
const currentAriaLabel = triggerButton.getAttribute('aria-label');
|
|
115
163
|
|
|
116
|
-
|
|
164
|
+
if (currentAriaLabel === triggerContainer.dataset.arialabel) {
|
|
165
|
+
triggerButton.setAttribute('aria-label', triggerContainer.dataset.arialabelactive);
|
|
166
|
+
} else {
|
|
167
|
+
triggerButton.setAttribute('aria-label', triggerContainer.dataset.arialabel);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
117
170
|
},
|
|
118
|
-
toggleExpand: (triggerButton, triggerContainer, accordionContent, isNonClosing) => {
|
|
119
|
-
const accordionTriggerButton = triggerButton;
|
|
120
|
-
const accordionTriggerButtonLabel = triggerButton.querySelector(`.${accordionTriggerLabelClass}`);
|
|
121
171
|
|
|
122
|
-
|
|
123
|
-
|
|
172
|
+
toggleExpand: ({ triggerContainer, accordionContent, isNonClosing, isContentFirst }, event, toggleEvent) => {
|
|
173
|
+
const triggerButton = event.target.closest(`.${accordionTriggerButtonClass}`);
|
|
174
|
+
const triggerButtonLabel = triggerButton.querySelector(`.${accordionTriggerLabelClass}`);
|
|
175
|
+
|
|
176
|
+
// If closed
|
|
177
|
+
if (triggerButton.getAttribute('aria-expanded') === 'false') {
|
|
178
|
+
triggerButton.setAttribute('aria-expanded', true);
|
|
124
179
|
triggerContainer.classList.add(accordionTriggerExpandedClass);
|
|
125
180
|
accordionContent.classList.add(accordionContentExpandedClass);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
accordionTriggerButton.parentElement.classList.add('mds-visually-hidden');
|
|
131
|
-
accordionTriggerButton.setAttribute('aria-disabled', true);
|
|
132
|
-
/* the disabled trigger shouldn't be reachable to keyboard navigaton (as it's hidden from view)
|
|
133
|
-
* so adding tabindex='-1' to take it off the navigation flow
|
|
134
|
-
*/
|
|
135
|
-
accordionTriggerButton.setAttribute('tabIndex', '-1');
|
|
181
|
+
|
|
182
|
+
if (isContentFirst) {
|
|
183
|
+
// triggerButton.focus();
|
|
184
|
+
accordion.cloneUnderButton(triggerButton, triggerContainer, accordionContent, toggleEvent);
|
|
136
185
|
}
|
|
186
|
+
|
|
187
|
+
if (isNonClosing || isContentFirst) {
|
|
188
|
+
accordion.hideButton(triggerButton, toggleEvent);
|
|
189
|
+
} else {
|
|
190
|
+
accordion.switchLabel(triggerButtonLabel);
|
|
191
|
+
accordion.switchAriaLabel(triggerButton, triggerContainer);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// If open
|
|
137
195
|
} else if (!isNonClosing) {
|
|
138
|
-
|
|
196
|
+
triggerButton.setAttribute('aria-expanded', false);
|
|
139
197
|
triggerContainer.classList.remove(accordionTriggerExpandedClass);
|
|
140
198
|
accordionContent.classList.remove(accordionContentExpandedClass);
|
|
199
|
+
|
|
200
|
+
accordion.switchLabel(triggerButtonLabel);
|
|
201
|
+
accordion.switchAriaLabel(triggerButton, triggerContainer);
|
|
202
|
+
|
|
203
|
+
if (isContentFirst) {
|
|
204
|
+
// Find original button
|
|
205
|
+
const overButton = accordionContent.previousElementSibling.querySelector(`.${accordionTriggerButtonClass}`);
|
|
206
|
+
|
|
207
|
+
// Set up under button with correct properties
|
|
208
|
+
triggerButton.setAttribute('aria-expanded', false);
|
|
209
|
+
triggerButton.removeAttribute('aria-disabled');
|
|
210
|
+
triggerButton.setAttribute('tabIndex', '0');
|
|
211
|
+
|
|
212
|
+
// Replace original button
|
|
213
|
+
overButton.replaceWith(triggerButton);
|
|
214
|
+
triggerContainer.classList.remove('mds-visually-hidden');
|
|
215
|
+
triggerButton.focus();
|
|
216
|
+
}
|
|
141
217
|
}
|
|
218
|
+
},
|
|
142
219
|
|
|
143
|
-
|
|
220
|
+
hideButton: (button, toggleEvent) => {
|
|
221
|
+
/* Hiding parent element instead of trigger itself
|
|
222
|
+
* to avoid NVDA issue where it doesn't announce aria-expanded
|
|
223
|
+
*/
|
|
224
|
+
button.setAttribute('aria-disabled', true);
|
|
225
|
+
button.parentElement.classList.add('mds-visually-hidden');
|
|
226
|
+
/* the disabled trigger shouldn't be reachable to keyboard navigaton (as it's hidden from view)
|
|
227
|
+
* so adding tabindex='-1' to take it off the navigation flow
|
|
228
|
+
*/
|
|
229
|
+
button.setAttribute('tabIndex', '-1');
|
|
230
|
+
button.removeEventListener('click', toggleEvent);
|
|
144
231
|
},
|
|
145
232
|
};
|
|
146
233
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
triggerLabel: triggerLabel,
|
|
8
8
|
triggerLabelActive: triggerLabelActive,
|
|
9
9
|
triggerNoJsHidden: triggerNoJsHidden,
|
|
10
|
-
|
|
10
|
+
triggerHtmlTag: triggerHtmlTag,
|
|
11
11
|
triggerClasses: triggerClasses,
|
|
12
12
|
triggerIsNonClosing: triggerIsNonClosing,
|
|
13
13
|
hideTriggerLabel: hideTriggerLabel,
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
leftAligned: leftAligned,
|
|
17
17
|
expandIcon: expandIcon,
|
|
18
18
|
collapseIcon: collapseIcon,
|
|
19
|
-
isExpanded: isExpanded
|
|
19
|
+
isExpanded: isExpanded,
|
|
20
|
+
noIcon: noIcon,
|
|
21
|
+
contentFirst: contentFirst,
|
|
22
|
+
ariaLabel: ariaLabel,
|
|
23
|
+
ariaLabelActive: ariaLabelActive
|
|
20
24
|
}) }}
|
|
21
25
|
<hr />
|
|
22
26
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## Vue
|
|
2
|
+
|
|
3
|
+
### Parameters
|
|
4
|
+
|
|
5
|
+
- `v-model (:modelValue)`: **required** The value of the input.
|
|
6
|
+
- `id`: `String` **required** - Id attribute of the input.
|
|
7
|
+
- `name`: `String` _optional_ - Name attribute of the input.
|
|
8
|
+
- `placeholder`: `String` _optional_ - Placeholder attribute of the input element. Default: `''`.
|
|
9
|
+
- `min`: `Number` _optional_ - Min attribute of the input element. Default: `Number.NEGATIVE_INFINITY`.
|
|
10
|
+
- `max`: `Number` _optional_ - Max attribute of the input element. Default: `Number.POSITIVE_INFINITY`.
|
|
11
|
+
- `step`: `Number` _optional_ - Step attribute of input element. Default: `1`.
|
|
12
|
+
- `error`: `Boolean` _optional_ - Used for a11y (`:aria-invalid` attribute). Default: `false`.
|
|
13
|
+
- `i18n`: `Object` _optional_ - Text to translate/customise.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
i18n: {
|
|
17
|
+
increaseAriaLabel: 'Increase', // aria label for "Increase" button
|
|
18
|
+
decreaseAriaLabel: 'Decrease', // aria label for "Decrease" button
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### i18n
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<mds-form-item input-id="my-input-number-id" class="mds-form-element--input">
|
|
26
|
+
<template #label>¿Cuántos idiomas hablas?</template>
|
|
27
|
+
<mds-input-number
|
|
28
|
+
id="my-input-number-id"
|
|
29
|
+
v-model="value"
|
|
30
|
+
min="1"
|
|
31
|
+
max="50"
|
|
32
|
+
:i18="{increaseAriaLabel: 'Aumentar', decreaseAriaLabel: 'Reducir'}"
|
|
33
|
+
/>
|
|
34
|
+
</mds-form-item>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> Note: Remember to wrap your input components with `mds-form-item` whenever you need to use them within a form.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{# VUE VERSION #}
|
|
2
|
+
<h2>Vue</h2>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<div id="vue-app">
|
|
6
|
+
{% raw %}
|
|
7
|
+
<mds-form-item v-for="(item, index) in examples"
|
|
8
|
+
:input-id="`${index}-vue-version`"
|
|
9
|
+
:optional="item.optional"
|
|
10
|
+
:error="item.value ? !!item.error : null"
|
|
11
|
+
class="mds-form-element--input"
|
|
12
|
+
style="margin-bottom: 50px; width: 230px;"
|
|
13
|
+
>
|
|
14
|
+
<template v-if="item.label" #label>{{item.label}}</template>
|
|
15
|
+
<template v-if="item.tooltip" #tooltip>{{item.tooltip}}</template>
|
|
16
|
+
<template v-if="item.value && item.help" #help>{{item.help}}</template>
|
|
17
|
+
<template v-if="item.value && item.error" #error>{{item.error}}</template>
|
|
18
|
+
<mds-input-number
|
|
19
|
+
:id="`${index}-vue-version`"
|
|
20
|
+
v-model="item.value"
|
|
21
|
+
:name="item.name"
|
|
22
|
+
:placeholder="item.placeholder"
|
|
23
|
+
:error="item.value ? !!item.error : null"
|
|
24
|
+
:step="item.step"
|
|
25
|
+
:min="item.min"
|
|
26
|
+
:max="item.max"
|
|
27
|
+
:i18n="item.i18n"
|
|
28
|
+
/>
|
|
29
|
+
</mds-form-item>
|
|
30
|
+
{% endraw %}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<script type="module" async>
|
|
34
|
+
{% include '_import-MdsLibrary.njk' %}
|
|
35
|
+
const { MdsFormItem, MdsInput, MdsInputNumber } = MdsLibrary;
|
|
36
|
+
|
|
37
|
+
const app = vue.createApp({
|
|
38
|
+
components: { MdsFormItem, MdsInput, MdsInputNumber },
|
|
39
|
+
data() {
|
|
40
|
+
return {
|
|
41
|
+
examples: [
|
|
42
|
+
{label: 'How many JS FE frameworks exist?', name: 'default input', value: null},
|
|
43
|
+
{label: 'When did time begin?', name: 'stars input', value: 1970, optional: true, min:0, max:2000},
|
|
44
|
+
{
|
|
45
|
+
label: 'Expected salary per year',
|
|
46
|
+
name: 'salary input error',
|
|
47
|
+
error: 'Sorry, your expected salary is too high',
|
|
48
|
+
help: "This number is out of our budget",
|
|
49
|
+
tooltip: "Try working for free",
|
|
50
|
+
min: 80000,
|
|
51
|
+
value: null,
|
|
52
|
+
placeholder: 'e.g. $15000',
|
|
53
|
+
step: 100
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: '¿Cuántos idiomas hablas?',
|
|
57
|
+
optional: true,
|
|
58
|
+
name: 'i18n example',
|
|
59
|
+
min: 1,
|
|
60
|
+
max: 50,
|
|
61
|
+
value: null,
|
|
62
|
+
i18n: {increaseAriaLabel: 'Aumentar', decreaseAriaLabel: 'Reducir'}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
}).mount('#vue-app');
|
|
68
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.mds-input-number {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
border: $mds-size-border-width-base solid $mds-color-neutral-lighter;
|
|
5
|
+
border-radius: $mds-size-border-radius-base;
|
|
6
|
+
background: $mds-color-neutral-white;
|
|
7
|
+
padding: 0;
|
|
8
|
+
color: $mds-color-text-base;
|
|
9
|
+
width: 100%;
|
|
10
|
+
min-width: $mds-size-container-min-width;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.mds-form-element--error .mds-form-control.mds-input-number {
|
|
14
|
+
padding: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.mds-input-number input {
|
|
18
|
+
flex: 1;
|
|
19
|
+
padding: $mds-size-baseline * 3;
|
|
20
|
+
line-height: inherit;
|
|
21
|
+
font-size: inherit;
|
|
22
|
+
color: inherit;
|
|
23
|
+
border: $mds-size-border-width-base solid $mds-color-neutral-lighter;
|
|
24
|
+
border-top: none;
|
|
25
|
+
border-bottom: none;
|
|
26
|
+
background: none;
|
|
27
|
+
outline: none;
|
|
28
|
+
appearance: none;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
-webkit-appearance: none;
|
|
31
|
+
-moz-appearance: textfield;
|
|
32
|
+
|
|
33
|
+
&::-webkit-outer-spin-button,
|
|
34
|
+
&::-webkit-inner-spin-button {
|
|
35
|
+
appearance: none;
|
|
36
|
+
-webkit-appearance: none;
|
|
37
|
+
-moz-appearance: none;
|
|
38
|
+
margin: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.mds-input-number button {
|
|
43
|
+
padding: ($mds-size-baseline * 2) ($mds-size-baseline * 3);
|
|
44
|
+
border: none;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
background: none;
|
|
47
|
+
color: inherit;
|
|
48
|
+
outline: none;
|
|
49
|
+
|
|
50
|
+
&:hover,
|
|
51
|
+
&:focus-within {
|
|
52
|
+
background: $mds-color-neutral-lightest;
|
|
53
|
+
color: $mds-color-link-hover;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&[aria-disabled='true'] {
|
|
57
|
+
cursor: not-allowed;
|
|
58
|
+
opacity: 20%;
|
|
59
|
+
}
|
|
60
|
+
}
|