<div class="rtds-search-bar" data-search-bar data-all-results-href="/search?q=" data-all-results-label="Tutti i risultati">
    <form class="rtds-search-bar__form" role="search" action="/search" method="get">
        <div class="rtds-search-bar__wrapper rtds-grid rtds-gap-2">
            <label for="searchBarVisibleLabel" class="rtds-search-bar__label rtds-text-base md:rtds-text-lg rtds-font-medium rtds-content-03 ">
                Cerca servizi, informazioni, aiuti...
            </label>

            <div class="rtds-search-bar__input-wrapper">
                <div class="rtds-search-bar__autocomplete" id="searchBarVisibleLabelContainer" data-placeholder="Search"></div>

                <button type="button" class="rtds-btn rtds-btn--icon rtds-btn--l rtds-search-bar__clear rtds-border-0 rtds-hidden hover:rtds-background-secondary hover:rtds-text-white" data-search-clear>
                    <span class="rtds-sr-only">Cancella ricerca</span>
                    <svg class="rtds-icon rtds-fill-current rtds-w-6 rtds-h-6" aria-hidden="true" focusable="false" role="img">
                        <use href="../../icons.svg#outline--x-circle" />
                    </svg>

                </button>

                <button type="submit" class="rtds-btn 
    r rtds-btn--icon rtds-btn--icon-square rtds-btn--primary rtds-search-bar__submit rtds-btn--l"><svg class="rtds-icon rtds-fill-current rtds-w-6 rtds-h-6" aria-hidden="true" focusable="false" role="img">
                        <use href="../../icons.svg#mini--magnifying-glass" />
                    </svg>
                    <span class="rtds-sr-only">

                        Cerca
                    </span>

                </button>

            </div>
        </div>
    </form>
</div>
<div class="rtds-search-bar{% if variant %} rtds-search-bar--{{ variant }}{% endif %}{% if classes %} {{ classes }}{% endif %}" data-search-bar{% if searchOptions %} data-options='{{ searchOptions | tojson }}'{% endif %}{% if searchUrl %} data-search-url="{{ searchUrl }}"{% endif %}{% if allResultsHref %} data-all-results-href="{{ allResultsHref }}"{% endif %}{% if allResultsLabel %} data-all-results-label="{{ allResultsLabel }}"{% endif %}>
  <form class="rtds-search-bar__form" role="search"{% if formAction %} action="{{ formAction }}"{% endif %}{% if formMethod %} method="{{ formMethod }}"{% endif %}>
    <div class="rtds-search-bar__wrapper{% if showLabel == true %} rtds-grid rtds-gap-2{% endif %}">
      <label for="{{ searchId }}" class="rtds-search-bar__label{% if showLabel != true %} rtds-sr-only{% else %} rtds-text-base md:rtds-text-lg rtds-font-medium rtds-content-03 {% endif %}">
        {{ label|default('Cerca') }}
      </label>
      
      <div class="rtds-search-bar__input-wrapper">
        <div class="rtds-search-bar__autocomplete" id="{{ searchId }}Container"{% if placeholder %} data-placeholder="{{ placeholder }}"{% endif %}></div>
        
        {% if showClearButton %}
        <button 
          type="button" 
          class="rtds-btn rtds-btn--icon rtds-btn--l rtds-search-bar__clear rtds-border-0 rtds-hidden hover:rtds-background-secondary hover:rtds-text-white" 
          data-search-clear
        >
          <span class="rtds-sr-only">Cancella ricerca</span>
          {% render '@icon', {id: 'outline--x-circle', size: 'rtds-w-6 rtds-h-6'}, true %}
        </button>
             
        {% endif %}

  
        
        {% render '@button--icon-square-l', {
          buttonType: 'submit',
          icon: 'mini--magnifying-glass',
          iconSize: 'rtds-w-6 rtds-h-6',
          label: submitLabel|default('Cerca'),
          classes: 'rtds-btn--primary rtds-search-bar__submit'
        }, true %}
      </div>
    </div>
  </form>
</div>
{
  "label": "Cerca servizi, informazioni, aiuti...",
  "searchId": "searchBarVisibleLabel",
  "submitLabel": "Cerca",
  "showClearButton": true,
  "showLabel": true,
  "formAction": "/search",
  "formMethod": "get",
  "placeholder": "Search",
  "allResultsHref": "/search?q=",
  "allResultsLabel": "Tutti i risultati"
}
  • Content:
    /**
     * SEARCH BAR COMPONENT
     * Componente di ricerca con autocomplete
     */
    @layer components {
        .rtds-search-bar {
            --_autocomplete-max-height: var(--autocomplete-max-height, 25rem);
            @apply rtds-relative rtds-w-full;
        }
    
        .rtds-search-bar__form {
            @apply rtds-w-full;
        }
    
        .rtds-search-bar__wrapper {
            @apply rtds-flex;
        }
    
        .rtds-search-bar__input-wrapper {
            @apply rtds-relative rtds-flex-1 rtds-flex rtds-items-center rtds-rounded-lg rtds-bg-white rtds-h-16;
            transition: border-color 0.2s ease;
        }
    
        /*.rtds-search-bar__input-wrapper:hover:not(:has(.autocomplete__input:focus)) {
            @apply rtds-border-gray-03;
        }*/
    
        /*.rtds-search-bar--focus .rtds-search-bar__input-wrapper {
            @apply rtds-border-primary;
        }*/
    
        .rtds-search-bar__autocomplete {
            @apply rtds-h-full rtds-flex-1;
        }
    
        .rtds-search-bar .autocomplete__wrapper {
            @apply rtds-h-full;
        }
    
        /* Autocomplete input styling */
        .rtds-search-bar .autocomplete__input {
            @apply rtds-w-full rtds-py-2 rtds-pl-3 rtds-pr-28 rtds-border rtds-bg-transparent rtds-text-sm rtds-font-medium rtds-content-03 rtds-rounded-lg rtds-h-full rtds-border-input;
            transition: border-color 0.2s ease;
        }
    
        .rtds-search-bar .autocomplete__input:hover,
        .rtds-search-bar .autocomplete__input:focus:not(:focus-visible) {
            @apply rtds-border-input;
        }
    
        .rtds-search-bar .autocomplete__input:hover,
        .rtds-search-bar .autocomplete__input:focus:where(:not(:focus-visible)),
        .rtds-search-bar .autocomplete__input--focused:where(:not(:focus-visible)),
        .is-opened-by-pointer .autocomplete__input--focused {
            @apply rtds-outline-0;
            outline: 2px solid transparent;
            box-shadow: none;
        }
    
        .rtds-search-bar .autocomplete__input::placeholder {
            @apply rtds-content-03 rtds-text-base;
        }
    
        /* Clear button */
        .rtds-search-bar__clear {
            @apply rtds-absolute rtds-right-16 rtds-top-1/2 rtds--translate-y-1/2 rtds-z-10;
            transition: background-color 0.2s ease;
        }
    
        /*.rtds-search-bar__clear:hover {
            @apply rtds-background-secondary rtds-text-white;
        }*/
    
        /* Submit button */
        .rtds-search-bar__submit {
            @apply rtds-absolute rtds-right-2 rtds-top-1/2 rtds--translate-y-1/2 rtds-shrink-0 rtds-z-10;
        }
    
    
        /* Variant: Compact */
       /* .rtds-search-bar--compact .rtds-search-bar__input-wrapper {
            @apply rtds-h-12;
        }
    
        .rtds-search-bar--compact .autocomplete__input {
            @apply rtds-py-1 rtds-pl-2 rtds-pr-16 rtds-text-sm rtds-h-full;
        }
    
        .rtds-search-bar--compact .rtds-search-bar__submit {
            @apply rtds-w-8 rtds-h-8;
        }
    
        .rtds-search-bar--compact .rtds-search-bar__clear {
            @apply rtds-w-5 rtds-h-5 rtds-right-10;
        }*/
    
        /* Variant: Full width */
       /* .rtds-search-bar--full-width {
            @apply rtds-w-full;
        }*/
    
        /* Autocomplete menu styling */
        :where(.rtds-search-bar) .autocomplete__menu {
            @apply rtds-border rtds-border-gray-01 rtds-rounded-lg rtds-shadow-md rtds-background-01 rtds-mt-2;
            max-height: var(--_autocomplete-max-height);
            overflow-y: auto;
        }
    
       :where(.rtds-search-bar) .autocomplete__option {
            @apply rtds-px-3 rtds-py-2 rtds-text-sm rtds-font-medium rtds-background-01 rtds-content-03 rtds-border-0;
        }
    
        :where(.rtds-search-bar) .autocomplete__option:hover,
        :where(.rtds-search-bar) .autocomplete__option--focused {
            @apply rtds-background-02;
        }
    
        :where(.rtds-search-bar) .autocomplete__option--focused {
          outline-style: solid;
          outline-width: 2px;
          outline-offset: -2px;
          @apply rtds-outline-focusring;
        }
    
        :where(.rtds-search-bar) .autocomplete__option--focused:where(:not(:focus-visible)) {
          outline: 2px solid transparent;
          box-shadow: none;
        }
        /* Hide default dropdown arrow */
        .rtds-search-bar .autocomplete__dropdown-arrow-down-wrapper {
            @apply rtds-hidden;
        }
    
        /* Autocomplete suggestion styling */
        .rtds-search-bar__autocomplete-suggestion {
            @apply rtds-flex rtds-flex-col rtds-gap-1;
        }
    
        .rtds-search-bar__suggestion-title {
            @apply rtds-text-sm rtds-font-bold rtds-content-01;
        }
    
        .rtds-search-bar__suggestion-type {
            @apply rtds-text-xs rtds-font-normal rtds-content-03;
        }
    
        /* All results option content wrapper */
        /* All results option styling */
        .autocomplete__option.rtds-search-bar__all-results-option-wrapper {
            @apply rtds-flex rtds-justify-end rtds-px-3;
        }
    
        .autocomplete__option.rtds-search-bar__all-results-option-wrapper:hover,
        .autocomplete__option.rtds-search-bar__all-results-option-wrapper:focus {
            @apply rtds-background-01;
        }
    
        .rtds-search-bar__all-results-option {
            @apply rtds-border-t rtds-border-gray-03 rtds-py-2 rtds-w-full rtds-flex rtds-justify-end rtds-flex-row;
        }
    
        .rtds-search-bar__all-results-option:hover .rtds-search-bar__all-results-link {
            @apply rtds-underline;
        }
    
        .rtds-search-bar__all-results-link {
            @apply rtds-flex rtds-items-center rtds-gap-2 hover:rtds-underline;
        }
    
        /* Mobile Search Modal */
        .rtds-search-bar__mobile-wrapper {
            @apply rtds-inline-flex;
        }
    
        .rtds-search-bar__modal {
            @apply rtds-z-50;
        }
    
        .rtds-search-bar__modal[aria-hidden="false"] {
            @apply rtds-flex;
        }
    
        .rtds-search-bar__modal > div {
            @apply rtds-max-w-full;
        }
    }
    
    
  • URL: /components/raw/search-bar/search-bar.css
  • Filesystem Path: components/03-molecules/search-bar/search-bar.css
  • Size: 5.6 KB
  • Content:
    import accessibleAutocomplete from 'accessible-autocomplete';
    
    /**
     * Initialize search bar component
     */
    function initSearchBar() {
        const searchBars = document.querySelectorAll('[data-search-bar]');
        
        searchBars.forEach((searchBar) => {
            const container = searchBar.querySelector('[id$="Container"]');
            const searchId = container?.id.replace('Container', '');
            const clearButton = searchBar.querySelector('[data-search-clear]');
            
            if (!container || !searchId) return;
            
            // Get options from data attribute or use defaults
            // Options can be an array of strings or objects with title and type
            let options = [];
            if (searchBar.dataset.options) {
                options = JSON.parse(searchBar.dataset.options);
            } else {
                // Default options
                options = [
                    { title: 'Risultato di ricerca', type: 'Content type' },
                    { title: 'Filtri di ricerca', type: 'Content type' },
                    { title: 'Servizio 1', type: 'Content type' },
                    { title: 'Servizio 2', type: 'Content type' },
                    { title: 'Informazione 1', type: 'Content type' },
                    { title: 'Informazione 2', type: 'Content type' }
                ];
            }
            
            // Normalize options to always be objects
            const normalizedOptions = options.map(option => {
                if (typeof option === 'string') {
                    return { title: option, type: 'Content type' };
                }
                return option;
            });
            
            // Get placeholder from data attribute
            const placeholder = container.dataset.placeholder || 'Search';
            
            // Get all results link configuration
            const allResultsHref = searchBar.dataset.allResultsHref || '#';
            const allResultsLabel = searchBar.dataset.allResultsLabel || 'Tutti i risultati';
            
            // Get icon path - try to find existing icon usage in the page
            const existingIcon = document.querySelector('svg use[href*="icons.svg"]');
            const iconPath = existingIcon ? existingIcon.getAttribute('href').split('#')[0] : '/icons.svg';
            
            // Salva un riferimento a syncResults per poter chiudere il menu programmaticamente
            let syncResultsRef = null;
            
            // Initialize accessible autocomplete
            accessibleAutocomplete({
                element: container,
                id: searchId,
                placeholder: placeholder,
                source: (query, syncResults) => {
                    // Salva il riferimento a syncResults
                    syncResultsRef = syncResults;
                    
                    if (query.length === 0) {
                        syncResults([]);
                        return;
                    }
                    
                    // Filter options based on query
                    const filtered = normalizedOptions.filter(option => 
                        option.title.toLowerCase().includes(query.toLowerCase())
                    );
                    
                    // Add "All results" option at the end if there are results
                    if (filtered.length > 0) {
                        filtered.push({
                            title: allResultsLabel,
                            type: 'all-results',
                            href: allResultsHref,
                            isAllResults: true
                        });
                    }
                    
                    syncResults(filtered);
                },
                showAllValues: false,
                dropdownArrow: () => '', // Hide default arrow
                onConfirm: (option) => {
                    if (option) {
                        // If it's the "All results" option, navigate to the link
                        if (option.isAllResults && option.href) {
                            window.location.href = option.href;
                            return;
                        }
                        // Handle selection - option is the full object
                        console.log('Selected:', option);
                    }
                },
                templates: {
                    inputValue: (result) => {
                        if (!result) return '';
                        // Don't set input value for "All results" option
                        if (result.isAllResults) return '';
                        return typeof result === 'string' ? result : result.title;
                    },
                    suggestion: (result) => {
                        if (!result) return '';
                        
                        // Check if it's the "All results" option
                        const isAllResults = typeof result === 'object' && result.isAllResults;
                        
                        if (isAllResults) {
                            // Render as footer link
                            return `
                                <div class="rtds-search-bar__autocomplete-suggestion rtds-search-bar__all-results-option">
                                    <a href="${result.href}" class="rtds-action-link rtds-action-link--sm rtds-search-bar__all-results-link">
                                        ${result.title}
                                        <svg class="rtds-icon rtds-fill-current rtds-w-4 rtds-h-4" aria-hidden="true" focusable="false" role="img">
                                            <use href="${iconPath}#mini--arrow-small-right" />
                                        </svg>
                                    </a>
                                </div>
                            `;
                        }
                        
                        // Regular suggestion
                        const title = typeof result === 'string' ? result : result.title;
                        const type = typeof result === 'string' ? 'Content type' : result.type;
                        return `
                            <div class="rtds-search-bar__autocomplete-suggestion">
                                <span class="rtds-search-bar__suggestion-title">${title}</span>
                                <span class="rtds-search-bar__suggestion-type">${type}</span>
                            </div>
                        `;
                    }
                }
            });
            
            // Get the actual input element created by accessible-autocomplete
            const autocompleteInput = searchBar.querySelector(`#${searchId}`);
            
            // Handle clicks on "All results" link and add class to option
            if (autocompleteInput) {
                const handleMenuClick = (e) => {
                    const link = e.target.closest('.rtds-search-bar__all-results-link');
                    if (link) {
                        e.preventDefault();
                        e.stopPropagation();
                        const href = link.getAttribute('href');
                        if (href && href !== '#') {
                            window.location.href = href;
                        }
                    }
                };
                
                const addClassToAllResultsOption = () => {
                    const menu = searchBar.querySelector('.autocomplete__menu');
                    if (menu) {
                        const allResultsOption = menu.querySelector('.autocomplete__option:has(.rtds-search-bar__all-results-option)');
                        if (allResultsOption) {
                            allResultsOption.classList.add('rtds-search-bar__all-results-option-wrapper');
                        }
                    }
                };
                
                // Funzione helper per verificare se il menu è visibile
                const isMenuVisible = () => {
                    const menu = searchBar.querySelector('.autocomplete__menu');
                    if (!menu) return false;
                    const style = window.getComputedStyle(menu);
                    return style.display !== 'none' && style.visibility !== 'hidden';
                };
                
                // Funzione per chiudere il menu usando syncResults
                const closeMenu = () => {
                    // Usa syncResults per svuotare i risultati, questo chiuderà il menu correttamente
                    if (syncResultsRef) {
                        syncResultsRef([]);
                    }
                    // Assicurati che aria-expanded sia false
                    autocompleteInput.setAttribute('aria-expanded', 'false');
                };
                
                // Gestione ESC per chiudere il menu e riportare il focus all'input
                const handleEscapeKey = (e) => {
                    if (e.key === 'Escape' || e.keyCode === 27) {
                        // Verifica se il menu è visibile
                        if (isMenuVisible()) {
                            // Chiudi il menu usando syncResults
                            closeMenu();
                            // Nascondi il pulsante di cancellazione se l'input è vuoto
                            if (clearButton && autocompleteInput.value.length === 0) {
                                clearButton.classList.add('rtds-hidden');
                            }
                            // Riporta il focus all'input
                            autocompleteInput.focus();
                            // Previeni il comportamento di default e ferma la propagazione
                            // per evitare che l'evento raggiunga il listener della modale
                            e.preventDefault();
                            e.stopPropagation();
                        }
                    }
                };
                
                // Aggiungi listener per ESC sull'input
                autocompleteInput.addEventListener('keydown', handleEscapeKey);
                
                // Aggiungi listener anche sul menu quando viene creato per gestire ESC durante la navigazione
                const handleMenuKeydown = (e) => {
                    if (e.key === 'Escape' || e.keyCode === 27) {
                        // Chiudi il menu usando syncResults
                        closeMenu();
                        // Nascondi il pulsante di cancellazione se l'input è vuoto
                        if (clearButton && autocompleteInput.value.length === 0) {
                            clearButton.classList.add('rtds-hidden');
                        }
                        // Riporta sempre il focus all'input quando si preme ESC dal menu
                        autocompleteInput.focus();
                        // Previeni il comportamento di default e ferma la propagazione
                        // per evitare che l'evento raggiunga il listener della modale
                        e.preventDefault();
                        e.stopPropagation();
                    }
                };
                
                // Listen for menu changes
                const observer = new MutationObserver(() => {
                    const menu = searchBar.querySelector('.autocomplete__menu');
                    if (menu) {
                        menu.addEventListener('click', handleMenuClick);
                        // Aggiungi listener per ESC sul menu
                        menu.addEventListener('keydown', handleMenuKeydown);
                        // Add class to all results option
                        setTimeout(addClassToAllResultsOption, 0);
                    }
                });
                
                observer.observe(searchBar, {
                    childList: true,
                    subtree: true
                });
                
                // Also check on input events
                autocompleteInput.addEventListener('input', () => {
                    setTimeout(addClassToAllResultsOption, 100);
                });
            }
            
            if (autocompleteInput && clearButton) {
                // Show/hide clear button based on input value
                const updateClearButton = () => {
                    if (autocompleteInput.value.length > 0) {
                        clearButton.classList.remove('rtds-hidden');
                    } else {
                        clearButton.classList.add('rtds-hidden');
                    }
                };
                
                // Listen to input events
                autocompleteInput.addEventListener('input', () => {
                    updateClearButton();
                });
                
                // Clear button handler
                clearButton.addEventListener('click', () => {
                    autocompleteInput.value = '';
                    autocompleteInput.focus();
                    updateClearButton();
                    
                    // Trigger input event to update autocomplete
                    autocompleteInput.dispatchEvent(new Event('input', { bubbles: true }));
                });
                
                // Initial state
                updateClearButton();
            }
        });
    }
    
    // Initialize on DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initSearchBar);
    } else {
        initSearchBar();
    }
    
    /**
     * Initialize mobile search modal
     */
    function initMobileSearchModal() {
        const modal = document.querySelector('.rtds-search-bar__modal');
        if (!modal) return;
        
        const modalId = modal.id;
        const triggerId = modal.dataset.triggerId || 'searchModalMobileTrigger';
        const closeButtonId = modal.dataset.closeButtonId || 'closeMobileSearchModal';
        const trigger = document.getElementById(triggerId);
        const closeButton = document.getElementById(closeButtonId);
        
        if (!trigger) return;
        
        // Helper functions
        const getFocusableElements = (container) => {
            const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable], summary';
            const elements = container.querySelectorAll(focusableSelector);
            return Array.from(elements).filter(el => 
                !el.disabled && 
                el.offsetParent !== null && 
                getComputedStyle(el).visibility !== 'hidden' &&
                !el.getAttribute('aria-hidden')
            );
        };
        
        let focusTrapHandler = null;
        
        const trapFocus = (e) => {
            const modalContent = modal.querySelector('.rtds-flex.rtds-flex-col');
            
            // Se l'elemento che sta ricevendo il focus non è dentro la modale, riporta il focus dentro
            if (modalContent && !modalContent.contains(e.target)) {
                e.preventDefault();
                const focusableElements = getFocusableElements(modalContent);
                if (focusableElements.length > 0) {
                    focusableElements[0].focus();
                }
            }
        };
        
        const openMobileSearchModal = (interactionType = 'pointer') => {
            const modalContent = modal.querySelector('.rtds-flex.rtds-flex-col');
            const header = document.querySelector('.rtds-header');
            
            modal.style.display = 'flex';
            modal.classList.add("rtds-z-20");
            modal.setAttribute('aria-hidden', 'false');
            if (header) {
                header.classList.add('has-search-modal-open');
            }
            
            // Aggiorna aria-expanded sul trigger
            if (trigger) {
                trigger.setAttribute('aria-expanded', 'true');
            }
            
            // Aggiungi classe per indicare il tipo di interazione
            modal.classList.remove('is-opened-by-pointer', 'is-opened-by-keyboard');
            if (interactionType === 'keyboard') {
                modal.classList.add('is-opened-by-keyboard');
            } else {
                modal.classList.add('is-opened-by-pointer');
            }
            
            // Attiva il focus trap a livello di document
            focusTrapHandler = trapFocus;
            document.addEventListener('focusin', focusTrapHandler);
            
            // Get all the focusable elements inside the modal content
            const focusableElements = getFocusableElements(modalContent);
            const searchInput = modalContent.querySelector('input[type="search"], input[id*="Search"], .autocomplete__input');
            
            // Focus on search input if available, otherwise first focusable element
            if (searchInput) {
                setTimeout(() => {
                    searchInput.focus();
                }, 100);
            } else if (focusableElements.length > 0) {
                focusableElements[0].focus();
            }
            
            if (!document.documentElement.classList.contains('!rtds-overflow-hidden')) {
                document.documentElement.classList.add('!rtds-overflow-hidden');
            }
        };
        
        const closeMobileSearchModal = () => {
            const header = document.querySelector('.rtds-header');
            
            // Rimuovi il focus trap
            if (focusTrapHandler) {
                document.removeEventListener('focusin', focusTrapHandler);
                focusTrapHandler = null;
            }
            
            if (document.documentElement.classList.contains('!rtds-overflow-hidden')) {
                document.documentElement.classList.remove('!rtds-overflow-hidden');
            }
            
            modal.style.display = 'none';
            modal.setAttribute('aria-hidden', 'true');
            modal.classList.remove("rtds-z-20");
            modal.classList.remove('is-opened-by-pointer', 'is-opened-by-keyboard');
            if (header) {
                header.classList.remove('has-search-modal-open');
            }
            
            // Aggiorna aria-expanded sul trigger
            if (trigger) {
                trigger.setAttribute('aria-expanded', 'false');
                trigger.focus();
            }
        };
        
        // Gestione apertura tramite click/touch (pointer)
        trigger.addEventListener('click', (e) => {
            openMobileSearchModal('pointer');
        });
        
        // Gestione apertura tramite tastiera (Enter/Space)
        trigger.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                openMobileSearchModal('keyboard');
            }
        });
        
        // Gestione chiusura
        if (closeButton) {
            closeButton.addEventListener('click', closeMobileSearchModal);
        }
        
        // Funzione helper per verificare se il menu autocomplete è visibile
        const isAutocompleteMenuVisible = () => {
            const autocompleteMenu = modal.querySelector('.autocomplete__menu');
            if (!autocompleteMenu) return false;
            const style = window.getComputedStyle(autocompleteMenu);
            return style.display !== 'none' && style.visibility !== 'hidden';
        };
        
        // Gestione keyboard nella modale
        modal.addEventListener('keydown', function (event) {
            const modalContent = modal.querySelector('.rtds-flex.rtds-flex-col');
            
            // Se l'utente inizia a navigare con la tastiera, rimuovi la classe is-opened-by-pointer
            // per permettere al focus ring di apparire normalmente
            if (modal.classList.contains('is-opened-by-pointer')) {
                // Rimuovi is-opened-by-pointer quando l'utente usa Tab, frecce, o altri tasti di navigazione
                if (event.key === 'Tab' || event.key.startsWith('Arrow') || event.key === 'Home' || event.key === 'End') {
                    modal.classList.remove('is-opened-by-pointer');
                }
            }
            
            if (event.key === 'Tab') {
                const focusableElements = getFocusableElements(modalContent);
                
                // Se non ci sono elementi focusabili, previeni la tabulazione
                if (focusableElements.length === 0) {
                    event.preventDefault();
                    return;
                }
                
                const firstFocusableElement = focusableElements[0];
                const lastFocusableElement = focusableElements[focusableElements.length - 1];
                
                if (event.shiftKey) {
                    // Shift + Tab: navigazione all'indietro
                    if (document.activeElement === firstFocusableElement) {
                        lastFocusableElement.focus();
                        event.preventDefault();
                    }
                } else {
                    // Tab: navigazione in avanti
                    if (document.activeElement === lastFocusableElement) {
                        firstFocusableElement.focus();
                        event.preventDefault();
                    }
                }
            }
            
            if (event.key === 'Escape') {
                // Verifica se il menu autocomplete è aperto
                const menuIsOpen = isAutocompleteMenuVisible();
                
                // Verifica se l'evento proviene dal menu autocomplete (non dall'input)
                const autocompleteMenu = modal.querySelector('.autocomplete__menu');
                const isEventFromAutocompleteMenu = autocompleteMenu && autocompleteMenu.contains(event.target);
                
                // Se il menu autocomplete è aperto E l'evento proviene dal menu stesso,
                // NON chiudere la modale - il listener dell'autocomplete gestirà l'ESC
                if (menuIsOpen && isEventFromAutocompleteMenu) {
                    // Ferma la propagazione per evitare che altri listener gestiscano l'evento
                    event.stopPropagation();
                    return;
                }
                
                // Se il menu autocomplete NON è aperto, chiudi la modale
                // (anche se l'evento proviene dall'input o da altri elementi nella modale)
                if (!menuIsOpen) {
                    closeMobileSearchModal();
                }
            }
        });
    }
    
    // Initialize mobile search modal on DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initMobileSearchModal);
    } else {
        initMobileSearchModal();
    }
    
    export default initSearchBar;
    
    
  • URL: /components/raw/search-bar/search-bar.js
  • Filesystem Path: components/03-molecules/search-bar/search-bar.js
  • Size: 21.2 KB

Search Bar

Componente di ricerca con autocomplete basato su Accessible Autocomplete.

Panoramica

Il componente implementa una barra di ricerca con funzionalità di autocompletamento accessibile, utilizzando la libreria Accessible Autocomplete. Supporta diversi stati (default, hover, focus, open) e varianti (base/md, compact, full-width).

Caratteristiche

  • Autocomplete accessibile tramite Accessible Autocomplete
  • Bottone di ricerca con icona
  • Bottone per cancellare l’input (quando c’è testo)
  • Dropdown con risultati di ricerca e filtri
  • Supporto per stati: default, hover, focus, open
  • Varianti: base (md), compact, full-width

Configurazione

Parametri Principali

  • label: Testo della label (default: “Cerca”)
  • searchId: ID univoco per il campo di ricerca (obbligatorio)
  • submitLabel: Testo per il bottone di ricerca (default: “Cerca”)
  • placeholder: Placeholder per l’input (default: “Search”)
  • showClearButton: Mostra/nasconde il bottone per cancellare (default: true)
  • showLabel: Mostra/nasconde visivamente il label (default: false)
    • false: Label nascosto con classe rtds-sr-only (solo per screen reader)
    • true: Label visibile con stili appropriati (utile per modali desktop)
  • showResults: Mostra/nasconde il dropdown dei risultati (default: true)
  • showAllResultsButton: Mostra/nasconde il bottone “Tutti i risultati” (default: true)
  • formAction: URL per il submit del form (default: “/search”)
  • formMethod: Metodo HTTP per il form (default: “get”)
  • variant: Variante del componente (“compact” o “full-width”)

Parametri per i Risultati

  • searchResults: Array di oggetti con title e type per i risultati di ricerca
  • searchFilters: Array di oggetti con title e type per i filtri di ricerca

Esempio Base

Utilizzare @search-bar con i parametri: searchId: 'mySearch', label: 'Cerca', placeholder: 'Inserisci la tua ricerca'.

Esempio con Risultati

Utilizzare @search-bar con i parametri: searchId: 'mySearch', searchResults (array di oggetti con title e type), searchFilters (array di oggetti con title e type).

Varianti

Base (md)

Variante standard con dimensioni medie.

Compact

Variante compatta con dimensioni ridotte per spazi limitati.

Full Width

Variante che occupa tutta la larghezza disponibile.

With Visible Label

Variante che mostra il label visivamente invece di nasconderlo con rtds-sr-only. Utile per modali desktop dove il label deve essere visibile all’utente.

Esempio: Utilizzare @search-bar--with-visible-label con i parametri: searchId: 'mySearch', label: 'Cerca servizi, informazioni, aiuti...', placeholder: 'Search'.

Mobile Modal

Variante che include sia il trigger button che la modale mobile per la ricerca. Questa variante è progettata per essere inclusa nel componente main-heading e gestisce automaticamente l’apertura/chiusura della modale con supporto per interazioni da puntatore e tastiera.

Parametri specifici per Mobile Modal:

  • modalId: ID della modale (default: “searchModalMobile”)
  • modalTitleId: ID del titolo della modale (default: “mobileSearchModalTitle”)
  • modalTitle: Titolo della modale (default: “Cerca nel sito”)
  • triggerId: ID del trigger button (default: “searchModalMobileTrigger”)
  • triggerLabel: Label per screen reader del trigger (default: “Cerca nel sito - apri la modale”)
  • closeButtonId: ID del bottone di chiusura (default: “closeMobileSearchModal”)
  • closeButtonLabel: Label per screen reader del bottone di chiusura (default: “Chiudi modale di ricerca”)

Esempio: Utilizzare @search-bar--mobile-modal con i parametri: modalId: 'searchModalMobile', triggerId: 'searchModalMobileTrigger', searchId: 'mobileSearchBar', label: 'Cerca', placeholder: 'Search'.

Stati

  • Default: Input con bordo grigio
  • Hover: Bordo leggermente più scuro
  • Focus: Bordo blu con ring di focus
  • Open: Dropdown dei risultati visibile

JavaScript

Il componente utilizza Accessible Autocomplete per gestire l’autocompletamento. Le opzioni possono essere passate tramite l’attributo data-options del componente:

<div data-search-bar data-options='["Opzione 1", "Opzione 2", "Opzione 3"]'>
  <!-- ... -->
</div>

Gestione Modale Mobile

La variante mobile-modal include funzionalità JavaScript avanzate per:

  • Apertura/Chiusura: Gestione dell’apertura e chiusura della modale tramite trigger button
  • Focus Management: Gestione del focus trap all’interno della modale
  • Rilevamento Tipo Interazione: Distinzione tra apertura da puntatore (mouse/touch) o tastiera per gestire correttamente il focus ring
  • Navigazione da Tastiera: Supporto completo per Tab, Shift+Tab, Escape e frecce
  • Accessibilità: Attributi ARIA appropriati e gestione degli stati aria-hidden e aria-expanded
  • Gestione aria-expanded: Il trigger button ha aria-expanded che viene aggiornato automaticamente:
    • aria-expanded="false" quando la modale è chiusa (valore iniziale)
    • aria-expanded="true" quando la modale è aperta

La modale rileva automaticamente il tipo di interazione utilizzato per aprirla e aggiunge classi CSS (is-opened-by-pointer o is-opened-by-keyboard) che possono essere utilizzate per personalizzare lo stile del focus ring.

Accessibilità

  • Label configurabile: può essere nascosta visivamente (showLabel: false, default) o visibile (showLabel: true) a seconda del contesto d’uso
  • Quando nascosta, la label è comunque disponibile per screen reader tramite rtds-sr-only
  • Quando visibile, la label utilizza stili appropriati per garantire la leggibilità
  • Supporto completo per navigazione da tastiera
  • Attributi ARIA appropriati (aria-expanded, aria-haspopup, aria-controls, aria-hidden)
  • Conforme alle linee guida WCAG
  • Gestione corretta del focus ring in base al tipo di interazione (puntatore vs tastiera)
  • Focus trap nella modale mobile per mantenere il focus all’interno durante la navigazione
  • Supporto per chiusura modale con tasto Escape
  • Gestione dinamica di aria-expanded sui trigger button per indicare lo stato della modale agli screen reader