@ilo-org/twig 1.7.4 → 1.8.1

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 (32) hide show
  1. package/dist/components/contextmenu/contextmenu.component.yml +8 -0
  2. package/dist/components/contextmenu/contextmenu.twig +6 -4
  3. package/dist/components/contextmenu/contextmenu.wingsuit.yml +8 -0
  4. package/dist/components/languagetoggle/languagetoggle.behavior.js +5 -1
  5. package/dist/components/languagetoggle/languagetoggle.component.yml +5 -0
  6. package/dist/components/languagetoggle/languagetoggle.twig +12 -9
  7. package/dist/components/languagetoggle/languagetoggle.wingsuit.yml +5 -0
  8. package/dist/components/logo/logo.component.yml +34 -0
  9. package/dist/components/logo/logo.twig +27 -0
  10. package/dist/components/logo/logo.wingsuit.yml +34 -0
  11. package/dist/components/nav/desktop/navdropdown.twig +15 -0
  12. package/dist/components/nav/desktop/navmenu.twig +27 -0
  13. package/dist/components/nav/desktop/navmenugrid.twig +16 -0
  14. package/dist/components/nav/mobile/mobiledrawer_layout.twig +26 -0
  15. package/dist/components/nav/mobile/mobiledrawer_main.twig +51 -0
  16. package/dist/components/nav/mobile/mobiledrawer_nested.twig +25 -0
  17. package/dist/components/nav/mobile/mobilemenulist.twig +20 -0
  18. package/dist/components/nav/nav.behavior.js +16 -0
  19. package/dist/components/nav/nav.component.yml +108 -0
  20. package/dist/components/nav/nav.twig +5 -0
  21. package/dist/components/nav/nav.wingsuit.yml +108 -0
  22. package/dist/components/nav/nav_compact.twig +109 -0
  23. package/dist/components/nav/nav_complex.twig +139 -0
  24. package/dist/components/radio/radio.twig +0 -1
  25. package/dist/styles/components/languagetoggle.css +1 -1
  26. package/dist/styles/components/scorecard.css +1 -1
  27. package/dist/styles/global.css.map +1 -1
  28. package/dist/styles/index.css +1 -1
  29. package/dist/styles/index.css.map +1 -1
  30. package/dist/styles/monorepo.css +1 -1
  31. package/dist/styles/monorepo.css.map +1 -1
  32. package/package.json +4 -4
@@ -21,4 +21,12 @@ contextmenu:
21
21
  url: "http://www.google.com"
22
22
  - label: Link Five Is Slightly Longer
23
23
  url: "http://www.google.com"
24
+ className:
25
+ type: string
26
+ label: className
27
+ description: Additional CSS class name(s) to add to the context menu
28
+ contextMenuId:
29
+ type: string
30
+ label: contextMenuId
31
+ description: Optional HTML ID attribute for the context menu element
24
32
  visibility: storybook
@@ -1,7 +1,9 @@
1
- {#
2
- CONTEXT MENU COMPONENT
3
- #}
4
- <ol class="ilo--context-menu">
1
+ {# contextmenu.twig #}
2
+
3
+ {% set className = className|default("") %}
4
+ {% set contextMenuId = contextMenuId|default("") %}
5
+
6
+ <ol role="menu" class="ilo--context-menu {{className}}" id="{{contextMenuId}}">
5
7
  {% for link in links %}
6
8
  <li class="ilo--context-menu--item{{ link.endsection|boolval ? ' endsection'}}">
7
9
  <a href="{{link.url}}" class="ilo--context-menu--link">
@@ -21,4 +21,12 @@ contextmenu:
21
21
  url: "http://www.google.com"
22
22
  - label: Link Five Is Slightly Longer
23
23
  url: "http://www.google.com"
24
+ className:
25
+ type: string
26
+ label: className
27
+ description: Additional CSS class name(s) to add to the context menu
28
+ contextMenuId:
29
+ type: string
30
+ label: contextMenuId
31
+ description: Optional HTML ID attribute for the context menu element
24
32
  visibility: storybook
@@ -1,7 +1,11 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- class LanguageToggle{constructor(element){this.element=element;this.prefix=`${this.element.dataset.prefix}--language-toggle`;this.contextMenuVisibleClass=`${this.prefix}--context-menu__open`;this.init();}init(){this.cacheDomReferences().setupHandlers().enable();}cacheDomReferences(){this.contextButton=this.element.querySelector(`.${this.prefix}--container`);this.contextMenu=this.element.querySelector(`.${this.prefix}--context-menu`);return this}setupHandlers(){this.contexMenuIsOpen=this.contexMenuIsOpen.bind(this);this.openContextMenu=this.openContextMenu.bind(this);this.closeContextMenu=this.closeContextMenu.bind(this);this.positionContextMenu=this.positionContextMenu.bind(this);this.onClick=this.onClick.bind(this);return this}enable(){this.contextButton.addEventListener("click",this.onClick.bind(this));window.addEventListener("load",()=>{this.positionContextMenu();});}onClick(e){e.stopPropagation();if(this.contexMenuIsOpen()){this.closeContextMenu();}else {this.openContextMenu();window.addEventListener("click",this.closeContextMenu,{once:true});}return this}openContextMenu(){this.contextMenu.classList.add(this.contextMenuVisibleClass);this.contextMenu.removeAttribute("hidden");this.contextButton.setAttribute("aria-expanded","true");this.positionContextMenu();return this}closeContextMenu(){this.contextMenu.classList.remove(this.contextMenuVisibleClass);this.contextMenu.setAttribute("hidden","hidden");this.contextButton.setAttribute("aria-expanded","false");}positionContextMenu(){const containerRect=this.element.getBoundingClientRect();const contextMenuRect=this.contextMenu.getBoundingClientRect();this.contextMenu.style.left=`${containerRect.left+(containerRect.width-contextMenuRect.width)/2}px`;this.contextMenu.style.top=`${containerRect.bottom}px`;}contexMenuIsOpen(){return this.contextMenu.classList.contains(this.contextMenuVisibleClass)}}
4
+ function createFocusTrap(event,focusableElements,escapeCallBack,tabCallBack){if(escapeCallBack===void 0)escapeCallBack=event=>{};if(tabCallBack===void 0)tabCallBack=event=>{};const firstFocusableElement=focusableElements[0];const lastFocusableElement=focusableElements[focusableElements.length-1];if(event.key==="Escape"){escapeCallBack(event);}if(event.key==="Tab"){if(event.shiftKey){if(document.activeElement===firstFocusableElement){event.preventDefault();lastFocusableElement.focus();tabCallBack(event);}}else if(document.activeElement===lastFocusableElement){event.preventDefault();firstFocusableElement.focus();tabCallBack(event);}}}
5
+
6
+ class StatefulComponent{constructor(element,initialState={}){this.element=element;this.initialState=initialState;this.state=this.#setupState(this.initialState);this.stateHandlers=new Map;this.#init();}#init(){this.setupState=this.#setupState.bind(this);this.registerStateHandler=this.registerStateHandler.bind(this);}registerStateHandler(prop,handler){if(!this.stateHandlers.has(prop)){this.stateHandlers.set(prop,[]);}this.stateHandlers.get(prop).push(handler);}#setupState(initialState){if(initialState===void 0)initialState={};return new Proxy(initialState,{set:(target,prop,value)=>{if(value!==target[prop]){target[prop]=value;const handlers=this.stateHandlers.get(prop);if(handlers){handlers.forEach(handler=>handler(value,prop));}}return true}})}}
7
+
8
+ class LanguageToggle extends StatefulComponent{constructor(element){const initialState={contextMenuIsOpen:false};super(element,initialState);this.init=()=>{this.cacheDomReferences().enableHandlers().registerStateHandlers();return this};this.cacheDomReferences=()=>{this.contextMenuTemplate=this.element.querySelector(`#${this.prefix}--context-menu--template`);this.contextButton=this.element.querySelector(`.${this.prefix}--container`);return this};this.registerStateHandlers=()=>{this.registerStateHandler("contextMenuIsOpen",value=>{if(value){this.handleOpenContextMenu();}else {this.handleCloseContextMenu();}});return this};this.enableHandlers=()=>{this.contextButton.addEventListener("click",this.handleClick);return this};this.handleClick=e=>{e.stopPropagation();this.state.contextMenuIsOpen=!this.state.contextMenuIsOpen;return this};this.handleOpenContextMenu=()=>{this.contextMenuContent=this.contextMenuTemplate.content.cloneNode(true);document.body.appendChild(this.contextMenuContent);this.contextMenu=document.body.querySelector(`.${this.prefix}--context-menu`);this.positionContextMenu();this.contextMenu.classList.add(this.contextMenuVisibleClass);this.contextButton.setAttribute("aria-expanded","true");window.addEventListener("click",this.handleOutsideClick);window.addEventListener("resize",this.positionContextMenu);this.handleTabNavigation();return this};this.handleCloseContextMenu=()=>{this.contextMenu.classList.remove(this.contextMenuVisibleClass);this.contextMenu.remove();this.contextButton.setAttribute("aria-expanded","false");this.contextMenu.removeEventListener("keydown",this.handleFocusTrap);window.removeEventListener("click",this.handleOutsideClick);window.removeEventListener("resize",this.positionContextMenu);};this.positionContextMenu=()=>{const containerRect=this.element.getBoundingClientRect();const contextMenuRect=this.contextMenu.getBoundingClientRect();this.contextMenu.style.left=`${containerRect.left+(containerRect.width-contextMenuRect.width)/2}px`;this.contextMenu.style.top=`${containerRect.bottom}px`;};this.handleOutsideClick=event=>{if(this.state.contextMenuIsOpen&&!this.element?.contains(event.target)&&!this.contextMenu?.contains(event.target)){this.state.contextMenuIsOpen=false;}};this.handleFocusTrap=event=>{const focusableElements=Array.from(this.contextMenu.querySelectorAll("a"));createFocusTrap(event,focusableElements,()=>{this.state.contextMenuIsOpen=false;this.contextButton.focus();});};this.handleTabNavigation=()=>{setTimeout(()=>{this.contextMenu.focus();this.contextMenu.addEventListener("keydown",this.handleFocusTrap);},100);};this.prefix=`${this.element.dataset.prefix}--language-toggle`;this.contextMenuVisibleClass=`${this.prefix}--context-menu__open`;this.contextMenuTemplate=null;this.contextMenuContent=null;this.contextMenu=null;this.init();}}
5
9
 
6
10
  Drupal.behaviors.languagetoggle={attach(){Array.prototype.forEach.call(document.querySelectorAll(`[data-loadcomponent="LanguageToggle"]`),element=>{if(!element.dataset.jsProcessed){new LanguageToggle(element);element.dataset.jsProcessed=true;}});}};
7
11
 
@@ -4,6 +4,11 @@ languagetoggle:
4
4
  label: Language Toggle
5
5
  description: The Language Toggle allows users to switch between different language versions of a website.
6
6
  fields:
7
+ className:
8
+ type: string
9
+ label: Class Name
10
+ description: Optional additional CSS class to be added to the component
11
+ preview: "custom-class"
7
12
  language:
8
13
  label: Language
9
14
  type: string
@@ -3,15 +3,16 @@
3
3
  {% set hideicon = hideicon|default(false) %}
4
4
  {% set theme = theme|default("light") %}
5
5
 
6
- <div class="{{ prefix }}--language-toggle {{ prefix }}--language-toggle__theme__{{ theme }}" data-prefix="ilo" data-loadcomponent="LanguageToggle">
7
- <button class="{{ prefix }}--language-toggle--container" aria-expanded="false" aria-controls="ilo--language-toggle--context-menu">
8
- {% if not hideicon %}
9
- <span class="{{ prefix }}--language-toggle--icon"></span>
10
- {% endif %}
11
- <span class="{{ prefix }}--language-toggle--action">{{ language }}</span>
12
- <span class="{{ prefix }}--language-toggle--arrow"></span>
13
- </button>
14
- <div role="menu" class="{{ prefix }}--language-toggle--context-menu" id="ilo--language-toggle--context-menu" hidden>
6
+ <div class="{{ prefix }}--language-toggle {{ prefix }}--language-toggle__theme__{{ theme }} {{ className|default('') }}" data-prefix="ilo" data-loadcomponent="LanguageToggle">
7
+ <button class="{{ prefix }}--language-toggle--container" aria-expanded="false" aria-controls="ilo--language-toggle--context-menu">
8
+ {% if not hideicon %}
9
+ <span class="{{ prefix }}--language-toggle--icon"></span>
10
+ {% endif %}
11
+ <span class="{{ prefix }}--language-toggle--action">{{ language }}</span>
12
+ <span class="{{ prefix }}--language-toggle--arrow"></span>
13
+ </button>
14
+ <template id="ilo--language-toggle--context-menu--template">
15
+ <div class="{{ prefix }}--language-toggle--context-menu" id="ilo--language-toggle--context-menu" tabindex="0">
15
16
  {% if links %}
16
17
  {% include "@components/contextmenu/contextmenu.twig" with {
17
18
  links: links,
@@ -19,4 +20,6 @@
19
20
  } only %}
20
21
  {% endif %}
21
22
  </div>
23
+ </template>
22
24
  </div>
25
+
@@ -4,6 +4,11 @@ languagetoggle:
4
4
  label: Language Toggle
5
5
  description: The Language Toggle allows users to switch between different language versions of a website.
6
6
  fields:
7
+ className:
8
+ type: string
9
+ label: Class Name
10
+ description: Optional additional CSS class to be added to the component
11
+ preview: "custom-class"
7
12
  language:
8
13
  label: Language
9
14
  type: string
@@ -0,0 +1,34 @@
1
+ logo:
2
+ namespace: Components/Logo
3
+ use: "@components/logo/logo.twig"
4
+ label: Logo
5
+ description: A logo component that can optionally be wrapped in a link
6
+ fields:
7
+ src:
8
+ type: string
9
+ label: Image Source
10
+ description: The source URL of the logo image
11
+ preview: "images/logo_en_horizontal_blue_dark.svg"
12
+ alt:
13
+ type: string
14
+ label: Alt Text
15
+ description: Alternative text for the logo image
16
+ preview: "ILO Logo"
17
+ link:
18
+ type: object
19
+ label: Link
20
+ description: Optional link configuration for the logo
21
+ preview:
22
+ href: "https://www.ilo.org"
23
+ label: "ILO Live Homepage"
24
+ className:
25
+ type: string
26
+ label: Wrapper Class
27
+ description: CSS class for the logo wrapper element
28
+ preview: "ilo--logo"
29
+ imgClassName:
30
+ type: string
31
+ label: Image Class
32
+ description: CSS class for the logo image element
33
+ preview: "ilo--logo__img"
34
+ visibility: storybook
@@ -0,0 +1,27 @@
1
+ {# logo.twig #}
2
+
3
+ {% set prefix = prefix|default('ilo') %}
4
+ {% set base_class = className|default('ilo' ~ '--logo') %}
5
+ {% set img_class = imgClassName|default(base_class ~ '__img') %}
6
+
7
+ {% if link and link.href %}
8
+ <a
9
+ href="{{ link.href }}"
10
+ class="{{ base_class }} {{ base_class }}--link"
11
+ {% if link.label %}
12
+ aria-label="{{ link.label }}"
13
+ {% endif %}
14
+ >
15
+ <img
16
+ class="{{ img_class }}"
17
+ src="{{ src }}"
18
+ alt="{{ alt }}"
19
+ >
20
+ </a>
21
+ {% else %}
22
+ <img
23
+ class="{{ base_class }} {{ img_class }}"
24
+ src="{{ src }}"
25
+ alt="{{ alt }}"
26
+ >
27
+ {% endif %}
@@ -0,0 +1,34 @@
1
+ logo:
2
+ namespace: Components/Logo
3
+ use: "@components/logo/logo.twig"
4
+ label: Logo
5
+ description: A logo component that can optionally be wrapped in a link
6
+ fields:
7
+ src:
8
+ type: string
9
+ label: Image Source
10
+ description: The source URL of the logo image
11
+ preview: "images/logo_en_horizontal_blue_dark.svg"
12
+ alt:
13
+ type: string
14
+ label: Alt Text
15
+ description: Alternative text for the logo image
16
+ preview: "ILO Logo"
17
+ link:
18
+ type: object
19
+ label: Link
20
+ description: Optional link configuration for the logo
21
+ preview:
22
+ href: "https://www.ilo.org"
23
+ label: "ILO Live Homepage"
24
+ className:
25
+ type: string
26
+ label: Wrapper Class
27
+ description: CSS class for the logo wrapper element
28
+ preview: "ilo--logo"
29
+ imgClassName:
30
+ type: string
31
+ label: Image Class
32
+ description: CSS class for the logo image element
33
+ preview: "ilo--logo__img"
34
+ visibility: storybook
@@ -0,0 +1,15 @@
1
+ {# navdropdown.twig #}
2
+
3
+ {% set prefix = prefix|default("ilo") %}
4
+ {% set base_class = 'ilo' ~ "--nav-dropdown" %}
5
+
6
+ <template id="{{ prefix }}--nav-dropdown__template">
7
+ <div class="{{ base_class }} {{ className|default("") }}" id="{{ base_class }}" tabindex="0">
8
+ <div class="{{ base_class }}__container">
9
+ {% include "@components/nav/desktop/navmenugrid.twig" with {
10
+ menu: menu,
11
+ prefix: prefix
12
+ } only %}
13
+ </div>
14
+ </div>
15
+ </template>
@@ -0,0 +1,27 @@
1
+ {# navmenu.twig #}
2
+
3
+ {% set prefix = prefix|default('ilo') %}
4
+ {% set base_class = 'ilo' ~ '--nav-menu' %}
5
+
6
+ <div class="{{ base_class }} {{ className }}">
7
+ <ul class="{{ base_class }}__list">
8
+ {% for item in menu %}
9
+ <li class="{{ base_class }}__item {{ item.className }}">
10
+ <a href="{{ item.href }}" class="{{ base_class }}__link">
11
+ {{ item.label }}
12
+ </a>
13
+ </li>
14
+ {% endfor %}
15
+ </ul>
16
+ {% if more %}
17
+ <button
18
+ class="{{ base_class }}__more"
19
+ aria-expanded="false"
20
+ aria-haspopup="menu"
21
+ aria-controls="{{ prefix }}--nav-dropdown"
22
+ id="{{ prefix }}--nav-dropdown__button">
23
+ {{ more.label }}
24
+ <span class="{{ base_class }}__more-icon"></span>
25
+ </button>
26
+ {% endif %}
27
+ </div>
@@ -0,0 +1,16 @@
1
+ {# navmenugrid.twig #}
2
+
3
+ {% set prefix = prefix|default('ilo') %}
4
+ {% set base_class = 'ilo' ~ '--nav-menu-grid' %}
5
+
6
+ <div class="{{ base_class }}">
7
+ {% for chunk in menu|batch(5) %}
8
+ <div class="{{ base_class }}__column">
9
+ {% for item in chunk %}
10
+ <a href="{{ item.href }}" class="{{ base_class }}__item">
11
+ {{ item.label }}
12
+ </a>
13
+ {% endfor %}
14
+ </div>
15
+ {% endfor %}
16
+ </div>
@@ -0,0 +1,26 @@
1
+ {% set prefix = prefix|default("ilo") %}
2
+ {% set base_class = 'ilo' ~ "--nav-mobile-drawer" %}
3
+ {% set mobile_class = 'ilo' ~ "--nav-mobile" %}
4
+ {% set id = id|default("primary") %}
5
+ {% set className = className|default("") %}
6
+
7
+ <template id="{{ base_class }}__template__{{ id }}">
8
+ <div class="{{ base_class }} {{ className }}" id="{{ base_class }}__{{ id }}" inert>
9
+ <div class="{{ base_class }}__header">
10
+ <div class="{{ base_class }}__header-main">
11
+ {{ block("mobile_drawer_header") }}
12
+ </div>
13
+ <button class="{{ base_class }}__header-close" aria-label="Close navigation">
14
+ <span class="{{ base_class }}__header-close__icon"></span>
15
+ </button>
16
+ </div>
17
+ <div class="{{ base_class }}__container">
18
+ <div class="{{ base_class }}__widgets">
19
+ <div class="{{ mobile_class}}__widgets">
20
+ {{ block("mobile_drawer_widgets") }}
21
+ </div>
22
+ </div>
23
+ {{ block("mobile_drawer_content") }}
24
+ </div>
25
+ </div>
26
+ </template>
@@ -0,0 +1,51 @@
1
+ {% extends "@components/nav/mobile/mobiledrawer_layout.twig" %}
2
+
3
+ {% block mobile_drawer_header %}
4
+ <div class="{{ mobile_class }}__branding">
5
+ <div class="{{ mobile_class }}__logo">
6
+ <img src="{{ branding.logo.drawer }}" alt="{{ branding.logo.alt }}">
7
+ </div>
8
+ </div>
9
+ {% endblock %}
10
+
11
+ {% block mobile_drawer_widgets %}
12
+ {% if widgets.search %}
13
+ <a class="{{ mobile_class }}__widgets-search" href="{{ widgets.search.url }}" aria-label="{{ widgets.search.label }}">
14
+ <span class="{{ mobile_class }}__widgets-search__label">
15
+ {{ widgets.search.label }}
16
+ </span>
17
+ <span class="{{ mobile_class }}__widgets-search__icon" />
18
+ </a>
19
+ {% endif %}
20
+
21
+ {% if widgets.link %}
22
+ <a class="{{ mobile_class }}__widgets-link" href="{{ widgets.link.href }}">{{ widgets.link.label }}</a>
23
+ {% endif %}
24
+
25
+ {% if widgets.language %}
26
+ <button
27
+ class="{{ mobile_class }}__widgets-language"
28
+ aria-label="{{ widgets.language.label }}"
29
+ aria-expanded="false"
30
+ aria-haspopup="menu"
31
+ aria-controls="{{ prefix }}--nav-mobile-drawer__languages"
32
+ id="{{ prefix }}--nav-mobile-drawer__languages__button"
33
+ >
34
+ <span class="{{ mobile_class }}__widgets-language__label">
35
+ {{ widgets.language.label }}: {{ widgets.language.language }}
36
+ </span>
37
+ <span class="{{ mobile_class }}__widgets-language__icon" />
38
+ </button>
39
+ {% endif %}
40
+ {% endblock %}
41
+
42
+ {% block mobile_drawer_content %}
43
+ {% include "@components/nav/mobile/mobilemenulist.twig" with {
44
+ menu: menu,
45
+ prefix: prefix,
46
+ } only %}
47
+
48
+ {% if showMoreButton %}
49
+ <button class="{{ mobile_class }}__more" aria-expanded="false" aria-controls="more-items" aria-label="More items">More<span class="{{ mobile_class }}__more__icon"></span></button>
50
+ {% endif %}
51
+ {% endblock %}
@@ -0,0 +1,25 @@
1
+ {% extends "@components/nav/mobile/mobiledrawer_layout.twig" %}
2
+ {% set prefix = prefix|default("ilo") %}
3
+ {% set menu_home = menu_home|default("Menu home") %}
4
+ {% set menu_title = title|default("Title") %}
5
+ {% set isActiveLabel = isActiveLabel|default(null) %}
6
+
7
+ {% block mobile_drawer_header %}
8
+ <div class="{{ mobile_class}}-drawer__header-main">
9
+ <button class="{{ mobile_class}}__nested__header" aria-label="Back to menu">
10
+ <span class="{{ mobile_class}}__nested__header__icon"></span>{{ menu_home }}
11
+ </button>
12
+ </div>
13
+ {% endblock %}
14
+
15
+ {% block mobile_drawer_widgets %}
16
+ <span class="{{ mobile_class}}__nested__title">{{ menu_title }}</span>
17
+ {% endblock %}
18
+
19
+ {% block mobile_drawer_content %}
20
+ {% include "@components/nav/mobile/mobilemenulist.twig" with {
21
+ menu: menu,
22
+ prefix: prefix,
23
+ isActiveLabel: isActiveLabel,
24
+ } only %}
25
+ {% endblock %}
@@ -0,0 +1,20 @@
1
+ {# mobilemenulist.twig #}
2
+
3
+ {# List items can have url or href attribute #}
4
+
5
+ {% set prefix = prefix|default("ilo") %}
6
+ {% set base_class = 'ilo' ~ "--nav-mobile-menu" %}
7
+ {% set isActiveLabel = isActiveLabel|default(null) %}
8
+
9
+ <ul class="{{ base_class }}">
10
+ {% for item in menu %}
11
+ {% set url = item.url|default(item.href|default('')) %}
12
+ {% set isActive = isActiveLabel is not null ? isActiveLabel == item.label : item.isActive %}
13
+ <li class="{{ base_class }}__item {% if isActive %}{{ base_class }}__item--active{% endif %}">
14
+ <a href="{{ url }}" class="{{ base_class }}__link">
15
+ {{ item.label }}
16
+ </a>
17
+ {% if isActive %}<span class="{{ base_class }}__marked"></span>{% endif %}
18
+ </li>
19
+ {% endfor %}
20
+ </ul>
@@ -0,0 +1,16 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ function createFocusTrap(event,focusableElements,escapeCallBack,tabCallBack){if(escapeCallBack===void 0)escapeCallBack=event=>{};if(tabCallBack===void 0)tabCallBack=event=>{};const firstFocusableElement=focusableElements[0];const lastFocusableElement=focusableElements[focusableElements.length-1];if(event.key==="Escape"){escapeCallBack(event);}if(event.key==="Tab"){if(event.shiftKey){if(document.activeElement===firstFocusableElement){event.preventDefault();lastFocusableElement.focus();tabCallBack(event);}}else if(document.activeElement===lastFocusableElement){event.preventDefault();firstFocusableElement.focus();tabCallBack(event);}}}
5
+
6
+ const BREAKPOINTS={xs:320,sm:414,md:610,lg:1024,xl:1140,xxl:1168};function getCurrentBreakpoint(){const width=window.innerWidth;if(width<BREAKPOINTS.xs)return "xs";if(width<BREAKPOINTS.sm)return "sm";if(width<BREAKPOINTS.md)return "md";if(width<BREAKPOINTS.lg)return "lg";if(width<BREAKPOINTS.xl)return "xl";return "xxl"}function createBreakpointObserver(callback){let currentBreakpoint=getCurrentBreakpoint();let timeoutId=null;let observer=null;function checkBreakpoint(){timeoutId=window.requestAnimationFrame(()=>{const newBreakpoint=getCurrentBreakpoint();if(newBreakpoint!==currentBreakpoint){currentBreakpoint=newBreakpoint;callback(newBreakpoint);}});}function start(){observer=new ResizeObserver(()=>{checkBreakpoint();});observer.observe(document.body);callback(currentBreakpoint);}function stop(){if(observer){observer.disconnect();observer=null;}if(timeoutId){window.cancelAnimationFrame(timeoutId);timeoutId=null;}}return {start,stop,getCurrentBreakpoint:()=>currentBreakpoint}}
7
+
8
+ class StatefulComponent{constructor(element,initialState={}){this.element=element;this.initialState=initialState;this.state=this.#setupState(this.initialState);this.stateHandlers=new Map;this.#init();}#init(){this.setupState=this.#setupState.bind(this);this.registerStateHandler=this.registerStateHandler.bind(this);}registerStateHandler(prop,handler){if(!this.stateHandlers.has(prop)){this.stateHandlers.set(prop,[]);}this.stateHandlers.get(prop).push(handler);}#setupState(initialState){if(initialState===void 0)initialState={};return new Proxy(initialState,{set:(target,prop,value)=>{if(value!==target[prop]){target[prop]=value;const handlers=this.stateHandlers.get(prop);if(handlers){handlers.forEach(handler=>handler(value,prop));}}return true}})}}
9
+
10
+ class Nav extends StatefulComponent{constructor(element){const initialState={dropDownIsOpen:false,isDesktop:true};super(element,initialState);this.startBreakpointObserver=()=>{this.breakpointObserver.start();return this};this.renderClientContent=()=>{const dropdownTemplate=this.element.querySelector(`#${this.prefix}--nav-dropdown__template`);if(dropdownTemplate){const dropdownContent=dropdownTemplate.content.cloneNode(true);document.body.appendChild(dropdownContent);}return this};this.cacheDomReferences=()=>{this.dropdownButton=this.element.querySelector(`.${this.prefix}--nav-menu__more`);this.dropdown=document.body.querySelector(`.${this.prefix}--nav-dropdown`);return this};this.enableHandlers=()=>{if(this.dropdownButton){this.dropdownButton.addEventListener("click",this.handleDropdownClick);}return this};this.registerStateHandlers=()=>{this.registerStateHandler("dropDownIsOpen",value=>{if(value){this.handleOpenDropdown();}else {this.handleCloseDropdown();}});this.registerStateHandler("isDesktop",this.handleBreakpointChange);return this};this.handleBreakpointChange=isDesktop=>{if(!isDesktop){this.state.dropDownIsOpen=false;}};this.handleOpenDropdown=()=>{this.handleResizeDropdown();this.resizeObserver.observe(this.element);window.addEventListener("click",this.handleOutsideClick);this.dropdown?.classList.add(`${this.prefix}--nav-dropdown--open`);this.dropdownButton.setAttribute("aria-expanded","true");this.dropdownButton?.classList.add(`${this.prefix}--nav-menu__more--open`);this.handleTabNavigation();};this.handleCloseDropdown=()=>{this.dropdown?.classList.remove(`${this.prefix}--nav-dropdown--open`);this.dropdownButton?.classList.remove(`${this.prefix}--nav-menu__more--open`);this.dropdownButton.setAttribute("aria-expanded","false");this.resizeObserver.disconnect();window.removeEventListener("click",this.handleOutsideClick);};this.handleResizeDropdown=()=>{this.dropdown.style.width=`${this.element.offsetWidth}px`;this.dropdown.style.left=`${this.element.getBoundingClientRect().left}px`;this.dropdown.style.top=`${this.element.getBoundingClientRect().bottom}px`;};this.handleDropdownClick=()=>{this.state.dropDownIsOpen=!this.state.dropDownIsOpen;};this.handleOutsideClick=event=>{if(this.state.dropDownIsOpen&&!this.element?.contains(event.target)&&!this.dropdown?.contains(event.target)){this.state.dropDownIsOpen=false;}};this.handleFocusTrap=event=>{createFocusTrap(event,this.dropdown.querySelectorAll("a"),()=>{this.state.dropDownIsOpen=false;this.dropdownButton.focus();});};this.handleTabNavigation=()=>{setTimeout(()=>{this.dropdown.focus();this.dropdown.addEventListener("keydown",this.handleFocusTrap);},100);};this.prefix=this.element.dataset.prefix;this.dropdown=null;this.breakpointObserver=createBreakpointObserver(breakpoint=>{this.state.isDesktop=["xl","xxl"].includes(breakpoint);});this.resizeObserver=new ResizeObserver(()=>{if(this.state.dropDownIsOpen){this.handleResizeDropdown();}});this.init();}init(){this.renderClientContent().cacheDomReferences().enableHandlers().registerStateHandlers().startBreakpointObserver();return this}}
11
+
12
+ class MobileNav extends StatefulComponent{constructor(element){const initialState={isMobile:false,mobDrawerIsOpen:false,languagesMobDrawerIsOpen:false,moreMobDrawerIsOpen:false};super(element,initialState);this.init=()=>{this.renderClientContent().cacheDomReferences().enableHandlers().registerStateHandlers().startBreakpointObserver();return this};this.startBreakpointObserver=()=>{this.breakpointObserver.start();return this};this.renderClientContent=()=>{const mobileDrawerTemplates=[`${this.prefix}--nav-mobile-drawer__template__main`,`${this.prefix}--nav-mobile-drawer__template__languages`,`${this.prefix}--nav-mobile-drawer__template__more`];mobileDrawerTemplates.forEach(templateId=>{const template=this.element.querySelector(`#${templateId}`);if(template){const content=template.content.cloneNode(true);document.body.appendChild(content);}});return this};this.cacheDomReferences=()=>{this.burger=this.element.querySelector(`[class$="burger"]`);this.mobileDrawer=document.body.querySelector(`#${this.prefix}--nav-mobile-drawer__main`);this.menuHomeButtons=document.body.querySelectorAll(`.${this.prefix}--nav-mobile__nested__header`);this.mobileDrawerCloseButtons=document.body.querySelectorAll(`.${this.prefix}--nav-mobile-drawer__header-close`);this.mobileLanguageButton=this.mobileDrawer.querySelector(`.${this.prefix}--nav-mobile__widgets-language`);this.languagesMobileDrawer=document.body.querySelector(`#${this.prefix}--nav-mobile-drawer__languages`);this.moreMobileDrawer=document.body.querySelector(`#${this.prefix}--nav-mobile-drawer__more`);this.mobileMoreButton=this.mobileDrawer.querySelector(`.${this.prefix}--nav-mobile__more`);return this};this.enableHandlers=()=>{this.burger.addEventListener("click",this.handleBurgerClick);if(this.mobileLanguageButton){this.mobileLanguageButton.addEventListener("click",this.handleLanguageButtonClick);}if(this.mobileMoreButton){this.mobileMoreButton.addEventListener("click",this.handleMoreButtonClick);}this.mobileDrawerCloseButtons.forEach(button=>{button.addEventListener("click",this.handleMobileDrawerClose);});this.menuHomeButtons.forEach(button=>{button.addEventListener("click",this.handleMenuHomeButtonClick);});return this};this.registerStateHandlers=()=>{this.registerStateHandler("mobDrawerIsOpen",value=>{if(value){this.handleOpenMobileDrawer();}else {this.handleCloseMobileDrawer();}});this.registerStateHandler("languagesMobDrawerIsOpen",value=>{if(value){this.handleOpenLanguagesMobileDrawer();}else {this.handleCloseLanguagesMobileDrawer();}});this.registerStateHandler("moreMobDrawerIsOpen",value=>{if(value){this.handleOpenMoreMobileDrawer();}else {this.handleCloseMoreMobileDrawer();}});this.registerStateHandler("isMobile",this.handleBreakpointChange);return this};this.handleBurgerClick=()=>{this.burger.setAttribute("aria-expanded","true");this.state.mobDrawerIsOpen=true;};this.handleLanguageButtonClick=()=>{this.state.languagesMobDrawerIsOpen=true;};this.handleMoreButtonClick=()=>{this.state.moreMobDrawerIsOpen=true;};this.handleMenuHomeButtonClick=()=>{this.state.languagesMobDrawerIsOpen=false;this.state.moreMobDrawerIsOpen=false;};this.handleMobileDrawerClose=()=>{this.state.mobDrawerIsOpen=false;this.state.languagesMobDrawerIsOpen=false;this.state.moreMobDrawerIsOpen=false;};this.handleOpenMobileDrawer=()=>{this.mobileDrawer.inert=false;this.mobileDrawer?.classList.add(`${this.prefix}--nav-mobile-drawer--open`);};this.handleCloseMobileDrawer=()=>{this.mobileDrawer.inert=true;this.mobileDrawer?.classList.remove(`${this.prefix}--nav-mobile-drawer--open`);this.burger.setAttribute("aria-expanded","false");};this.handleOpenLanguagesMobileDrawer=()=>{this.languagesMobileDrawer.inert=false;this.mobileDrawer.inert=true;this.mobileLanguageButton.setAttribute("aria-expanded","true");this.languagesMobileDrawer.classList.add(`${this.prefix}--nav-mobile-drawer--open`);};this.handleCloseLanguagesMobileDrawer=()=>{this.languagesMobileDrawer.inert=true;this.mobileDrawer.inert=false;this.mobileLanguageButton.setAttribute("aria-expanded","false");this.languagesMobileDrawer?.classList.remove(`${this.prefix}--nav-mobile-drawer--open`);};this.handleOpenMoreMobileDrawer=()=>{this.moreMobileDrawer.inert=false;this.mobileDrawer.inert=true;this.mobileMoreButton.setAttribute("aria-expanded","true");this.moreMobileDrawer.classList.add(`${this.prefix}--nav-mobile-drawer--open`);};this.handleCloseMoreMobileDrawer=()=>{this.moreMobileDrawer.inert=true;this.mobileDrawer.inert=false;this.mobileMoreButton.setAttribute("aria-expanded","false");this.moreMobileDrawer?.classList.remove(`${this.prefix}--nav-mobile-drawer--open`);};this.handleBreakpointChange=isMobile=>{if(!isMobile){this.state.mobDrawerIsOpen=false;this.state.languagesMobDrawerIsOpen=false;this.state.moreMobDrawerIsOpen=false;}};this.prefix=this.element.dataset.prefix;this.mobileDrawer=null;this.mobileDrawerClose=null;this.languagesMobileDrawer=null;this.languagesMobileDrawerClose=null;this.mobileLanguageButton=null;this.burger=null;this.breakpointObserver=createBreakpointObserver(breakpoint=>{this.state.isMobile=!["xl","xxl"].includes(breakpoint);});this.init();}}
13
+
14
+ Drupal.behaviors.nav={attach(){Array.prototype.forEach.call(document.querySelectorAll(`[data-loadcomponent="Nav"]`),element=>{if(!element.dataset.jsProcessed){new Nav(element);new MobileNav(element);element.dataset.jsProcessed=true;}});}};
15
+
16
+ })();
@@ -0,0 +1,108 @@
1
+ nav:
2
+ namespace: Components/Nav
3
+ use: "@components/nav/nav.twig"
4
+ label: SubsiteNav
5
+ description: A complex navigation component with branding, widgets, and menu sections
6
+ fields:
7
+ branding:
8
+ type: object
9
+ label: Branding
10
+ description: Branding elements including logo and name
11
+ required: true
12
+ preview:
13
+ logo:
14
+ main: "images/ilo-live-logo-en-dark.png"
15
+ mobile: "images/ilo-live-logo-en-light.png"
16
+ drawer: "images/ilo-live-logo-en-dark.png"
17
+ alt: "ILO Logo"
18
+ link:
19
+ href: "https://live.ilo.org"
20
+ label: "ILO Live Homepage"
21
+ tag:
22
+ main: "Advancing social justice, promoting decent work"
23
+ sub: "ILO is a specialized agency of the United Nations"
24
+ widgets:
25
+ type: object
26
+ label: Widgets
27
+ description: Widgets section including link and language toggle
28
+ preview:
29
+ link:
30
+ href: "https://www.ilo.org"
31
+ label: "Go to main ILO website"
32
+ language:
33
+ label: "Language"
34
+ language: "English"
35
+ links:
36
+ - label: "English"
37
+ url: "https://www.ilo.org/en"
38
+ - label: "Français"
39
+ url: "https://www.ilo.org/fr"
40
+ - label: "Español"
41
+ url: "https://www.ilo.org/es"
42
+ - label: "العربية"
43
+ url: "https://www.ilo.org/ar"
44
+ - label: "中文"
45
+ url: "https://www.ilo.org/ru"
46
+ - label: "Português"
47
+ url: "https://www.ilo.org/pt"
48
+ - label: "Italiano"
49
+ url: "https://www.ilo.org/it"
50
+ search:
51
+ label: "Search"
52
+ url: "https://www.ilo.org/search"
53
+ facadeItems:
54
+ type: object
55
+ label: Facade Menu Items
56
+ description: Primary menu items displayed in the main navigation
57
+ preview:
58
+ - href: "https://www.ilo.org/"
59
+ label: "Menu Item 1"
60
+ isActive: true
61
+ className: "test"
62
+ - href: "https://www.ilo.org/"
63
+ label: "Menu Item 2"
64
+ isActive: false
65
+ className: "test"
66
+ - href: "https://www.ilo.org/"
67
+ label: "Menu Item 3"
68
+ isActive: false
69
+ className: "test"
70
+ moreItems:
71
+ type: object
72
+ label: More Menu Items
73
+ description: Additional menu items displayed in the dropdown
74
+ preview:
75
+ - href: "https://www.ilo.org/more1"
76
+ label: "More Item 1"
77
+ - href: "https://www.ilo.org/more2"
78
+ label: "More Item 2"
79
+ - href: "https://www.ilo.org/more3"
80
+ label: "More Item 3"
81
+ - href: "https://www.ilo.org/more4"
82
+ label: "More Item 4"
83
+ - href: "https://www.ilo.org/more5"
84
+ label: "More Item 5"
85
+ - href: "https://www.ilo.org/more6"
86
+ label: "More Item 6"
87
+ - href: "https://www.ilo.org/more7"
88
+ label: "More Item 7"
89
+ - href: "https://www.ilo.org/more8"
90
+ label: "More Item 8"
91
+ labels:
92
+ type: object
93
+ label: Labels
94
+ description: Text labels used throughout the navigation
95
+ preview:
96
+ more: "More"
97
+ language: "Language"
98
+ home: "Menu home"
99
+ settings:
100
+ navtype:
101
+ label: Type
102
+ type: select
103
+ description: What kind of navigation component to render
104
+ options:
105
+ compact: compact
106
+ complex: complex
107
+ preview: "complex"
108
+ visibility: storybook
@@ -0,0 +1,5 @@
1
+ {% if navtype == 'compact' %}
2
+ {% include '@components/nav/nav_compact.twig' %}
3
+ {% else %}
4
+ {% include '@components/nav/nav_complex.twig' %}
5
+ {% endif %}