<div class="n7-dropdown-menu has-dropdown-menu relative">
<button class="n7-dropdown-menu__trigger n7-dropdown-trigger group w-full flex items-center font-medium hover:underline focus:n7-content-primary aria-expanded:n7-background-02 lg:aria-expanded:bg-white aria-expanded:n7-content-01 p-3 gap-2 text-sm lg:text-base text-gray-700" aria-expanded="false" aria-controls="megamenu01">
Componenti da validare<svg class="n7-icon inline-block align-middle fill-current w-5 h-5 ml-auto transition-all n7-dropdown-menu__trigger-icon" aria-hidden="true" focusable="false" role="img">
<use href="../../icons.svg#mini--chevron-down" />
</svg>
</button>
<!-- MEGAMENU CONTENT -->
<div class="n7-dropdown-menu__list hidden border n7-border-gray-01 bg-white lg:shadow-lg
lg:absolute top-full left-0 right-0 z-50
min-w-full w-auto
lg:min-w-[800px] lg:max-w-[1000px] lg:w-auto lg:right-auto
lg:shadow-xl" id="megamenu01">
<div class="overflow-hidden rounded-lg">
<div class="flex flex-col lg:flex-row">
<!-- Left Panel -->
<div class="w-full lg:w-1/3 bg-gray-50 p-4 lg:p-6 border-b n7-border-gray-01 lg:border-b-0 lg:border-r lg:border-r-gray-200">
<div class="relative lg:border-l-4 lg:border-blue-500 lg:pl-5">
<p class="font-semibold text-gray-900">Guida ai componenti</p>
<p class="mt-2 text-sm text-gray-600">Trova tutti i componenti disponibili per il tuo progetto. Organizzati per categorie e funzionalità.</p>
</div>
</div>
<!-- Right Panel with menu items -->
<div class="w-full lg:w-2/3 p-4 lg:p-6">
<ul class="n7-megamenu-list flex flex-col lg:grid lg:grid-cols-1 lg:md:grid-cols-2 list-none m-0 p-0">
<li class="n7-megamenu-item border-b n7-border-gray-01 last-of-type:border-b-0 lg:border-b-0 lg:rounded-md">
<!-- Item con sottomenu -->
<div class="n7-megamenu-dropdown group w-full lg:bg-gray-50 lg:rounded-md">
<button type="button" class="n7-megamenu-trigger w-full flex items-center p-3 text-sm font-medium text-gray-900 lg:rounded-md text-left transition-colors duration-200 hover:underline focus:n7-background-02" aria-expanded="false" data-dropdown-trigger>
<svg class="mr-3 h-5 w-5 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" data-dropdown-icon>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
<span>Layout & Structure</span>
</button>
<!-- Sottomenu -->
<ul class="n7-megamenu-submenu hidden overflow-hidden bg-gray-50 lg:bg-transparent lg:px-3 lg:py-3 list-none m-0 p-0 transition-all duration-200" data-dropdown-content>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/grid" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Grid System
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/flexbox" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Flexbox Utilities
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/container" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Container
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/spacing" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Spacing
</a>
</li>
</ul>
</div>
</li>
<li class="n7-megamenu-item border-b n7-border-gray-01 last-of-type:border-b-0 lg:border-b-0 lg:rounded-md">
<!-- Item con sottomenu -->
<div class="n7-megamenu-dropdown group w-full lg:bg-gray-50 lg:rounded-md">
<button type="button" class="n7-megamenu-trigger w-full flex items-center p-3 text-sm font-medium text-gray-900 lg:rounded-md text-left transition-colors duration-200 hover:underline focus:n7-background-02" aria-expanded="false" data-dropdown-trigger>
<svg class="mr-3 h-5 w-5 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" data-dropdown-icon>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
<span>Form Elements</span>
</button>
<!-- Sottomenu -->
<ul class="n7-megamenu-submenu hidden overflow-hidden bg-gray-50 lg:bg-transparent lg:px-3 lg:py-3 list-none m-0 p-0 transition-all duration-200" data-dropdown-content>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/inputs" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Input Fields
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/buttons" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Buttons
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/checkboxes" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Checkboxes & Radio
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/selects" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Select & Dropdown
</a>
</li>
</ul>
</div>
</li>
<li class="n7-megamenu-item border-b n7-border-gray-01 last-of-type:border-b-0 lg:border-b-0 lg:rounded-md">
<!-- Item con sottomenu -->
<div class="n7-megamenu-dropdown group w-full lg:bg-gray-50 lg:rounded-md">
<button type="button" class="n7-megamenu-trigger w-full flex items-center p-3 text-sm font-medium text-gray-900 lg:rounded-md text-left transition-colors duration-200 hover:underline focus:n7-background-02" aria-expanded="false" data-dropdown-trigger>
<svg class="mr-3 h-5 w-5 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" data-dropdown-icon>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
<span>Navigation</span>
</button>
<!-- Sottomenu -->
<ul class="n7-megamenu-submenu hidden overflow-hidden bg-gray-50 lg:bg-transparent lg:px-3 lg:py-3 list-none m-0 p-0 transition-all duration-200" data-dropdown-content>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/navigation" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Main Navigation
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/breadcrumbs" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Breadcrumbs
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/pagination" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Pagination
</a>
</li>
</ul>
</div>
</li>
<li class="n7-megamenu-item border-b n7-border-gray-01 last-of-type:border-b-0 lg:border-b-0 lg:rounded-md">
<!-- Item con sottomenu -->
<div class="n7-megamenu-dropdown group w-full lg:bg-gray-50 lg:rounded-md">
<button type="button" class="n7-megamenu-trigger w-full flex items-center p-3 text-sm font-medium text-gray-900 lg:rounded-md text-left transition-colors duration-200 hover:underline focus:n7-background-02" aria-expanded="false" data-dropdown-trigger>
<svg class="mr-3 h-5 w-5 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" data-dropdown-icon>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
<span>Content Display</span>
</button>
<!-- Sottomenu -->
<ul class="n7-megamenu-submenu hidden overflow-hidden bg-gray-50 lg:bg-transparent lg:px-3 lg:py-3 list-none m-0 p-0 transition-all duration-200" data-dropdown-content>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/cards" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Cards
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/tables" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Tables
</a>
</li>
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="/components/lists" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
Lists
</a>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% set dropdownId = id or 'dropdown' %}
{% set triggerText = label or 'Menu item' %}
<{% if listItem %}li{% else %}div{% endif %} class="n7-dropdown-menu{% if listItem %} has-dropdown-menu{% endif %}{% if isMegamenu %} has-dropdown-menu{% endif %} relative{% block classes %}{% if classes %} {{ classes }}{% endif %}{%- endblock classes -%}">
<button class="n7-dropdown-menu__trigger n7-dropdown-trigger group w-full flex items-center font-medium hover:underline focus:n7-content-primary {% if triggerAriaExpandedStyles %} {{ triggerAriaExpandedStyles }}{% endif %}{% if triggerSpacing %} {{ triggerSpacing }}{% endif %}{% if triggerFontSize %} {{ triggerFontSize }}{% endif %}{% if triggerTextColor %} {{ triggerTextColor }}{% endif %}"
aria-expanded="false"
aria-controls="{{ dropdownId }}">
{% if preContent %}
{{ preContent | safe }}
{% endif %}
{{ triggerText }}
{%- render '@icon--small', {id: 'mini--chevron-down',size: 'w-5 h-5', classes: 'ml-auto transition-all n7-dropdown-menu__trigger-icon'},true -%}
</button>
{% if isMegamenu %}
<!-- MEGAMENU CONTENT -->
<div class="n7-dropdown-menu__list hidden border n7-border-gray-01 bg-white lg:shadow-lg
lg:absolute top-full left-0 right-0 z-50
min-w-full w-auto
lg:min-w-[800px] lg:max-w-[1000px] lg:w-auto lg:right-auto
{% if listClasses %} {{ listClasses }}{% endif %}"
id="{{ dropdownId }}">
<div class="overflow-hidden rounded-lg">
<div class="flex flex-col lg:flex-row">
<!-- Left Panel -->
{% if leftPanel %}
<div class="w-full lg:w-1/3 bg-gray-50 p-4 lg:p-6 border-b n7-border-gray-01 lg:border-b-0 lg:border-r lg:border-r-gray-200">
<div class="relative lg:border-l-4 lg:border-blue-500 lg:pl-5">
{% if leftPanel.title %}
<p class="font-semibold text-gray-900">{{ leftPanel.title }}</p>
{% endif %}
{% if leftPanel.description %}
<p class="mt-2 text-sm text-gray-600">{{ leftPanel.description | safe }}</p>
{% endif %}
</div>
</div>
{% endif %}
<!-- Right Panel with menu items -->
<div class="w-full {% if leftPanel %}lg:w-2/3{% endif %} p-4 lg:p-6">
<ul class="n7-megamenu-list flex flex-col lg:grid lg:grid-cols-1 lg:md:grid-cols-2 list-none m-0 p-0">
{% for item in rightPanelItems %}
<li class="n7-megamenu-item border-b n7-border-gray-01 last-of-type:border-b-0 lg:border-b-0 lg:rounded-md">
{% if item.children %}
<!-- Item con sottomenu -->
<div class="n7-megamenu-dropdown group w-full lg:bg-gray-50 lg:rounded-md">
<button
type="button"
class="n7-megamenu-trigger w-full flex items-center p-3 text-sm font-medium text-gray-900 lg:rounded-md text-left transition-colors duration-200 hover:underline focus:n7-background-02"
aria-expanded="false"
data-dropdown-trigger>
<svg class="mr-3 h-5 w-5 transition-transform duration-200" viewBox="0 0 24 24" fill="currentColor" data-dropdown-icon>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
<span>{{ item.text }}</span>
</button>
<!-- Sottomenu -->
<ul class="n7-megamenu-submenu hidden overflow-hidden bg-gray-50 lg:bg-transparent lg:px-3 lg:py-3 list-none m-0 p-0 transition-all duration-200" data-dropdown-content>
{% for child in item.children %}
<li class="n7-megamenu-subitem border-b n7-border-gray-01 last:border-b-0 lg:border-b-0 lg:py-1">
<a href="{{ child.href or '#' }}" class="n7-megamenu-sublink block text-sm lg:text-xs text-gray-600 p-3 lg:p-0 lg:pl-8 lg:pb-2.5 lg:py-1 transition-colors duration-200 hover:underline hover:n7-background-02 focus:n7-background-02">
{{ child.text }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<!-- Item semplice -->
<div class="n7-megamenu-dropdown lg:bg-gray-50 lg:rounded-md">
<a href="{{ item.href or '#' }}" class="n7-megamenu-link group flex items-center p-3 text-sm font-medium text-gray-900 w-full transition-colors duration-200 lg:rounded-md hover:underline hover:n7-background-02 focus:n7-background-02">
<svg class="mr-3 h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"></path>
</svg>
{{ item.text }}
</a>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% else %}
<!-- DROPDOWN SEMPLICE -->
<ul class="n7-dropdown-menu__list hidden border border-t-0 n7-border-gray-01{% if listWidth %} {{ listWidth }}{% endif %} {% if listPosition %} {{ listPosition }}{% endif %}{% if listResponsiveStyles %} {{ listResponsiveStyles }}{% endif %}{% if listClasses %} {{ listClasses }}{% endif %}" id="{{ dropdownId }}">
{% for item in items %}
<li class="n7-dropdown-menu__item group border-b n7-border-gray-01 last-of-type:border-b-0{% if listItemClasses %} {{ listItemClasses }} {% endif %}">
<a class="block p-3 transition bg-white{% if dropdownItemFontSize %} {{ dropdownItemFontSize }}{% endif %} hover:underline hover:n7-background-02 focus:n7-background-02 group-first:rounded-t group-last:rounded-b" href="{{ item.href or '#' }}">
<div class="flex gap-2 items-center">
{% if item.iconLeft %}
{% render '@icon--small', { id: item.iconLeft, classes: 'mr-2', size: 'w-4 h-4 md:w-5 md:h-5 lg:w-6 lg:h-6' }, true %}
{% endif %}
<span class="grow">{% if item.label %}{{ item.label }}{% else %} Item {% endif %}</span>
{% if item.iconRight %}
{% render '@icon--small', { id: item.iconRight, classes: 'ml-2 transition-all duration-200 ease-out transform group-hover:translate-x-1', size: 'w-4 h-4 md:w-5 md:h-5 lg:w-6 lg:h-6' }, true %}
{% endif %}
</div>
{% if item.description %}<div class="text-sm text-gray-600 mt-1">{{ item.description }}</div>{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</{% if listItem %}li{% else %}div{% endif %}>
{
"label": "Componenti da validare",
"id": "megamenu01",
"triggerSpacing": "p-3 gap-2",
"triggerFontSize": "text-sm lg:text-base",
"dropdownItemFontSize": "text-sm xl:text-base",
"listResponsiveStyles": "lg:border-t lg:absolute lg:rounded lg:shadow-md",
"triggerAriaExpandedStyles": "aria-expanded:n7-background-02 lg:aria-expanded:bg-white aria-expanded:n7-content-01",
"items": [
{
"label": "Item label 1",
"iconLeft": "solid--cube-transparent"
},
{
"label": "Item label 3",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
"iconRight": "mini--chevron-right"
},
{
"label": "Item label 4",
"iconLeft": "solid--cube-transparent",
"iconRight": "mini--chevron-right"
},
{
"label": "Item label 5"
}
],
"triggerTextColor": "text-gray-700",
"listClasses": "lg:shadow-xl",
"isMegamenu": true,
"leftPanel": {
"title": "Guida ai componenti",
"description": "Trova tutti i componenti disponibili per il tuo progetto. Organizzati per categorie e funzionalità."
},
"rightPanelItems": [
{
"text": "Layout & Structure",
"children": [
{
"text": "Grid System",
"href": "/components/grid"
},
{
"text": "Flexbox Utilities",
"href": "/components/flexbox"
},
{
"text": "Container",
"href": "/components/container"
},
{
"text": "Spacing",
"href": "/components/spacing"
}
]
},
{
"text": "Form Elements",
"children": [
{
"text": "Input Fields",
"href": "/components/inputs"
},
{
"text": "Buttons",
"href": "/components/buttons"
},
{
"text": "Checkboxes & Radio",
"href": "/components/checkboxes"
},
{
"text": "Select & Dropdown",
"href": "/components/selects"
}
]
},
{
"text": "Navigation",
"children": [
{
"text": "Main Navigation",
"href": "/components/navigation"
},
{
"text": "Breadcrumbs",
"href": "/components/breadcrumbs"
},
{
"text": "Pagination",
"href": "/components/pagination"
}
]
},
{
"text": "Content Display",
"children": [
{
"text": "Cards",
"href": "/components/cards"
},
{
"text": "Tables",
"href": "/components/tables"
},
{
"text": "Lists",
"href": "/components/lists"
}
]
}
]
}
/**
* DROPDOWN MENU
*/
@layer components {
/* TODO: MOVE STYLE TO TAILWIND CLASS? */
.n7-dropdown-menu__list[style*="block"] {
@apply z-10;
}
}
@layer components {
.n7-dropdown-trigger[aria-expanded="true"] .n7-dropdown-menu__trigger-icon {
@apply -rotate-180;
}
.n7-megamenu-trigger[aria-expanded="true"] [data-dropdown-icon] {
@apply rotate-90;
}
.n7-dropdown-menu__list {
transition: opacity 0.2s ease-in-out;
}
.n7-megamenu-submenu {
transition: opacity 0.2s ease-in-out;
}
/* ==========================================================================
POSIZIONAMENTO INTELLIGENTE
========================================================================== */
/* Allineamento orizzontale */
.align-right {
@apply right-0 left-auto;
}
.align-left {
@apply left-0 right-auto;
}
/* Forzatura quando non c'è spazio */
.force-right {
right: 0 !important;
left: auto !important;
}
/* Posizionamento verticale per sottomenu */
.open-upward {
@apply bottom-full top-auto;
}
.open-downward {
@apply top-full bottom-auto;
}
}
/*
* JavaScript per supportare sia dropdown semplici che megamenu
*/
'use strict';
function findAncestor(element, tagName) {
while (element) {
if (element.tagName.toLowerCase() === tagName) {
return element;
}
element = element.parentElement;
}
return null;
}
function getTabbableElements(container) {
return container.querySelectorAll(
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([type="hidden"]):not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
}
// Funzione per calcolare la posizione PRIMA di mostrare il menu
function calculateOptimalPosition(menu, trigger) {
// Rimuovi classi precedenti
menu.classList.remove('align-right', 'align-left', 'force-right');
// Rendi il menu temporaneamente visibile ma invisibile per misurarlo
const wasHidden = menu.classList.contains('hidden');
menu.classList.remove('hidden');
menu.style.visibility = 'hidden';
menu.style.opacity = '0';
// Misura immediatamente
const menuRect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
let positionClass = null;
// Se il menu esce a destra
if (menuRect.right > viewportWidth - 10) {
positionClass = 'align-right';
// Test se con align-right va ancora fuori
menu.classList.add('align-right');
const testRect = menu.getBoundingClientRect();
if (testRect.right > viewportWidth - 10) {
positionClass = 'force-right';
menu.classList.remove('align-right');
menu.classList.add('force-right');
}
}
// Se il menu esce a sinistra
else if (menuRect.left < 10) {
positionClass = 'align-left';
menu.classList.add('align-left');
}
// Nascondilo di nuovo se era nascosto
if (wasHidden) {
menu.classList.add('hidden');
}
menu.style.visibility = '';
menu.style.opacity = '';
return positionClass;
}
class DisclosureNav {
constructor(domNode) {
this.rootNode = domNode;
this.controlledNodes = [];
this.openIndex = null;
this.useArrowKeys = true;
this.isClosing = false;
this.topLevelNodes = [
...this.rootNode.querySelectorAll(
'.main-link, .rtds-dropdown-trigger, .n7-dropdown-menu__trigger, .n7-dropdown-trigger, .n7-primary-navigation__link'
),
];
this.topLevelNodes.forEach((node, index) => {
if (
node.tagName.toLowerCase() === 'button' &&
node.hasAttribute('aria-controls')
) {
const menu = node.parentNode.querySelector('ul, div.n7-dropdown-menu__list');
if (menu) {
this.controlledNodes.push(menu);
node.setAttribute('aria-expanded', 'false');
this.toggleMenu(menu, false);
menu.addEventListener('keydown', this.onMenuKeyDown.bind(this));
node.addEventListener('click', this.onButtonClick.bind(this));
node.addEventListener('keydown', this.onButtonKeyDown.bind(this));
}
} else {
this.controlledNodes.push(null);
node.addEventListener('keydown', this.onLinkKeyDown.bind(this));
}
});
document.addEventListener('click', this.onDocumentClick.bind(this));
document.addEventListener('keydown', this.onGlobalKeyDown.bind(this));
window.addEventListener('resize', this.onWindowResize.bind(this));
}
onWindowResize() {
if (this.openIndex !== null) {
const menu = this.controlledNodes[this.openIndex];
const trigger = this.topLevelNodes[this.openIndex];
if (menu && trigger) {
calculateOptimalPosition(menu, trigger);
}
}
}
focusElement(element, delay = 50) {
if (!element || typeof element.focus !== 'function') return;
setTimeout(() => {
try {
element.focus();
} catch (e) {
console.warn('Focus failed:', e);
}
}, delay);
}
onGlobalKeyDown(event) {
if (event.key !== 'Tab' || this.openIndex === null || this.isClosing) {
return;
}
const openMenu = this.controlledNodes[this.openIndex];
if (!openMenu) return;
const activeElement = document.activeElement;
// Se siamo in un sottomenu del megamenu, lascia che MegaMenuSubmenu lo gestisca
const isInSubmenu = activeElement.closest('[data-dropdown-content]');
if (isInSubmenu && !isInSubmenu.classList.contains('hidden')) {
return;
}
// Gestisci gli elementi tabbabili del menu
let allTabbableInMenu;
if (openMenu.classList.contains('n7-dropdown-menu__list')) {
// Per megamenu: escludi elementi in sottomenu chiusi
allTabbableInMenu = Array.from(getTabbableElements(openMenu)).filter(el => {
const hiddenSubmenu = el.closest('[data-dropdown-content].hidden');
return !hiddenSubmenu;
});
} else {
// Per dropdown semplice: prendi tutti
allTabbableInMenu = Array.from(getTabbableElements(openMenu));
}
const currentIndex = allTabbableInMenu.indexOf(activeElement);
if (!event.shiftKey && currentIndex === allTabbableInMenu.length - 1) {
// Tab sull'ultimo elemento del menu - resta sul trigger
event.preventDefault();
const currentTrigger = this.topLevelNodes[this.openIndex];
this.closeMenu();
this.focusElement(currentTrigger);
}
else if (event.shiftKey && currentIndex === 0) {
// Shift+Tab sul primo elemento del menu
event.preventDefault();
this.closeMenuAndFocusTrigger();
}
}
closeMenu() {
if (this.openIndex === null || this.isClosing) return;
this.isClosing = true;
this.topLevelNodes[this.openIndex].setAttribute('aria-expanded', 'false');
this.toggleMenu(this.controlledNodes[this.openIndex], false);
this.openIndex = null;
setTimeout(() => {
this.isClosing = false;
}, 100);
}
closeMenuAndFocusTrigger() {
if (this.openIndex === null || this.isClosing) return;
const trigger = this.topLevelNodes[this.openIndex];
this.closeMenu();
this.focusElement(trigger);
}
controlFocusByKey(keyboardEvent, nodeList, currentIndex) {
switch (keyboardEvent.key) {
case 'ArrowUp':
case 'ArrowLeft':
keyboardEvent.preventDefault();
if (currentIndex > -1) {
var prevIndex = Math.max(0, currentIndex - 1);
nodeList[prevIndex].focus();
}
break;
case 'ArrowDown':
case 'ArrowRight':
keyboardEvent.preventDefault();
if (currentIndex > -1) {
var nextIndex = Math.min(nodeList.length - 1, currentIndex + 1);
nodeList[nextIndex].focus();
}
break;
case 'Home':
keyboardEvent.preventDefault();
nodeList[0].focus();
break;
case 'End':
keyboardEvent.preventDefault();
nodeList[nodeList.length - 1].focus();
break;
}
}
close() {
this.closeMenu();
}
onButtonClick(event) {
if (this.isClosing) return;
var target = event.target;
if (target.tagName.toLowerCase() !== 'button') {
var buttonAncestor = findAncestor(target, 'button');
if (buttonAncestor) {
buttonAncestor.click();
return;
}
}
var button = event.target;
var buttonIndex = this.topLevelNodes.indexOf(button);
var buttonExpanded = button.getAttribute('aria-expanded') === 'true';
this.toggleExpand(buttonIndex, !buttonExpanded);
}
onButtonKeyDown(event) {
if (this.isClosing) return;
var targetButtonIndex = this.topLevelNodes.indexOf(document.activeElement);
if (event.key === 'Escape') {
event.preventDefault();
this.closeMenuAndFocusTrigger();
} else if (
this.useArrowKeys &&
this.openIndex === targetButtonIndex &&
event.key === 'ArrowDown'
) {
event.preventDefault();
const menu = this.controlledNodes[this.openIndex];
const firstTabbable = getTabbableElements(menu)[0];
if (firstTabbable) {
firstTabbable.focus();
}
} else if (this.useArrowKeys) {
this.controlFocusByKey(event, this.topLevelNodes, targetButtonIndex);
}
}
onLinkKeyDown(event) {
var targetLinkIndex = this.topLevelNodes.indexOf(document.activeElement);
if (this.useArrowKeys) {
this.controlFocusByKey(event, this.topLevelNodes, targetLinkIndex);
}
}
onMenuKeyDown(event) {
if (this.openIndex === null || this.isClosing) {
return;
}
if (event.key === 'Escape') {
event.preventDefault();
event.stopImmediatePropagation();
this.closeMenuAndFocusTrigger();
return;
}
var menuFocusables = Array.from(getTabbableElements(this.controlledNodes[this.openIndex]));
var currentIndex = menuFocusables.indexOf(document.activeElement);
if (this.useArrowKeys) {
this.controlFocusByKey(event, menuFocusables, currentIndex);
}
}
toggleExpand(index, expanded) {
if (this.isClosing) return;
if (this.openIndex !== null && this.openIndex !== index) {
this.closeMenu();
}
if (this.topLevelNodes[index]) {
this.openIndex = expanded ? index : null;
this.topLevelNodes[index].setAttribute('aria-expanded', expanded);
this.toggleMenu(this.controlledNodes[index], expanded);
}
}
toggleMenu(domNode, show) {
if (!domNode) return;
const isMegaMenu = domNode.classList.contains('n7-dropdown-menu__list');
const parentMenu = domNode.closest('.has-dropdown-menu, .n7-dropdown-menu');
const navModule = domNode.closest('.rtds-primary-navigation__module');
if (show) {
// Calcola la posizione PRIMA di mostrare il menu
const trigger = this.topLevelNodes[this.openIndex];
if (trigger) {
calculateOptimalPosition(domNode, trigger);
}
// Ora mostra il menu con la posizione già corretta
if (isMegaMenu) {
domNode.classList.remove('hidden');
domNode.classList.add('block');
} else {
domNode.classList.remove('rtds-hidden', 'is-hidden');
domNode.classList.add('is-open');
}
if (parentMenu) {
parentMenu.classList.add('has-dropdown-open');
}
if (navModule) {
navModule.classList.add('has-inner-dropdown-open');
}
} else {
if (isMegaMenu) {
domNode.classList.remove('block');
domNode.classList.add('hidden');
} else {
domNode.classList.remove('is-open');
domNode.classList.add('rtds-hidden');
}
if (parentMenu) {
parentMenu.classList.remove('has-dropdown-open');
}
if (navModule) {
navModule.classList.remove('has-inner-dropdown-open');
}
domNode.classList.remove('align-right', 'align-left', 'force-right', 'open-upward', 'open-downward');
}
}
updateKeyControls(useArrowKeys) {
this.useArrowKeys = useArrowKeys;
}
onDocumentClick(event) {
if (this.rootNode.contains(event.target) || this.isClosing) {
return;
}
this.closeMenu();
}
}
class MegaMenuSubmenu {
constructor() {
this.openSubmenus = new Map();
this.isClosing = false;
this.init();
}
focusElement(element, delay = 50) {
if (!element || typeof element.focus !== 'function') return;
setTimeout(() => {
try {
element.focus();
} catch (e) {
console.warn('Submenu focus failed:', e);
}
}, delay);
}
init() {
const megaMenuTriggers = document.querySelectorAll('[data-dropdown-trigger]');
megaMenuTriggers.forEach(trigger => {
trigger.addEventListener('click', this.handleSubmenuClick.bind(this));
trigger.addEventListener('keydown', this.handleSubmenuKeydown.bind(this));
});
document.addEventListener('keydown', this.onGlobalKeyDown.bind(this));
window.addEventListener('resize', this.onWindowResize.bind(this));
}
onWindowResize() {
this.openSubmenus.forEach((submenu, trigger) => {
calculateOptimalPosition(submenu, trigger);
});
}
onGlobalKeyDown(event) {
if (event.key !== 'Tab' || this.isClosing) {
return;
}
const activeElement = document.activeElement;
const submenu = activeElement.closest('[data-dropdown-content]');
// Solo per sottomenu di megamenu, non per dropdown normali
if (submenu && !submenu.classList.contains('hidden') && submenu.closest('.n7-dropdown-menu__list')) {
const trigger = submenu.parentElement.querySelector('[data-dropdown-trigger]');
if (!trigger) return;
const tabbableElements = getTabbableElements(submenu);
const currentIndex = Array.from(tabbableElements).indexOf(activeElement);
if (!event.shiftKey && currentIndex === tabbableElements.length - 1) {
event.preventDefault();
this.closeSubmenuAndFocusTrigger(trigger);
}
else if (event.shiftKey && currentIndex === 0) {
event.preventDefault();
this.closeSubmenuAndFocusTrigger(trigger);
}
}
}
closeSubmenu(trigger) {
const submenu = trigger.parentElement.querySelector('[data-dropdown-content]');
const icon = trigger.querySelector('[data-dropdown-icon]');
if (submenu) {
this.isClosing = true;
this.toggleSubmenu(trigger, submenu, icon, false);
setTimeout(() => {
this.isClosing = false;
}, 100);
}
}
closeSubmenuAndFocusTrigger(trigger) {
this.closeSubmenu(trigger);
this.focusElement(trigger);
}
handleSubmenuClick(event) {
if (this.isClosing) return;
event.preventDefault();
event.stopPropagation();
const trigger = event.currentTarget;
const submenu = trigger.parentElement.querySelector('[data-dropdown-content]');
const icon = trigger.querySelector('[data-dropdown-icon]');
if (!submenu) return;
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
this.closeOtherSubmenus(trigger);
this.toggleSubmenu(trigger, submenu, icon, !isExpanded);
}
handleSubmenuKeydown(event) {
if (this.isClosing) return;
const trigger = event.currentTarget;
const submenu = trigger.parentElement.querySelector('[data-dropdown-content]');
const icon = trigger.querySelector('[data-dropdown-icon]');
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleSubmenuClick(event);
} else if (event.key === 'Escape') {
event.preventDefault();
this.closeSubmenuAndFocusTrigger(trigger);
} else if (event.key === 'ArrowDown' && trigger.getAttribute('aria-expanded') === 'true') {
event.preventDefault();
const firstTabbable = getTabbableElements(submenu)[0];
if (firstTabbable) {
firstTabbable.focus();
}
}
}
toggleSubmenu(trigger, submenu, icon, show) {
trigger.setAttribute('aria-expanded', show);
if (show) {
// Calcola la posizione PRIMA di mostrare il sottomenu
calculateOptimalPosition(submenu, trigger);
// Ora mostra il sottomenu con la posizione già corretta
submenu.classList.remove('hidden');
submenu.classList.add('block');
this.openSubmenus.set(trigger, submenu);
if (icon) {
icon.style.transform = 'rotate(90deg)';
}
} else {
submenu.classList.remove('block');
submenu.classList.add('hidden');
this.openSubmenus.delete(trigger);
if (icon) {
icon.style.transform = 'rotate(0deg)';
}
submenu.classList.remove('align-right', 'align-left', 'force-right', 'open-upward', 'open-downward');
}
}
closeOtherSubmenus(currentTrigger) {
const megaMenu = currentTrigger.closest('.n7-dropdown-menu__list');
if (!megaMenu) return;
const allTriggers = megaMenu.querySelectorAll('[data-dropdown-trigger]');
allTriggers.forEach(trigger => {
if (trigger !== currentTrigger) {
const submenu = trigger.parentElement.querySelector('[data-dropdown-content]');
const icon = trigger.querySelector('[data-dropdown-icon]');
if (submenu) {
this.toggleSubmenu(trigger, submenu, icon, false);
}
}
});
}
closeAllSubmenus() {
this.openSubmenus.forEach((submenu, trigger) => {
const icon = trigger.querySelector('[data-dropdown-icon]');
this.toggleSubmenu(trigger, submenu, icon, false);
});
}
}
window.addEventListener(
'load',
function () {
var menus = document.querySelectorAll('.has-dropdown-menu, div.rtds-dropdown-menu, .n7-dropdown-menu[class*="has-dropdown-menu"], .n7-dropdown-menu');
var disclosureMenus = [];
var processedPrimaryNavs = new Set();
for (var i = 0; i < menus.length; i++) {
const menu = menus[i];
// Se è dentro un primary navigation
const primaryNav = menu.closest('.n7-primary-navigation');
if (primaryNav) {
// Se non abbiamo già processato questo primary navigation
if (!processedPrimaryNavs.has(primaryNav)) {
processedPrimaryNavs.add(primaryNav);
// Inizializza sul modulo principale che contiene tutti i menu
const mainModule = primaryNav.querySelector('.n7-primary-navigation__module');
if (mainModule) {
disclosureMenus.push(new DisclosureNav(mainModule));
}
}
} else {
// Menu standalone (non in primary navigation)
disclosureMenus.push(new DisclosureNav(menu));
}
}
new MegaMenuSubmenu();
},
false
);
Dropdown menu component can be used as a dropdown indipendent component (has div as wrapper) or as a list item in a menu list - list-item variant (eg in primary menu).
In preview, component style is missing - this component is dependant on list/menu in which is used.
Blocks: