@ilo-org/twig 0.15.0 → 0.16.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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @ilo-org/twig@0.15.0 build:lib /home/runner/work/designsystem/designsystem/packages/twig
2
+ > @ilo-org/twig@0.16.0 build:lib /home/runner/work/designsystem/designsystem/packages/twig
3
3
  > node importprefix.js && node importsvgs.js && pnpm output
4
4
 
5
5
  theme prefix added
6
6
 
7
- > @ilo-org/twig@0.15.0 output /home/runner/work/designsystem/designsystem/packages/twig
7
+ > @ilo-org/twig@0.16.0 output /home/runner/work/designsystem/designsystem/packages/twig
8
8
  > node outputtwigs.js
9
9
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @ilo-org/twig
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 138890e7a: implemented new icon component
8
+
9
+ ### Patch Changes
10
+
11
+ - ac23bf484: Add clear button to search input field
12
+ - fd26dbafc: Enable Button to use newly refactored Icon component
13
+ - db2ecaf48: Correct the way the Table of Contents passes properties to the Open and Close buttons.
14
+ - Updated dependencies [ac23bf484]
15
+ - Updated dependencies [1b9c54f99]
16
+ - Updated dependencies [138890e7a]
17
+ - @ilo-org/styles@0.14.0
18
+
3
19
  ## 0.15.0
4
20
 
5
21
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilo-org/twig",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Twig components for the ILO's Design System",
6
6
  "main": "",
@@ -27,7 +27,7 @@
27
27
  "@ilo-org/brand-assets": "0.4.0",
28
28
  "@ilo-org/fonts": "0.1.2",
29
29
  "@ilo-org/icons": "0.2.1",
30
- "@ilo-org/styles": "0.13.3",
30
+ "@ilo-org/styles": "0.14.0",
31
31
  "@ilo-org/themes": "0.7.1",
32
32
  "@ilo-org/utils": "0.0.11"
33
33
  },
@@ -3,18 +3,21 @@
3
3
  #}
4
4
  {% if url %}
5
5
  <a class="{{prefix}}--button ilo--button--{{size}} {{prefix}}--button--{{type}}{% if icon %} icon icon__position--{{iconPosition}}{% endif %}{% if icononly %} icon--only{% endif %}{% if className %} {{className}}{% endif %}" href="{{url}}" {% if target is defined and target != 'false' %} target="{{target}}" rel="noopener noreferrer" {% endif %} {% if disabled is defined and disabled == 'true' %} disabled {% endif %}>
6
-
7
6
  <span class="link__label">{{label}}</span>
8
- {% if icon %}
9
- {% include '@components/icon/icon.twig' with {icon: icon} %}
10
- {% endif %}
7
+ {% block button_icon %}
8
+ {% if icon %}
9
+ {% include '@components/icon/icon.twig' with {
10
+ prefix: prefix,
11
+ name: icon.name,
12
+ size: icon.size,
13
+ color: icon.color
14
+ } only %}
15
+ {% endif %}
16
+ {% endblock %}
11
17
  </a>
12
18
  {% else %}
13
19
  <button class="{{prefix}}--button ilo--button--{{size}} {{prefix}}--button--{{type}}{% if icon %} icon icon__position--{{iconPosition}}{% endif %}{% if icononly %} icon--only{% endif %}{% if className %} {{className}}{% endif %}" {% if kind %} type="{{kind}}" {% endif %} {% if opensmodal %} aria-haspopup="dialog" {% endif %} {% if disabled is defined and disabled == 'true' %} disabled {% endif %} {% if name %} name="{{name}}" {% endif %}>
14
-
15
20
  <span class="button__label">{{label}}</span>
16
- {% if icon %}
17
- {% include '@components/icon/icon.twig' with {icon: icon} %}
18
- {% endif %}
21
+ {{ block("button_icon") }}
19
22
  </button>
20
23
  {% endif %}
@@ -23,16 +23,18 @@ button:
23
23
  preview: ""
24
24
  required: false
25
25
  target:
26
- type: string
26
+ type: object
27
27
  label: target
28
28
  description: Should the button open in a new tab? If so, include the name of the tab. Only evaluated if the url field is populated.
29
29
  preview: "true"
30
30
  required: false
31
31
  icon:
32
- type: string
32
+ type: object
33
33
  label: Icon
34
- description: optional icon to include in button
35
- preview: false
34
+ description: Optional icon to include in button. See the `Icon` component for a full list of arguments
35
+ preview:
36
+ name: "add"
37
+ size: 24
36
38
  required: false
37
39
  className:
38
40
  type: string
@@ -0,0 +1,16 @@
1
+ import Icon from "./icon";
2
+
3
+ Drupal.behaviors.icon = {
4
+ attach() {
5
+ Array.prototype.forEach.call(
6
+ document.querySelectorAll(`[data-loadcomponent="Icon"]`),
7
+ (element) => {
8
+ if (!element.dataset.jsProcessed) {
9
+ // eslint-disable-next-line no-console
10
+ new Icon(element);
11
+ element.dataset.jsProcessed = true;
12
+ }
13
+ }
14
+ );
15
+ },
16
+ };
@@ -0,0 +1,128 @@
1
+ import * as Icons from "@ilo-org/icons";
2
+
3
+ const SIZING = [16, 20, 24, 32];
4
+ const DEFAULT_SIZE = 24;
5
+ const SVG_NS = "http://www.w3.org/2000/svg";
6
+ const PATH_NS = "http://www.w3.org/2000/path";
7
+
8
+ /**
9
+ * The Icon module which handles rendering icons
10
+ *
11
+ * @class Icon
12
+ */
13
+ export default class Icon {
14
+ /**
15
+ * Icon constructor which assigns the element passed into the constructor
16
+ *
17
+ * @param {SVGElement} element - REQUIRED - the module's container
18
+ */
19
+ constructor(element) {
20
+ /**
21
+ * Reference to the DOM element that is the root of the component
22
+ * @property {SVGElement}
23
+ */
24
+ this.element = element;
25
+
26
+ /** Initialize the icon logic*/
27
+ this.init();
28
+ }
29
+
30
+ /**
31
+ * Initializes the icon logic
32
+ */
33
+ init() {
34
+ const isValid = this.validate();
35
+ if (!isValid) {
36
+ return;
37
+ }
38
+
39
+ const svg = this.build();
40
+
41
+ this.prepare(svg);
42
+ this.element.outerHTML = svg.outerHTML;
43
+
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * Validate icon prop
49
+ *
50
+ * @returns {boolean}
51
+ */
52
+ validate() {
53
+ let { name, size } = this.element.dataset;
54
+ if (!SIZING.includes(+size)) {
55
+ console.warn(
56
+ `Icon size ${size} is not supported. Defaulting to ${DEFAULT_SIZE}px`
57
+ );
58
+ size = DEFAULT_SIZE;
59
+ }
60
+
61
+ if (!name) {
62
+ console.warn("Icon name is required");
63
+ return false;
64
+ }
65
+
66
+ const reference = this.getIconConfig(name, size);
67
+ if (!reference) {
68
+ console.warn(`Icon "${name}" not found`);
69
+ return false;
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Build a new SVG element
77
+ *
78
+ * @returns {SVGElement}
79
+ */
80
+ build() {
81
+ const { name, size, color } = this.element.dataset;
82
+ const config = this.getIconConfig(name, size);
83
+ const element = document.createElementNS(SVG_NS, "svg");
84
+
85
+ for (const [key, value] of Object.entries(config.attrs)) {
86
+ element.setAttribute(key, value);
87
+ }
88
+
89
+ if (color) {
90
+ element.setAttribute("fill", color);
91
+ }
92
+
93
+ for (const item of config.content) {
94
+ const child = document.createElementNS(PATH_NS, item.elem);
95
+ for (let [key, value] of Object.entries(item.attrs)) {
96
+ if (key === "fill" && color) {
97
+ value = color;
98
+ }
99
+ child.setAttribute(key, value);
100
+ }
101
+ element.appendChild(child);
102
+ }
103
+
104
+ return element;
105
+ }
106
+
107
+ /**
108
+ * Get the icon config
109
+ *
110
+ * @param {string} name
111
+ * @param {string} size
112
+ */
113
+ getIconConfig(name, size) {
114
+ const nameUpper = name.charAt(0).toUpperCase() + name.slice(1);
115
+
116
+ return Icons[`${nameUpper}${size}`];
117
+ }
118
+
119
+ /**
120
+ * Attach final attributes
121
+ *
122
+ * @param {SVGElement} icon - REQUIRED - the svg icon instance
123
+ */
124
+ prepare(icon) {
125
+ icon.setAttribute("class", this.element.getAttribute("class"));
126
+ icon.setAttribute("data-js-processed", true);
127
+ }
128
+ }
@@ -0,0 +1,10 @@
1
+ import "./index";
2
+
3
+ export default {};
4
+
5
+ const patternDefinition = require("./icon.wingsuit.yml");
6
+
7
+ /* eslint-disable-next-line */
8
+ export const wingsuit = {
9
+ patternDefinition,
10
+ };
@@ -1,4 +1,4 @@
1
1
  {#
2
2
  ICON COMPONENT
3
3
  #}
4
- <img class="{{prefix}}--icon" src="{{'/images/'~icon~'.svg'}}" alt="{{icon}}" role="presentation">
4
+ <svg class="{{prefix}}--icon" data-name="{{name}}" data-size="{{size}}" data-color="{{color}}" data-loadcomponent="Icon"></svg>
@@ -0,0 +1,29 @@
1
+ icon:
2
+ namespace: Components/User Interface
3
+ use: "@components/icon/icon.twig"
4
+ label: Icon
5
+ description: A component for displaying an svg icon.
6
+ settings:
7
+ name:
8
+ type: string
9
+ label: Name
10
+ description: The name of the icon to display. <strong style="font-weight:bold">The name should match SVG file</strong>, see available icons in the <a target="_blank" href="https://github.com/international-labour-organization/designsystem/tree/develop/packages/icons/src/svg">@ilo-org/icons</a> package
11
+ preview: "arrowright"
12
+ required: true
13
+ size:
14
+ type: select
15
+ label: Size
16
+ description: The size of the icon in pixels
17
+ options:
18
+ 16: 16
19
+ 20: 20
20
+ 24: 24
21
+ 32: 32
22
+ preview: "24"
23
+ required: false
24
+ color:
25
+ type: string
26
+ label: Fill
27
+ description: The fill color of the icon in hex
28
+ preview: "#000000"
29
+ required: false
@@ -1,6 +1,6 @@
1
1
  /**
2
- * media
2
+ * Icon
3
3
  */
4
- // Module template
5
- import "./image.twig";
6
- import "./image.wingsuit.yml";
4
+ import "./icon.twig";
5
+ import "./icon.wingsuit.yml";
6
+ import "./icon.behavior";
@@ -4,3 +4,4 @@
4
4
  // Module template
5
5
  import "./search.twig";
6
6
  import "./search.wingsuit.yml";
7
+ import "./search.behavior";
@@ -0,0 +1,15 @@
1
+ import Search from "./search";
2
+
3
+ Drupal.behaviors.search = {
4
+ attach() {
5
+ Array.prototype.forEach.call(
6
+ document.querySelectorAll(`[data-loadcomponent="Search"]`),
7
+ (element) => {
8
+ if (!element.dataset.jsProcessed) {
9
+ new Search(element);
10
+ element.dataset.jsProcessed = true;
11
+ }
12
+ }
13
+ );
14
+ },
15
+ };
@@ -0,0 +1,103 @@
1
+ import { EVENTS, ARIA } from "@ilo-org/utils";
2
+
3
+ /**
4
+ * The Search module which handles the clear button.
5
+ *
6
+ * @class Search
7
+ */
8
+ export default class Search {
9
+ /**
10
+ * Search constructor which assigns the element passed into the constructor
11
+ * to the `this.element` property for later reference
12
+ *
13
+ * @param {HTMLElement} element - REQUIRED - the module's container
14
+ */
15
+ constructor(element) {
16
+ /**
17
+ * Reference to the DOM element that is the root of the component
18
+ * @property {Object}
19
+ */
20
+ this.element = element;
21
+ this.multipleExpanded = false;
22
+
23
+ // Initialize the view
24
+ this.init();
25
+ }
26
+
27
+ /**
28
+ * Initializes the view by calling the functions to
29
+ * create DOM references, setup event handlers and
30
+ * then create the event listeners
31
+ *
32
+ * @return {Object} Search A reference to the instance of the class
33
+ * @chainable
34
+ */
35
+ init() {
36
+ this.cacheDomReferences().setupHandlers().enable();
37
+
38
+ return this;
39
+ }
40
+
41
+ /**
42
+ * Find all necessary DOM elements used in the view and cache them
43
+ *
44
+ * @return {Object} Search A reference to the instance of the class
45
+ * @chainable
46
+ */
47
+ cacheDomReferences() {
48
+ /**
49
+ * The field that a user interacts with on a form
50
+ * @type {Object}
51
+ */
52
+ this.searchButton = this.element.querySelector(
53
+ ".ilo--searchfield--clear-button"
54
+ );
55
+ this.searchInputField = this.element.querySelector(".ilo--input");
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Bind event handlers with the proper context of `this`.
61
+ *
62
+ * @return {Object} Search A reference to the current instance of the class
63
+ * @chainable
64
+ */
65
+ setupHandlers() {
66
+ this.onClick = this.onClick.bind(this);
67
+ this.KeyPressHandler = this.onKeyPress.bind(this);
68
+ return this;
69
+ }
70
+
71
+ /**
72
+ * Creates event listeners to enable interaction with view
73
+ *
74
+ * @return {Object} Search A reference to the instance of the class
75
+ * @chainable
76
+ */
77
+ enable() {
78
+ this.searchButton.addEventListener(EVENTS.CLICK, this.onClick.bind(this));
79
+ this.searchInputField.addEventListener(
80
+ "keyup",
81
+ this.onKeyPress.bind(this, this.searchButton)
82
+ );
83
+
84
+ return this;
85
+ }
86
+
87
+ /**
88
+ * Onclick interaction with the clear button
89
+ */
90
+ onClick(e) {
91
+ this.searchInputField.value = "";
92
+ this.searchButton.classList.remove("show");
93
+ }
94
+
95
+ onKeyPress(searchButton, e) {
96
+ const inputValue = e.target.value.trim();
97
+ if (inputValue !== "") {
98
+ this.searchButton.classList.add("show");
99
+ } else {
100
+ this.searchButton.classList.remove("show");
101
+ }
102
+ }
103
+ }
@@ -6,17 +6,26 @@
6
6
  {% set searchClass = [baseClass, class] %}
7
7
  {% set inputClass = prefix ~ "--input" %}
8
8
  {% set buttonClass = baseClass ~ "--button" %}
9
+ {% set fieldSetClass = prefix ~ "--fieldset" %}
10
+ {% set clearButtonClass = baseClass ~ "--clear-button" %}
9
11
 
10
12
  {% if error %}
11
13
  {% set inputClass = inputClass|merge([baseClass ~ "__error"]) %}
12
14
  {% endif %}
13
15
 
14
-
15
16
  {% block formfield %}
16
-
17
- <div class="{{ searchClass|join(" ") }}" {% if style %} style={{ style }} {% endif %}>
17
+ <div data-loadcomponent="Search" class="{{ searchClass|join(" ") }}" {% if style %} style={{ style }} {% endif %}>
18
+ <div class="{{fieldSetClass}}">
18
19
  <input name="{{name}}" type="{{type}}" class="{{ inputClass }}{% if error %} error{% endif %}" {% if placeholder %} placeholder="{{placeholder}}" {% endif %} {% if disabled %} disabled {% endif %} {% if required %} required {% endif %} id="{% if id %}{{id}}{% else %}{{name}}{% endif %}"/>
20
+ <span class="{{clearButtonClass}}" >
21
+ {% include "@components/icon/icon.twig" with {
22
+ "name": "Close",
23
+ "size": "24",
24
+ "color": "#230050"
25
+ } %}
26
+ </span>
27
+ </div>
19
28
  <input class="{{ buttonClass }}" type="submit"/>
20
- </div>
21
29
 
30
+ </div>
22
31
  {% endblock %}
@@ -1,23 +1,47 @@
1
- {#
2
- TABLE OF CONTENTS COMPONENT
3
- #}
1
+ {# tableofcontents.twig #}
2
+
3
+ {% set openbutton_size = openbutton.size|default("medium") %}
4
+ {% set openbutton_type = openbutton.type|default("secondary") %}
5
+ {% set openbutton_label = openbutton.label|default("Open") %}
6
+ {% set openbutton_label = openbutton.label|default("Open") %}
7
+ {% set closebutton_label = closebutton.label|default("Close") %}
8
+
4
9
  {% if items %}
5
- <div class="{{prefix}}--table-of-contents--wrapper" data-loadcomponent="TableOfContents" data-prefix="{{prefix}}">
6
- <div class="{{prefix}}--table-of-contents--trigger">
7
- {% include '@components/button/button.twig' with openbutton %}
8
- </div>
9
- <div class="{{prefix}}--table-of-contents--modal">
10
- <h3 class="{{prefix}}--table-of-contents--headline">{{headline}}</h3>
11
- {% include '@components/button/button.twig' with closebutton %}
12
- </div>
13
- <nav class="{{prefix}}--table-of-contents">
14
- <ul class="{{prefix}}--table-of-contents--list">
15
- {% for item in items %}
16
- <li class="{{prefix}}--table-of-contents--list--item">
17
- <a class="{{prefix}}--table-of-contents--link" href="{{item.href}}">{{item.label}}</a>
18
- </li>
19
- {% endfor %}
20
- </ul>
21
- </nav>
22
- </div>
10
+ <div class="{{prefix}}--table-of-contents--wrapper" data-loadcomponent="TableOfContents" data-prefix="{{prefix}}">
11
+ <div class="{{prefix}}--table-of-contents--trigger">
12
+ {% include '@components/button/button.twig' with {
13
+ prefix: prefix,
14
+ size: openbutton_size,
15
+ type: openbutton_type,
16
+ label: openbutton_label,
17
+ opensmodal: true,
18
+ className: "toc--modal--open"
19
+ } only %}
20
+ </div>
21
+ <div class="{{prefix}}--table-of-contents--modal">
22
+ <h3 class="{{prefix}}--table-of-contents--headline">{{headline}}</h3>
23
+ {% include '@components/button/button.twig' with {
24
+ prefix: prefix,
25
+ size: "large",
26
+ type: "tertiary",
27
+ label: closebutton_label,
28
+ icon: {
29
+ name: "close",
30
+ size: 24,
31
+ },
32
+ iconPos: "center",
33
+ icononly: true,
34
+ className: "toc--modal--close"
35
+ } only %}
36
+ </div>
37
+ <nav class="{{prefix}}--table-of-contents">
38
+ <ul class="{{prefix}}--table-of-contents--list">
39
+ {% for item in items %}
40
+ <li class="{{prefix}}--table-of-contents--list--item">
41
+ <a class="{{prefix}}--table-of-contents--link" href="{{item.href}}">{{item.label}}</a>
42
+ </li>
43
+ {% endfor %}
44
+ </ul>
45
+ </nav>
46
+ </div>
23
47
  {% endif %}
@@ -7,26 +7,18 @@ tableofcontents:
7
7
  openbutton:
8
8
  type: object
9
9
  label: Open Button
10
- description: The label and settings for the button that will show the table of contents at small breakpoints
10
+ description: Settings for the button that will show the table of contents at small breakpoints. Only the `size`, `type` and `label` properties will be passed to the underlying button.
11
11
  preview:
12
12
  size: "medium"
13
13
  type: "secondary"
14
- label: "Open Button"
15
- className: "toc--modal--open"
16
- opensmodal: true
14
+ label: "Open"
17
15
  required: true
18
16
  closebutton:
19
17
  type: object
20
18
  label: Close Button
21
- description: Label and settings for the "close" button
19
+ description: Settings for the button that closes the table of contents at small breakpoints. Only the `label` property will be passed to the underlying button.
22
20
  preview:
23
- size: "large"
24
- type: "tertiary"
25
- label: "Close Button"
26
- icon: close
27
- iconPos: center
28
- icononly: true
29
- className: "toc--modal--close"
21
+ label: "Close"
30
22
  headline:
31
23
  type: string
32
24
  label: Headline