@madgex/design-system 10.1.3 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,3 +137,6 @@ await styleDictionary.buildPlatform('json-variables');
137
137
 
138
138
  await cleanTempFiles();
139
139
  ```
140
+
141
+ \_
142
+ \_
@@ -1 +1 @@
1
- [{"name":"asterisk"},{"name":"calendar"},{"name":"cart"},{"name":"chevron-down"},{"name":"check"},{"name":"chevron-left"},{"name":"chevron-right"},{"name":"chevron-up"},{"name":"close"},{"name":"cross"},{"name":"doc-pdf"},{"name":"doc"},{"name":"email"},{"name":"external"},{"name":"flag"},{"name":"information"},{"name":"job"},{"name":"list-bullets"},{"name":"list-numbers"},{"name":"location-pin"},{"name":"menu"},{"name":"minus"},{"name":"plus-small"},{"name":"plus"},{"name":"question-mark"},{"name":"redo"},{"name":"search"},{"name":"settings"},{"name":"share"},{"name":"social-facebook"},{"name":"social-linkedin"},{"name":"social-pinterest"},{"name":"social-reddit"},{"name":"social-twitter"},{"name":"spinner"},{"name":"star-fill"},{"name":"star-outline"},{"name":"text-bold"},{"name":"text-italic"},{"name":"text-link"},{"name":"text-strike-through"},{"name":"text-underline"},{"name":"triangle-down"},{"name":"triangle-right"},{"name":"triangle-up"},{"name":"undo"},{"name":"upload"},{"name":"user"}]
1
+ [{"name":"asterisk"},{"name":"calendar"},{"name":"cart"},{"name":"check"},{"name":"chevron-down"},{"name":"chevron-left"},{"name":"chevron-right"},{"name":"chevron-up"},{"name":"close"},{"name":"cross"},{"name":"doc-pdf"},{"name":"doc"},{"name":"email"},{"name":"external"},{"name":"flag"},{"name":"information"},{"name":"job"},{"name":"list-bullets"},{"name":"list-numbers"},{"name":"location-pin"},{"name":"menu"},{"name":"minus"},{"name":"plus-small"},{"name":"plus"},{"name":"question-mark"},{"name":"redo"},{"name":"search"},{"name":"settings"},{"name":"share"},{"name":"social-facebook"},{"name":"social-linkedin"},{"name":"social-pinterest"},{"name":"social-reddit"},{"name":"social-twitter"},{"name":"spinner"},{"name":"star-fill"},{"name":"star-outline"},{"name":"text-bold"},{"name":"text-italic"},{"name":"text-link"},{"name":"text-strike-through"},{"name":"text-underline"},{"name":"triangle-down"},{"name":"triangle-right"},{"name":"triangle-up"},{"name":"undo"},{"name":"upload"},{"name":"user"}]
@@ -1 +1 @@
1
- (()=>{"use strict";const e=e=>{e.classList.remove("mds-switch-state--default"),e.classList.add("mds-switch-state--inverse")},t=e=>{e.classList.remove("mds-switch-state--inverse"),e.classList.add("mds-switch-state--default")},o=()=>{Array.from(document.querySelectorAll(".js-mds-switch-state")).forEach(o=>{o.addEventListener("click",o=>{o.preventDefault(),o.stopPropagation();const s=o.currentTarget;s.classList.contains("mds-switch-state--default")?e(s):(s.classList.contains("mds-switch-state--inverse"),t(s))})})},s="mds-notification",n={init:(e,t,o=3e3)=>{const{body:c}=document;n.hide(c);const a=document.createElement("div");a.setAttribute("role","alert"),a.classList.add(s,"mds-message",`mds-message--${t}`),a.innerText=e,n.show(c,a),setTimeout(n.hide,o,c)},show:(e,t)=>{e.appendChild(t)},hide:e=>{Array.from(e.querySelectorAll(`.${s}`)).forEach(e=>{e.parentNode.removeChild(e)})}},c=n,a=()=>{Array.from(document.querySelectorAll(".js-notification-example")).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const t=e.currentTarget,o=t.innerText,{messageType:s}=t.dataset;c.init(o,s)})})},i="mds-combobox",l=()=>{!function(){const e=document.querySelector('[data-combobox-id="distance-selection"]');if(e){const t=e.querySelector("select"),o=Array.from(t.querySelectorAll("option"));e.querySelector(i).options=o.slice(1).map(e=>({value:e.value,label:e.textContent}))}}(),function(){const e=document.querySelector('[data-combobox-id="salary-selection"]');if(e){const t=e.querySelector("select"),o=Array.from(t.querySelectorAll("option"));e.querySelector(i).options=o.slice(1).map(e=>({value:e.value,label:e.textContent}))}}(),function(){const e=document.querySelector('[data-combobox-id="salary-selection"] mds-combobox'),t=document.querySelector("#salary-selection-hidden-input");e&&t&&e.addEventListener("select-option",()=>{console.log("Setting #salary-selection-hidden-input.."),t.value="flip"===t.value?"flop":"flip"})}(),function(){const e=document.querySelector('[data-combobox-id="salary-selection"] mds-combobox'),t=document.querySelector("#salary-selection-hidden-input");e&&t&&e.addEventListener("clear-all",()=>{console.log("Clearing #salary-selection-hidden-input.."),t.value=""})}(),function(){const e=document.querySelector('[data-combobox-id="keywords-lookup"]');if(e){const t=e.querySelector(i);t.filterOptions=!1,t.addEventListener("search",e=>{const[o]=e.detail;o&&o.length>2?fetch(`https://api.datamuse.com/sug?s=${o}`).then(e=>e.json()).then(e=>{const o=e.map(({word:e})=>({value:e,label:e}));return t.options=o,o}).catch(console.log):t.options=[]})}}()};document.addEventListener("DOMContentLoaded",()=>{o(),a(),setTimeout(()=>{l()},1e3)})})();
1
+ (()=>{"use strict";const e=e=>{e.classList.remove("mds-switch-state--default"),e.classList.add("mds-switch-state--inverse")},t=e=>{e.classList.remove("mds-switch-state--inverse"),e.classList.add("mds-switch-state--default")},s=()=>{Array.from(document.querySelectorAll(".js-mds-switch-state")).forEach(s=>{s.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation();const a=s.currentTarget;a.classList.contains("mds-switch-state--default")?e(a):(a.classList.contains("mds-switch-state--inverse"),t(a))})})},a="mds-notification",i={init:(e,t,s=3e3)=>{const{body:r}=document;i.hide(r);const n=document.createElement("div");n.setAttribute("role","alert"),n.classList.add(a,"mds-message",`mds-message--${t}`),n.innerText=e,i.show(r,n),setTimeout(i.hide,s,r)},show:(e,t)=>{e.appendChild(t)},hide:e=>{Array.from(e.querySelectorAll(`.${a}`)).forEach(e=>{e.parentNode.removeChild(e)})}},r=i,n=()=>{Array.from(document.querySelectorAll(".js-notification-example")).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const t=e.currentTarget,s=t.innerText,{messageType:a}=t.dataset;r.init(s,a)})})};document.addEventListener("DOMContentLoaded",()=>{s(),n()})})();
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@madgex/design-system",
3
3
  "author": "Madgex",
4
4
  "license": "UNLICENSED",
5
- "version": "10.1.3",
5
+ "version": "12.0.0",
6
6
  "main": "dist/js/index.js",
7
7
  "exports": {
8
8
  ".": "./dist/js/index.js",
@@ -5,14 +5,9 @@ Use the [callable](https://mozilla.github.io/nunjucks/templating.html#call) synt
5
5
 
6
6
  ## Parameters
7
7
 
8
- - `value`: Value attribute on the button or input
9
- - `name`: add `name` attribute
10
- - `element`: we're using `<button>` by default but you can change to be a link or an input
11
- - `href`: if `element` is a link then add the url using `href`
8
+ - `href`: this sets the href and changes the button to an anchor element
12
9
  - `classes`: css classes to add on the button (for example `mds-width-full`)
13
- - `disabled`: disable button
14
10
  - `attributes`: you can add extra attributes by passing an object to the parameter. Example: `attributes: { attribute-name: 'attribute-value' }`
15
- - `type`: by default it will be `submit`
16
11
 
17
12
  ## Full width button
18
13
 
@@ -1,40 +1,11 @@
1
- {# Determine type of element to use, if not explicitly set -#}
1
+ {% from "../../sub-components/attributes/macro.njk" import MdsAttributes %}
2
2
 
3
- {%- if params.element -%}
4
- {% set element = params.element | lower %}
5
- {%- else -%}
6
- {% if params.href %}
7
- {% set element = 'a' %}
8
- {% else %}
9
- {% set element = 'button' %}
10
- {% endif %}
11
- {%- endif -%}
12
-
13
- {#- Define common attributes that we can use across all element types #}
14
-
15
- {%- set commonAttributes %} class="mds-button
16
- {%- if params.classes %} {{ params.classes }}{% endif -%}
17
- {%- if params.disabled %} mds-button--disabled{% endif %}"
18
- {%- for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %} data-test="button{% if params.id %}-{{params.id}}{% endif %}"
19
- {%- endset -%}
20
-
21
- {#- Define common attributes we can use for both button and input types #}
22
-
23
- {%- set buttonAttributes %}
24
- {% if params.name %} name="{{ params.name }}"{% endif %} type="{{ params.type if params.type else 'submit' }}"{% if params.disabled %} disabled="disabled" aria-disabled="true"{% endif %}{% endset %}
25
-
26
- {#- Actually create a button... or a link! #}
27
-
28
- {%- if element == 'a' %}
29
- <a href="{{ params.href if params.href else '#' }}" draggable="false" {{- commonAttributes | safe }}>
30
- {{ caller() }}
3
+ {%- if params.href -%}
4
+ <a href="{{ params.href }}" class="mds-button {{ params.classes }}" {{- MdsAttributes(params.attributes) -}}>
5
+ {{- caller() -}}
31
6
  </a>
32
-
33
- {%- elseif element == 'button' %}
34
- <button {%- if params.value %} value="{{ params.value }}"{% endif %} {{- buttonAttributes | safe }} {{- commonAttributes | safe }}>
35
- {{ caller() }}
7
+ {%- else -%}
8
+ <button class="mds-button {{ params.classes }}" {{- MdsAttributes(params.attributes) -}}>
9
+ {{- caller() -}}
36
10
  </button>
37
-
38
- {%- elseif element == 'input' %}
39
- <input value="{{ params.value }}" {{- buttonAttributes | safe }} {{- commonAttributes | safe }}>
40
- {%- endif %}
11
+ {%- endif -%}
@@ -3,62 +3,43 @@ module.exports = {
3
3
  status: 'wip',
4
4
  context: {
5
5
  items: [
6
- { name: 'link-buttons', href: 'http://madgex.com', text: 'Link button 1', id: 'link-1' },
7
6
  {
8
- name: 'link-buttons',
9
7
  href: 'http://madgex.com',
10
- text: 'Link Plain button 2',
11
- id: 'link-2',
12
- classes: 'mds-button--plain',
8
+ text: 'Link button 1',
13
9
  },
14
10
  {
15
- name: 'input-buttons',
16
- element: 'input',
17
- value: 'Input button 3',
18
- id: 'link-3',
11
+ href: 'http://madgex.com',
12
+ text: 'Link Plain button 2',
13
+ classes: 'mds-button--plain',
19
14
  },
20
15
  {
21
- name: 'plain-buttons',
22
16
  text: 'Plain button',
23
17
  classes: 'mds-button--plain',
24
- id: 'plain',
25
18
  },
26
19
  {
27
- name: 'small-buttons',
28
20
  text: 'Small button',
29
21
  classes: 'mds-button--small',
30
- id: 'small',
31
22
  },
32
23
  {
33
- name: 'large-buttons',
34
24
  text: 'Large button',
35
25
  classes: 'mds-button--large',
36
- id: 'large',
37
26
  },
38
27
  {
39
- name: 'icon-buttons',
40
- useIcon: true,
41
- classes: 'mds-button--icon',
42
- id: 'icon',
43
- },
44
- {
45
- name: 'full-width-example',
46
28
  text: 'Full width example',
47
29
  classes: 'mds-width-full mds-width-md-auto',
48
- id: 'full',
49
30
  },
50
31
  {
51
- name: 'neutral',
52
32
  text: 'Neutral',
53
33
  classes: 'mds-button--neutral',
54
- id: 'neutral',
55
34
  },
56
35
  {
57
- name: 'prevent double submit',
58
36
  text: 'Prevent double submit',
59
- id: 'double-submit',
60
37
  classes: 'js-mds-button-double-submit',
61
38
  },
39
+ {
40
+ text: 'With attributes',
41
+ attributes: { attributed: 'yes', 'other-thing': 'also' },
42
+ },
62
43
  ],
63
44
  },
64
45
  };
@@ -1,41 +1,14 @@
1
1
 
2
2
  {% from "./button/_macro.njk" import MdsButton %}
3
- {% from "./icons/_macro.njk" import MdsIcon %}
4
3
 
5
4
  {% for item in items %}
6
-
7
- {% if item.useIcon %}
8
- {%- set html -%}
9
- {{ MdsIcon({
10
- iconName: 'search',
11
- classes: 'mds-icon--md',
12
- visuallyHiddenLabel: 'Search icon'
13
- }) }}
14
- {%- endset -%}
15
- {% call MdsButton({
16
- element: item.element,
17
- id: item.id,
18
- value: item.value,
19
- href: item.href,
20
- classes: item.classes,
21
- attributes: item.attributes
22
- }) -%}
23
- <!-- example - in normal use `safe` is discouraged as it is dangerous, and not needed -->
24
- {{ html | safe }}
25
- {%- endcall %}
26
- {% else %}
27
5
  {% call MdsButton({
28
- element: item.element,
29
- id: item.id,
30
- value: item.value,
31
6
  href: item.href,
32
7
  classes: item.classes,
33
8
  attributes: item.attributes
34
9
  }) -%}
35
10
  {{ item.text }}
36
11
  {%- endcall %}
37
- {% endif %}
38
-
39
12
  {% endfor %}
40
13
 
41
14
 
@@ -3,7 +3,11 @@
3
3
  - `id`: the id of your combobox
4
4
  - `name`: the name of the input for form submission. Uses ID unless specified - _optional_
5
5
  - `labelText`: the text used in the label
6
- - `options`: a json object of key, value pairs e.g { 45: 'Orange' }. To be used when falling back to a native select if Javascript is not available
6
+ - `options`: a JSON array, e.g `[{ label: 'Orange', value: 45 }]`. Populates both mds-combobox, and fallback select options
7
+ - `apiUrl`: when populated, `options` is ignored and data is fetching from an API URL instead
8
+ - `apiQueryKey`: the query paramter name added to `apiUrl` - _optional_ (defaults to 'searchText')
9
+ - `apiOptionsPath`: where to grab an array of options on api response, e.g. `data.options` would be an array of options. _optional_ . leave undefined to use root api response as array
10
+ - `optionLabelPath`: where to grab the visual label propertly from the option object e.g. 'label' or 'title' or 'nested.object.label' _optional_ (defaults to `label`)
7
11
  - `value`: a default selected value for the input - _optional_
8
12
  - `fallbackTo`: the form element to use as a fallback. Should be either 'select' or 'input'
9
13
  - `placeholder`: the placeholder for your input (defaults to 'Please select')
@@ -29,13 +33,31 @@ i18n: {
29
33
  }
30
34
  ```
31
35
 
32
- ## Events
36
+ ## Updating hidden input values
33
37
 
34
- The following events are emiited.
38
+ When a user selects an option, any inputs passed inside MdsCombobox with the data attribute `data-key` will be populated with values from that chosen option.
35
39
 
36
- - `select-option`
37
- - `search`
38
- - `clear-all`
40
+ ### Example 1
41
+
42
+ Populate the hidden input with the `value` property from the selected option `{label:"my label", value: 56}`.
43
+
44
+ ```njk
45
+ {\% call MdsCombobox({...}) \%}
46
+ <input type="hidden" data-key="value" /> <!-- value will be 56 -->
47
+ {\% endcall \%}
48
+ ```
49
+
50
+ ### Example 2
51
+
52
+ Populate the hidden input with the `nested.thing` property from the selected option `{label:"my label", nested: {thing: 989 }, lat: "56.202", lon: '0.142'}`.
53
+
54
+ ```njk
55
+ {\% call MdsCombobox({...}) \%}
56
+ <input type="hidden" data-key="nested.thing" /> <!-- value will be 989 -->
57
+ <input type="hidden" data-key="lat" /> <!-- value will be 56.202 -->
58
+ <input type="hidden" data-key="lon" /> <!-- value will be 0.142 -->
59
+ {\% endcall \%}
60
+ ```
39
61
 
40
62
  ## Accessibility
41
63
 
@@ -52,47 +52,58 @@
52
52
  validationErrorId: validationErrorId,
53
53
  validationError: params.validationError
54
54
  }) }}
55
- <div class="mds-form-element__fallback">
56
- {% if params.fallbackTo === 'select' and params.options %}
57
- <select
58
- class="mds-form-control"
59
- id="{{ comboboxId }}"
60
- name="{{ comboboxName }}"
61
- value="{{ params.defaultValue|default('') }}"
62
- {% if ariaDescribedBy %}aria-describedby="{{ariaDescribedBy}}"{% endif %}
63
- >
64
- <option>{{ placeholder }}</option>
65
- {%- if params.options -%}
66
- {%- for value, option in params.options -%}
67
- <option value="{{ value }}">{{ option }}</option>
68
- {%- endfor -%}
69
- {%- endif -%}
70
- </select>
71
- {% elseif params.fallbackTo === 'input' %}
72
- <input
73
- class="mds-form-control"
74
- type="text"
75
- name="{{ comboboxName }}"
76
- autocomplete="off"
77
- id="{{ comboboxId }}"
78
- value="{{ params.value|default('') }}"
79
- placeholder="{{ placeholder }}"
80
- {% if params.validationError %}aria-invalid="true"{% endif %}
81
- {% if ariaDescribedBy %}aria-describedby="{{ariaDescribedBy}}"{% endif %}
82
- />
83
- {% endif %}
84
- </div>
85
55
  {# Leave the custom element at the bottom so it has access to the above elements on render #}
86
56
  <mds-combobox
87
57
  comboboxid="{{ comboboxId }}"
88
58
  placeholder="{{ params.placeholder }}"
89
59
  iconpath="{{ defaultIconPath }}"
60
+ {% if params.options.length %}options="{{params.options | dump}}"{% endif %}
61
+ {% if params.apiUrl %}api-url="{{params.apiUrl}}"{% endif%}
62
+ {% if params.apiQueryKey %}api-query-key="{{params.apiQueryKey}}"{% endif%}
63
+ {% if params.apiOptionsPath %}api-options-path="{{params.apiOptionsPath}}"{% endif%}
64
+ {% if params.optionLabelPath %}option-label-path="{{params.optionLabelPath}}"{% endif%}
90
65
  i18n="{{ params.i18n | dump }}"
91
66
  data-aria-invalid="{{ params.validationError }}"
92
67
  {% if params.minSearchCharacters %}min-search-characters="{{ params.minSearchCharacters }}"{% endif %}
93
68
  {% if params.fallbackTo === 'input' %}name="{{ comboboxName }}"{% endif %}
94
69
  {% if params.value %}value="{{ params.value }}"{% endif %}
95
70
  {% if ariaDescribedBy %} describedby-id="{{ariaDescribedBy}}"{% endif -%}
96
- ></mds-combobox>
71
+ >
72
+ <div class="mds-form-element__fallback">
73
+ {% if params.fallbackTo === 'select' and params.options %}
74
+ <select
75
+ class="mds-form-control"
76
+ id="{{ comboboxId }}"
77
+ name="{{ comboboxName }}"
78
+ value="{{ params.defaultValue|default('') }}"
79
+ {% if ariaDescribedBy %}aria-describedby="{{ariaDescribedBy}}"{% endif %}
80
+ >
81
+ <option>{{ placeholder }}</option>
82
+ {%- if params.options -%}
83
+ {%- for option in params.options -%}
84
+ <option value="{{ option.value }}">{{ option.label }}</option>
85
+ {%- endfor -%}
86
+ {%- endif -%}
87
+ </select>
88
+ {% elseif params.fallbackTo === 'input' %}
89
+ <input
90
+ class="mds-form-control"
91
+ type="text"
92
+ name="{{ comboboxName }}"
93
+ autocomplete="off"
94
+ id="{{ comboboxId }}"
95
+ value="{{ params.value|default('') }}"
96
+ placeholder="{{ placeholder }}"
97
+ {% if params.validationError %}aria-invalid="true"{% endif %}
98
+ {% if ariaDescribedBy %}aria-describedby="{{ariaDescribedBy}}"{% endif %}
99
+ />
100
+ {% endif %}
101
+ </div>
102
+ {% if caller %}
103
+ <span slot="target-inputs">
104
+ {{- caller() -}}
105
+ </span>
106
+ {% endif %}
107
+ </mds-combobox>
97
108
  </div>
98
109
  {%- endif -%}
@@ -1,5 +1,9 @@
1
- const optionsSalary = { ...new Array(20).fill().map((_, index) => `Up to £${10 + index * 10}k`) };
2
- const optionsDistance = { ...new Array(20).fill().map((_, index) => `Within ${5 * (1 + index)} miles`) };
1
+ const optionsSalary = new Array(20)
2
+ .fill()
3
+ .map((_, index) => ({ label: `Up to £${10 + index * 10}k`, value: 10 + index * 10 }));
4
+ const optionsDistance = new Array(20)
5
+ .fill()
6
+ .map((_, index) => ({ label: `Within ${5 * (1 + index)} miles`, value: 5 * (1 + index) }));
3
7
 
4
8
  module.exports = {
5
9
  title: 'Combobox',
@@ -58,6 +62,10 @@ module.exports = {
58
62
  placeholder: 'eg. Web developer',
59
63
  fallbackTo: 'input',
60
64
  minSearchCharacters: 3,
65
+ apiUrl: 'https://api.datamuse.com/sug',
66
+ apiQueryKey: 's',
67
+ apiOptionsPath: undefined,
68
+ optionLabelPath: 'word',
61
69
  i18n: {
62
70
  requiredIcon: 'Required (test i18n)',
63
71
  loadingText: 'Loading (test i18n)',
@@ -82,6 +90,10 @@ module.exports = {
82
90
  vModel: 'Initial Value',
83
91
  fallbackTo: 'input',
84
92
  minSearchCharacters: 3,
93
+ apiUrl: 'https://api.datamuse.com/sug',
94
+ apiQueryKey: 's',
95
+ apiOptionsPath: undefined,
96
+ optionLabelPath: 'word',
85
97
  },
86
98
  },
87
99
  {
@@ -1,11 +1,11 @@
1
1
  {% from "./inputs/combobox/_macro.njk" import MdsCombobox %}
2
2
 
3
3
  {% if not multiple %}
4
-
5
4
  <div class="mds-grid-row">
6
5
  <div class="mds-grid-col-6">
7
6
  <h3>{{ variantTitle }}</h3>
8
- {{ MdsCombobox({
7
+
8
+ {% call MdsCombobox({
9
9
  id: id,
10
10
  name: name,
11
11
  labelText: labelText,
@@ -19,17 +19,27 @@
19
19
  validationError: validationError,
20
20
  helpText: helpText,
21
21
  i18n: i18n,
22
- minSearchCharacters: minSearchCharacters
23
- }) }}
24
- {#
25
- The below only applies to one combobox example.
26
- Demonstrates how the clear-all event (fired when combobox is cleared),
27
- can be used to clear hidden form fields.
28
- #}
22
+ minSearchCharacters: minSearchCharacters,
23
+ apiUrl: apiUrl,
24
+ apiQueryKey: apiQueryKey,
25
+ apiOptionsPath: apiOptionsPath,
26
+ optionLabelPath: optionLabelPath
27
+ }) %}
28
+ {#
29
+ The below demonstrates how a target input value can be set on option selection, or on clear of combobox
30
+ #}
31
+ {% if id === 'salary-selection' %}
32
+ <input disabled data-key="value" placeholder="normally hidden target" style="position:absolute; left: 100%; top: 0;" />
33
+ {% endif %}
34
+ {% if id === 'keywords-lookup' or id === 'keywords-lookup-prefilled' %}
35
+ <input disabled data-key="word" placeholder="normally hidden target" style="position:absolute; left: 100%; top: 0;" />
36
+ {% endif %}
37
+ {% endcall %}
38
+
29
39
  {% if id === 'salary-selection' %}
30
- <p class="mds-margin-top-b3">A hidden input is cleared when clear-all event fires. Check <em>#salary-selection-hidden-input</em> in dev tools.</p>
31
- <input type="hidden" id="salary-selection-hidden-input" />
40
+ <p class="mds-margin-top-b3">A hidden input is cleared when combobox is cleared.
32
41
  {% endif %}
42
+
33
43
  <br><br>
34
44
  </div>
35
45
  </div>
@@ -80,19 +80,19 @@
80
80
  <div class="mds-file-upload__selected-state">
81
81
  <div class="mds-file-upload__file-name-container">
82
82
  {{- MdsIcon({
83
- iconName: 'check',
84
- classes: 'mds-icon--md',
85
- hasContainer: true,
86
- containerClasses: 'mds-icon-container--circle mds-icon-container--success mds-icon-container--before'
87
- })
88
- -}}
83
+ iconName: 'check',
84
+ classes: 'mds-icon--md',
85
+ hasContainer: true,
86
+ containerClasses: 'mds-icon-container--circle mds-icon-container--success mds-icon-container--before'
87
+ })
88
+ -}}
89
89
  <span class="mds-visually-hidden">{{ params.i18n.selectedFileText | default('Selected file:') }} </span>
90
90
  <span class="mds-file-upload__file-name">{% if params.value %}{{ params.value }}{% endif %}</span>
91
91
  </div>
92
92
  {% call MdsButton({
93
- classes: 'mds-button--plain mds-button--small mds-file-upload__remove-button',
94
- type: 'button'
95
- }) -%}
93
+ classes: 'mds-button--plain mds-button--small mds-file-upload__remove-button',
94
+ attributes: { type: 'button' }
95
+ }) -%}
96
96
  {{- params.i18n.removeButtonText | default('Remove file') -}}
97
97
  {{-
98
98
  MdsIcon({
@@ -9,7 +9,7 @@
9
9
 
10
10
  {% call MdsButton({
11
11
  classes: 'mds-button--plain js-mds-modal-close mds-modal-default-close-button',
12
- type: 'button'
12
+ attributes: { type: 'button' }
13
13
  }) -%}
14
14
  {{ closeText }}
15
15
  {{ MdsIcon({
@@ -13,7 +13,7 @@ Donec dapibus consectetur fermentum. Proin vel lacus elit. Nunc eu massa magna.
13
13
  Nullam faucibus pulvinar felis. Sed et tortor enim. Nulla luctus maximus mi aliquam tempor. Vivamus dignissim condimentum augue sed commodo. Donec a elementum risus, id interdum sem. Morbi malesuada tempor dapibus. Nam varius augue augue, vel pellentesque felis venenatis nec. Vivamus eget vulputate risus. Curabitur mollis, neque id tempus laoreet, nibh lectus aliquam felis, euismod varius mi turpis a sapien. Etiam ut mi a nisi faucibus egestas at eget ante.</p>
14
14
  <p>{% call MdsButton({
15
15
  classes: 'mds-button--neutral js-mds-modal-close',
16
- type: 'button'
16
+ attributes: { type: 'button' }
17
17
  }) -%}
18
18
  Close
19
19
  {%- endcall %}
@@ -5,7 +5,7 @@
5
5
  <p id="modal-description">I'm a modal window with <a href="#">a link</a>.</p>
6
6
  <p>{% call MdsButton({
7
7
  classes: 'mds-button--neutral js-mds-modal-close',
8
- type: 'button'
8
+ attributes: { type: 'button' }
9
9
  }) -%}
10
10
  Close
11
11
  {%- endcall %}
@@ -5,6 +5,5 @@
5
5
  headingTag: headingTag,
6
6
  actionText: actionText,
7
7
  actionHref: actionHref,
8
- actionElement: actionElement,
9
8
  classes: classes
10
9
  }) }}
@@ -6,7 +6,6 @@
6
6
  <div class="mds-section-title__action">
7
7
  {%- if params.actionHref -%}
8
8
  {% call MdsButton({
9
- element: params.actionElement,
10
9
  href: params.actionHref,
11
10
  classes: params.classes
12
11
  }) -%}
@@ -4,7 +4,9 @@
4
4
  - `classes`: add extra CSS classes to the component
5
5
  - `allHeadersTag`: the html tag to use for the panel header (h2 as default)
6
6
  - `allHeadersDisplay`: wheter the panel header should be always visible or not (default false)
7
- - `content`: is an array of objects containing [label, selected, id, content, headerText, linkCustomAttr] for each tab, the content for the panel can also be a custom component, headerText is text used for each panel header
7
+ - `content`: is an array of objects containing [label, selected, id, content, headerText, linkCustomAttr] for each tab, the content for the panel can also be a custom component, headerText is
8
+ text used for each panel header
9
+ - `content[{linkCustomAttr}]`: you can add extra attributes by passing an object to the parameter. Example: `attributes: { attribute-name: 'attribute-value' }`
8
10
 
9
11
  ## Variations
10
12
  Tabs have different style depending on the amount of items to display, if 2 tabs then it will always display tabs (except when js is disabled), if more than 2 then it will display a list of links for small devices and tabs for desktop
@@ -1,4 +1,5 @@
1
1
  {% from "./tab-id.njk" import TabId %}
2
+ {% from "../../sub-components/attributes/macro.njk" import MdsAttributes %}
2
3
 
3
4
  {% if params.content %}
4
5
  {% set kebabName -%}
@@ -11,12 +12,12 @@
11
12
  mds-tabs--full-tabbed js-full-tabbed
12
13
  {%- endif -%}
13
14
  {%- endset %}
14
- <div class="mds-tabs {{ tabVariation }}{%- if params.name %} mds-tabs--{{ kebabName }}{%- endif -%}{% if params.classes %} {{ params.classes }}{% endif %}" data-test="tabs-{{kebabName}}">
15
+ <div class="mds-tabs {{ tabVariation }}{%- if params.name %} mds-tabs--{{ kebabName }}{%- endif -%}{% if params.classes %} {{ params.classes }}{% endif %}">
15
16
  <ul class="mds-tabs__list">
16
17
  {%- for item in params.content -%}
17
18
  {%- set tabId = TabId(item, params, loop.index) -%}
18
19
  <li class="mds-tabs__list-item">
19
- <a href="#{{ tabId }}" id="label-{{tabId}}" class="mds-tabs__tab{%- if item.selected %} mds-tabs__tab--selected{%- endif %} js-tabs-item" data-test="tabs-trigger{% if tabId %}-{{tabId}}{% endif %}" {% if item.linkCustomAttr %} {{item.linkCustomAttr | safe}}{% endif %}>
20
+ <a href="#{{ tabId }}" id="label-{{ tabId }}" class="mds-tabs__tab{%- if item.selected %} mds-tabs__tab--selected{%- endif %} js-tabs-item" {{ MdsAttributes(item.linkCustomAttr) }}>
20
21
  {{- item.label -}}
21
22
  </a>
22
23
  </li>
@@ -42,7 +43,7 @@
42
43
  {%- endif -%}
43
44
  {%- endset -%}
44
45
 
45
- <section class="mds-tabs__panel{%- if not item.selected %} mds-tabs__panel--hidden {%- endif -%}{% if item.contentClasses %} {{ item.contentClasses }}{% endif %}" id="{{ tabId }}" data-test="tab-panel{% if tabId %}-{{tabId}}{% endif %}">
46
+ <section class="mds-tabs__panel{%- if not item.selected %} mds-tabs__panel--hidden {%- endif -%}{% if item.contentClasses %} {{ item.contentClasses }}{% endif %}" id="{{ tabId }}">
46
47
  {# id is used in js to apply aria-labelledby, don't forget to update both if needed #}
47
48
  <{{allHeadersTag}} class="mds-tabs__panel-header{{allHeadersDisplay}}">{{- headerText | safe -}}</{{allHeadersTag}}>
48
49
  <div class="mds-tabs__panel__content">
@@ -117,14 +117,14 @@ module.exports = {
117
117
  label: 'Plane',
118
118
  selected: true,
119
119
  headerText: 'Flying craft',
120
- linkCustomAttr: 'data-metric="wings"',
120
+ linkCustomAttr: { 'data-metric': 'wings' },
121
121
  content:
122
122
  '<p>Inspect the anchor tags inside each tab to see the custom attributes that are added. One use case of this is allowing us to add click metrics.</p>',
123
123
  },
124
124
  {
125
125
  label: 'Helicopter',
126
126
  headerText: 'Flying craft',
127
- linkCustomAttr: 'data-metric="rotor-blades"',
127
+ linkCustomAttr: { 'data-metric': 'rotor-blades' },
128
128
  content:
129
129
  '<p>Inspect the anchor tags inside each tab to see the custom attributes that are added. One use case of this is allowing us to add click metrics.</p>',
130
130
  },
@@ -1,11 +1,7 @@
1
1
  import switchStateScript from './fractal-scripts/switch-state';
2
2
  import notificationScript from './fractal-scripts/notification';
3
- import comboboxScript from './fractal-scripts/combobox';
4
3
 
5
4
  document.addEventListener('DOMContentLoaded', () => {
6
5
  switchStateScript.init();
7
6
  notificationScript.init();
8
- setTimeout(() => {
9
- comboboxScript.init();
10
- }, 1000);
11
7
  });
@@ -6,7 +6,6 @@
6
6
  <h3>Get job alerts</h3>
7
7
  <p>Create a job alert and receive personalised <a href="/some-link">job recommendations</a> straight to your inbox</p>
8
8
  {% call MdsButton({
9
- element: 'a',
10
9
  href: '/somewhere'
11
10
  }) -%}
12
11
  Create alert
@@ -0,0 +1,94 @@
1
+ {#
2
+ Renders component attributes as string
3
+
4
+ * By default or using `optional: false`, attributes render as ` name="value"`
5
+ * Using `optional: true`, attributes with empty (`null`, `undefined` or `false`) values are omitted
6
+ * Using `optional: true`, attributes with `true` (boolean) values render `name` only without value
7
+
8
+ {@link https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML}
9
+
10
+ @example
11
+ Output attribute ` aria-hidden="true"` when `true` (boolean) or `"true"` (string)
12
+
13
+ ```njk
14
+ MdsAttributes({
15
+ "aria-hidden": true
16
+ })
17
+ ```
18
+
19
+ @example
20
+ Output attribute ` aria-hidden="false"` when `false` (boolean) or `"false"` (string)
21
+
22
+ ```njk
23
+ MdsAttributes({
24
+ "aria-hidden": false
25
+ })
26
+ ```
27
+
28
+ @example
29
+ Output attribute ` hidden=""` when `null`, `undefined` or empty `""` (string)
30
+
31
+ ```njk
32
+ MdsAttributes({
33
+ "hidden": undefined
34
+ })
35
+ ```
36
+
37
+ @example
38
+ Output attribute ` hidden` as boolean attribute when optional and `true`
39
+
40
+ ```njk
41
+ MdsAttributes({
42
+ hidden: {
43
+ value: true,
44
+ optional: true
45
+ },
46
+ })
47
+ ```
48
+
49
+ @example
50
+ Output empty string when optional and `null`, `undefined` or `false`
51
+
52
+ ```njk
53
+ MdsAttributes({
54
+ hidden: {
55
+ value: undefined,
56
+ optional: true
57
+ },
58
+ })
59
+ ```
60
+
61
+ @private
62
+ @param {{ [attribute: string]: string | { value: string, optional?: boolean } } | string} attributes - Component attributes param
63
+ #}
64
+ {% macro MdsAttributes(attributes) -%}
65
+ {#- Default attributes output -#}
66
+ {% set attributesHtml = attributes if attributes is string else "" %}
67
+
68
+ {#- Append attribute name/value pairs -#}
69
+ {%- if attributes is mapping %}
70
+ {% for name, value in attributes %}
71
+ {#- Detect if this is a `safe` filtered value. Just pass the value through if so. -#}
72
+ {#- https://github.com/alphagov/govuk-frontend/issues/4937 -#}
73
+ {% if value is mapping and not [undefined, null].includes(value.val) and value.length %}
74
+ {% set value = value.val %}
75
+ {% endif %}
76
+
77
+ {#- Set default attribute options -#}
78
+ {% set options = value if value is mapping else {
79
+ value: value,
80
+ optional: false
81
+ } %}
82
+
83
+ {#- Output ` name` only (no value) for boolean attributes -#}
84
+ {% if options.optional === true and options.value === true %}
85
+ {% set attributesHtml = attributesHtml + " " + name | escape %}
86
+ {#- Skip optional empty attributes or output ` name="value"` pair by default -#}
87
+ {% elif (options.optional === true and not [undefined, null, false].includes(options.value)) or options.optional !== true %}
88
+ {% set attributesHtml = attributesHtml + " " + name | escape + '="' + options.value | escape + '"' %}
89
+ {% endif %}
90
+ {% endfor %}
91
+ {% endif -%}
92
+
93
+ {{- attributesHtml | safe -}}
94
+ {%- endmacro %}
@@ -1,88 +0,0 @@
1
- const elementName = 'mds-combobox';
2
-
3
- function bindToLocationSelect() {
4
- const el = document.querySelector(`[data-combobox-id="distance-selection"]`);
5
-
6
- if (el) {
7
- const selectInput = el.querySelector('select');
8
- const options = Array.from(selectInput.querySelectorAll('option'));
9
- const vueSelect = el.querySelector(elementName);
10
-
11
- vueSelect.options = options.slice(1).map((opt) => ({ value: opt.value, label: opt.textContent }));
12
- }
13
- }
14
-
15
- function bindToSalarySelect() {
16
- const el = document.querySelector(`[data-combobox-id="salary-selection"]`);
17
-
18
- if (el) {
19
- const selectInput = el.querySelector('select');
20
- const options = Array.from(selectInput.querySelectorAll('option'));
21
- const vueSelect = el.querySelector(elementName);
22
-
23
- vueSelect.options = options.slice(1).map((opt) => ({ value: opt.value, label: opt.textContent }));
24
- }
25
- }
26
-
27
- function setSalaryHiddenField() {
28
- const comboBoxEl = document.querySelector(`[data-combobox-id="salary-selection"] mds-combobox`);
29
- const hiddenInputEl = document.querySelector('#salary-selection-hidden-input');
30
-
31
- if (comboBoxEl && hiddenInputEl) {
32
- comboBoxEl.addEventListener('select-option', () => {
33
- console.log('Setting #salary-selection-hidden-input..');
34
- hiddenInputEl.value = hiddenInputEl.value === 'flip' ? 'flop' : 'flip';
35
- });
36
- }
37
- }
38
-
39
- function clearSalaryHiddenField() {
40
- const comboBoxEl = document.querySelector(`[data-combobox-id="salary-selection"] mds-combobox`);
41
- const hiddenInputEl = document.querySelector('#salary-selection-hidden-input');
42
-
43
- if (comboBoxEl && hiddenInputEl) {
44
- comboBoxEl.addEventListener('clear-all', () => {
45
- console.log('Clearing #salary-selection-hidden-input..');
46
- hiddenInputEl.value = '';
47
- });
48
- }
49
- }
50
-
51
- function bindToApi() {
52
- const el = document.querySelector(`[data-combobox-id="keywords-lookup"]`);
53
-
54
- if (el) {
55
- const vueSelect = el.querySelector(elementName);
56
-
57
- vueSelect.filterOptions = false;
58
- vueSelect.addEventListener('search', (event) => {
59
- const [searchValue] = event.detail;
60
-
61
- if (searchValue && searchValue.length > 2) {
62
- fetch(`https://api.datamuse.com/sug?s=${searchValue}`)
63
- .then((res) => res.json())
64
- .then((data) => {
65
- const options = data.map(({ word }) => ({ value: word, label: word }));
66
-
67
- vueSelect.options = options;
68
- return options;
69
- })
70
- .catch(console.log);
71
- } else {
72
- vueSelect.options = [];
73
- }
74
- });
75
- }
76
- }
77
-
78
- const combobox = {
79
- init: () => {
80
- bindToLocationSelect();
81
- bindToSalarySelect();
82
- setSalaryHiddenField();
83
- clearSalaryHiddenField();
84
- bindToApi();
85
- },
86
- };
87
-
88
- export default combobox;