@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.
@@ -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
- const label = triggerContainer.querySelector('.mds-accordion-trigger__label');
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
- accordionContent.setAttribute('aria-label', labelText);
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(triggerButton, triggerContainer, accordionContent, isNonClosing);
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
- createButton: (element) => {
90
- const accordionTriggerSpan = element.querySelector(`.${accordionTriggerClass} > span`);
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
- if (element.dataset.labelinverse) {
110
- const labelToInverse = element.textContent;
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
- element.textContent = element.dataset.labelinverse;
113
- element.setAttribute(accordionLabelInverseData, labelToInverse);
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
- return element;
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
- if (accordionTriggerButton.getAttribute('aria-expanded') === 'false') {
123
- accordionTriggerButton.setAttribute('aria-expanded', true);
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
- if (isNonClosing) {
127
- /* Hiding parent element instead of trigger itself
128
- * to avoid NVDA issue where it doesn't announce aria-expanded
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
- accordionTriggerButton.setAttribute('aria-expanded', false);
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
- accordion.switchLabel(accordionTriggerButtonLabel);
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
- triggerIsHeader: triggerIsHeader,
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,5 @@
1
+ module.exports = {
2
+ title: 'InputNumber',
3
+ meta: { status: { njk: '', vue: 'wip' } },
4
+ context: {},
5
+ };
@@ -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
+ }
@@ -15,3 +15,4 @@
15
15
  @import '../../components/inputs/checkbox-pill/checkbox-pill';
16
16
  @import '../../components/modal/modal';
17
17
  @import '../../components/skip-link/skip-link';
18
+ @import '../../components/inputs/input-number/input-number';