@ilo-org/twig 1.7.3 → 1.8.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.
Files changed (30) hide show
  1. package/dist/components/accordion/accordion-item.twig +2 -1
  2. package/dist/components/contextmenu/contextmenu.component.yml +8 -0
  3. package/dist/components/contextmenu/contextmenu.twig +6 -4
  4. package/dist/components/contextmenu/contextmenu.wingsuit.yml +8 -0
  5. package/dist/components/languagetoggle/languagetoggle.behavior.js +5 -1
  6. package/dist/components/languagetoggle/languagetoggle.component.yml +5 -0
  7. package/dist/components/languagetoggle/languagetoggle.twig +12 -9
  8. package/dist/components/languagetoggle/languagetoggle.wingsuit.yml +5 -0
  9. package/dist/components/nav/desktop/navdropdown.twig +15 -0
  10. package/dist/components/nav/desktop/navmenu.twig +27 -0
  11. package/dist/components/nav/desktop/navmenugrid.twig +16 -0
  12. package/dist/components/nav/mobile/mobiledrawer_layout.twig +26 -0
  13. package/dist/components/nav/mobile/mobiledrawer_main.twig +51 -0
  14. package/dist/components/nav/mobile/mobiledrawer_nested.twig +25 -0
  15. package/dist/components/nav/mobile/mobilemenulist.twig +20 -0
  16. package/dist/components/nav/nav.behavior.js +16 -0
  17. package/dist/components/nav/nav.component.yml +105 -0
  18. package/dist/components/nav/nav.twig +5 -0
  19. package/dist/components/nav/nav.wingsuit.yml +105 -0
  20. package/dist/components/nav/nav_compact.twig +102 -0
  21. package/dist/components/nav/nav_complex.twig +125 -0
  22. package/dist/components/radio/radio.twig +0 -1
  23. package/dist/styles/components/languagetoggle.css +1 -1
  24. package/dist/styles/components/scorecard.css +1 -1
  25. package/dist/styles/global.css.map +1 -1
  26. package/dist/styles/index.css +1 -1
  27. package/dist/styles/index.css.map +1 -1
  28. package/dist/styles/monorepo.css +1 -1
  29. package/dist/styles/monorepo.css.map +1 -1
  30. package/package.json +3 -3
@@ -4,12 +4,13 @@
4
4
  {% set id = id %}
5
5
  {% set button_id = 'button-' ~ id %}
6
6
  {% set panel_id = 'panel-' ~ id %}
7
+ {% set defaultExpanded = defaultExpanded|default(false) %}
7
8
  {% set expanded_class = defaultExpanded ? 'ilo' ~ '--accordion--panel__open' : '' %}
8
9
  {% set scroll_class = scroll ? 'ilo' ~ '--accordion--panel__scroll' : '' %}
9
10
 
10
11
  <li class="ilo--accordion--item" id="{{ id }}">
11
12
  <div class="ilo--h3">
12
- <button class="ilo--accordion--button ilo--accordion--button__{{ size|default('small') }}" aria-expanded="{{ defaultExpanded }}" aria-controls="{{ panel_id }}" id="{{ button_id }}">
13
+ <button class="ilo--accordion--button ilo--accordion--button__{{ size|default('small') }}" aria-expanded="{{ defaultExpanded ? 'true' : 'false' }}" aria-controls="{{ panel_id }}" id="{{ button_id }}">
13
14
  <span class="ilo--accordion--label">{{label}}</span>
14
15
  <span class="ilo--accordion--icon"></span>
15
16
  </button>
@@ -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,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,105 @@
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
+ tag:
19
+ main: "Advancing social justice, promoting decent work"
20
+ sub: "ILO is a specialized agency of the United Nations"
21
+ widgets:
22
+ type: object
23
+ label: Widgets
24
+ description: Widgets section including link and language toggle
25
+ preview:
26
+ link:
27
+ href: "https://www.ilo.org"
28
+ label: "Go to main ILO website"
29
+ language:
30
+ label: "Language"
31
+ language: "English"
32
+ links:
33
+ - label: "English"
34
+ url: "https://www.ilo.org/en"
35
+ - label: "Français"
36
+ url: "https://www.ilo.org/fr"
37
+ - label: "Español"
38
+ url: "https://www.ilo.org/es"
39
+ - label: "العربية"
40
+ url: "https://www.ilo.org/ar"
41
+ - label: "中文"
42
+ url: "https://www.ilo.org/ru"
43
+ - label: "Português"
44
+ url: "https://www.ilo.org/pt"
45
+ - label: "Italiano"
46
+ url: "https://www.ilo.org/it"
47
+ search:
48
+ label: "Search"
49
+ url: "https://www.ilo.org/search"
50
+ facadeItems:
51
+ type: object
52
+ label: Facade Menu Items
53
+ description: Primary menu items displayed in the main navigation
54
+ preview:
55
+ - href: "https://www.ilo.org/"
56
+ label: "Menu Item 1"
57
+ isActive: true
58
+ className: "test"
59
+ - href: "https://www.ilo.org/"
60
+ label: "Menu Item 2"
61
+ isActive: false
62
+ className: "test"
63
+ - href: "https://www.ilo.org/"
64
+ label: "Menu Item 3"
65
+ isActive: false
66
+ className: "test"
67
+ moreItems:
68
+ type: object
69
+ label: More Menu Items
70
+ description: Additional menu items displayed in the dropdown
71
+ preview:
72
+ - href: "https://www.ilo.org/more1"
73
+ label: "More Item 1"
74
+ - href: "https://www.ilo.org/more2"
75
+ label: "More Item 2"
76
+ - href: "https://www.ilo.org/more3"
77
+ label: "More Item 3"
78
+ - href: "https://www.ilo.org/more4"
79
+ label: "More Item 4"
80
+ - href: "https://www.ilo.org/more5"
81
+ label: "More Item 5"
82
+ - href: "https://www.ilo.org/more6"
83
+ label: "More Item 6"
84
+ - href: "https://www.ilo.org/more7"
85
+ label: "More Item 7"
86
+ - href: "https://www.ilo.org/more8"
87
+ label: "More Item 8"
88
+ labels:
89
+ type: object
90
+ label: Labels
91
+ description: Text labels used throughout the navigation
92
+ preview:
93
+ more: "More"
94
+ language: "Language"
95
+ home: "Menu home"
96
+ settings:
97
+ navtype:
98
+ label: Type
99
+ type: select
100
+ description: What kind of navigation component to render
101
+ options:
102
+ compact: compact
103
+ complex: complex
104
+ preview: "complex"
105
+ 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 %}
@@ -0,0 +1,105 @@
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
+ tag:
19
+ main: "Advancing social justice, promoting decent work"
20
+ sub: "ILO is a specialized agency of the United Nations"
21
+ widgets:
22
+ type: object
23
+ label: Widgets
24
+ description: Widgets section including link and language toggle
25
+ preview:
26
+ link:
27
+ href: "https://www.ilo.org"
28
+ label: "Go to main ILO website"
29
+ language:
30
+ label: "Language"
31
+ language: "English"
32
+ links:
33
+ - label: "English"
34
+ url: "https://www.ilo.org/en"
35
+ - label: "Français"
36
+ url: "https://www.ilo.org/fr"
37
+ - label: "Español"
38
+ url: "https://www.ilo.org/es"
39
+ - label: "العربية"
40
+ url: "https://www.ilo.org/ar"
41
+ - label: "中文"
42
+ url: "https://www.ilo.org/ru"
43
+ - label: "Português"
44
+ url: "https://www.ilo.org/pt"
45
+ - label: "Italiano"
46
+ url: "https://www.ilo.org/it"
47
+ search:
48
+ label: "Search"
49
+ url: "https://www.ilo.org/search"
50
+ facadeItems:
51
+ type: object
52
+ label: Facade Menu Items
53
+ description: Primary menu items displayed in the main navigation
54
+ preview:
55
+ - href: "https://www.ilo.org/"
56
+ label: "Menu Item 1"
57
+ isActive: true
58
+ className: "test"
59
+ - href: "https://www.ilo.org/"
60
+ label: "Menu Item 2"
61
+ isActive: false
62
+ className: "test"
63
+ - href: "https://www.ilo.org/"
64
+ label: "Menu Item 3"
65
+ isActive: false
66
+ className: "test"
67
+ moreItems:
68
+ type: object
69
+ label: More Menu Items
70
+ description: Additional menu items displayed in the dropdown
71
+ preview:
72
+ - href: "https://www.ilo.org/more1"
73
+ label: "More Item 1"
74
+ - href: "https://www.ilo.org/more2"
75
+ label: "More Item 2"
76
+ - href: "https://www.ilo.org/more3"
77
+ label: "More Item 3"
78
+ - href: "https://www.ilo.org/more4"
79
+ label: "More Item 4"
80
+ - href: "https://www.ilo.org/more5"
81
+ label: "More Item 5"
82
+ - href: "https://www.ilo.org/more6"
83
+ label: "More Item 6"
84
+ - href: "https://www.ilo.org/more7"
85
+ label: "More Item 7"
86
+ - href: "https://www.ilo.org/more8"
87
+ label: "More Item 8"
88
+ labels:
89
+ type: object
90
+ label: Labels
91
+ description: Text labels used throughout the navigation
92
+ preview:
93
+ more: "More"
94
+ language: "Language"
95
+ home: "Menu home"
96
+ settings:
97
+ navtype:
98
+ label: Type
99
+ type: select
100
+ description: What kind of navigation component to render
101
+ options:
102
+ compact: compact
103
+ complex: complex
104
+ preview: "complex"
105
+ visibility: storybook