<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">
            <label for="searchBarDefault" class="rtds-search-bar__label rtds-sr-only">
                Cerca
            </label>

            <div class="rtds-search-bar__input-wrapper">
                <div class="rtds-search-bar__autocomplete" id="searchBarDefaultContainer" 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",
  "searchId": "searchBarId",
  "submitLabel": "Cerca",
  "showClearButton": true,
  "showLabel": false,
  "formAction": "/search",
  "formMethod": "get",
  "placeholder": "Search",
  "allResultsHref": "/search?q=",
  "allResultsLabel": "Tutti i risultati"
}
  • Content:
    /**
     * SEARCH BAR COMPONENT
     * Componente di ricerca con autocomplete
     */
    .rtds-search-bar {
            --_autocomplete-max-height: var(--autocomplete-max-height, 25rem);
            position: relative;
            width: 100%;
        }
    .rtds-search-bar__form {
        width: 100%;
    }
    .rtds-search-bar__wrapper {
        display: flex;
    }
    .rtds-search-bar__input-wrapper {
        position: relative;
        height: var(--spacing-16);
        flex: 1 1 0%;
        --tw-bg-opacity: 1;
        background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
        display: flex;
        align-items: center;
        border-radius: 0.5rem;
            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 {
        height: 100%;
        flex: 1 1 0%;
    }
    .rtds-search-bar .autocomplete__wrapper {
        height: 100%;
    }
    /* Autocomplete input styling */
    .rtds-search-bar .autocomplete__input {
        height: 100%;
        width: 100%;
        background-color: transparent;
        padding-top: var(--spacing-2);
        padding-bottom: var(--spacing-2);
        padding-left: var(--spacing-3);
        padding-right: var(--spacing-28);
        font-size: var(--fontsize-text-sm);
        font-weight: 500;
            color: var(--color-content-03);
            border-color: var(--color-border-input);
        border-radius: 0.5rem;
        border-width: 1px;
            transition: border-color 0.2s ease;
    }
    .rtds-search-bar .autocomplete__input:hover,
        .rtds-search-bar .autocomplete__input:focus:not(:focus-visible) {
            border-color: var(--color-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 {
        outline-width: 0px;
            outline: 2px solid transparent;
            box-shadow: none;
    }
    .rtds-search-bar .autocomplete__input::-moz-placeholder {
        font-size: var(--fontsize-text-base);
            color: var(--color-content-03);
    }
    .rtds-search-bar .autocomplete__input::placeholder {
        font-size: var(--fontsize-text-base);
            color: var(--color-content-03);
    }
    /* Clear button */
    .rtds-search-bar__clear {
        position: absolute;
        right: var(--spacing-16);
        top: 50%;
        z-index: 10;
        --tw-translate-y: -50%;
        transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
            transition: background-color 0.2s ease;
    }
    /*.rtds-search-bar__clear:hover {
            @apply rtds-background-secondary rtds-text-white;
        }*/
    /* Submit button */
    .rtds-search-bar__submit {
        position: absolute;
        right: var(--spacing-2);
        top: 50%;
        z-index: 10;
        flex-shrink: 0;
        --tw-translate-y: -50%;
        transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
    }
    /* 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 {
        margin-top: var(--spacing-2);
            background-color: var(--color-background-01);
            border-color: var(--color-border-gray-01);
        border-radius: 0.5rem;
        --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
        --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
        box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
        border-width: 1px;
            max-height: var(--_autocomplete-max-height);
            overflow-y: auto;
    }
    :where(.rtds-search-bar) .autocomplete__option {
        border-width: 0px;
        padding-left: var(--spacing-3);
        padding-right: var(--spacing-3);
        padding-top: var(--spacing-2);
        padding-bottom: var(--spacing-2);
        font-size: var(--fontsize-text-sm);
        font-weight: 500;
            color: var(--color-content-03);
            background-color: var(--color-background-01);
    }
    :where(.rtds-search-bar) .autocomplete__option:hover,
        :where(.rtds-search-bar) .autocomplete__option--focused {
            background-color: var(--color-background-02);
        }
    :where(.rtds-search-bar) .autocomplete__option--focused {
          outline-style: solid;
          outline-width: 2px;
          outline-offset: -2px;
          outline-color: var(--color-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 {
        display: none;
    }
    /* Autocomplete suggestion styling */
    .rtds-search-bar__autocomplete-suggestion {
        flex-direction: column;
        display: flex;
        gap: var(--spacing-1);
    }
    .rtds-search-bar__suggestion-title {
        font-size: var(--fontsize-text-sm);
            color: var(--color-content-01);
        font-weight: 700;
    }
    .rtds-search-bar__suggestion-type {
        font-size: var(--fontsize-text-xs);
        font-weight: 400;
            color: var(--color-content-03);
    }
    /* All results option content wrapper */
    /* All results option styling */
    .autocomplete__option.rtds-search-bar__all-results-option-wrapper {
        justify-content: flex-end;
        padding-left: var(--spacing-3);
        padding-right: var(--spacing-3);
        display: flex;
    }
    .autocomplete__option.rtds-search-bar__all-results-option-wrapper:hover,
        .autocomplete__option.rtds-search-bar__all-results-option-wrapper:focus {
            background-color: var(--color-background-01);
        }
    .rtds-search-bar__all-results-option {
        width: 100%;
        flex-direction: row;
        justify-content: flex-end;
        border-top-width: 1px;
        padding-top: var(--spacing-2);
        padding-bottom: var(--spacing-2);
            border-color: var(--color-border-gray-03);
        display: flex;
    }
    .rtds-search-bar__all-results-option:hover .rtds-search-bar__all-results-link {
        text-decoration-line: underline;
    }
    .rtds-search-bar__all-results-link {
        display: flex;
        align-items: center;
        gap: var(--spacing-2);
    }
    .rtds-search-bar__all-results-link:hover {
        text-decoration-line: underline;
    }
    /* Mobile Search Modal */
    .rtds-search-bar__mobile-wrapper {
        display: inline-flex;
    }
    .rtds-search-bar__modal {
        z-index: 50;
    }
    .rtds-search-bar__modal[aria-hidden="false"] {
        display: flex;
    }
    .rtds-search-bar__modal > div {
        max-width: 100%;
    }
    
  • URL: /components/raw/search-bar/search-bar.built.css
  • Filesystem Path: components/03-molecules/search-bar/search-bar.built.css
  • Size: 7.2 KB
  • 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();
            }
        });
    }
    
    window.addEventListener('load', 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();
                }
            }
        });
    }
    
    window.addEventListener('load', initMobileSearchModal);
    
    export default initSearchBar;
    
    
  • URL: /components/raw/search-bar/search-bar.js
  • Filesystem Path: components/03-molecules/search-bar/search-bar.js
  • Size: 20.9 KB

Search Bar

Componente di ricerca con autocomplete basato su Accessible Autocomplete.


Comportamento

Il componente è renderizzato come <div data-search-bar> contenente un <form role="search"> con:

  • <label> — visibile o nascosta tramite rtds-sr-only; sempre associata al campo tramite for/id
  • <input type="text"> — campo di ricerca con autocomplete
  • <button type="button"> (opzionale) — pulsante per cancellare il testo, con <span class="rtds-sr-only"> come nome accessibile
  • <button type="submit"> — pulsante di ricerca

La variante mobile-modal aggiunge un <button> trigger esterno e una modale con focus trap.


Varianti

Varianti di Stile

  • default (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: Label visibile invece di nascosta con rtds-sr-only — utile per modali desktop

Varianti di Stato

  • mobile-modal: Include trigger button e modale mobile per la ricerca, con focus trap e gestione aria-expanded

Parametri di configurazione

Contesto di sviluppo Fractal/Nunjucks — Questa sezione riguarda esclusivamente i parametri del context .config.yml usati durante lo sviluppo nella libreria. Non descrive attributi HTML o proprietà da usare nei temi, CMS o ambienti di implementazione: per quello, fare riferimento alla sezione Esempi di utilizzo.

Parametri Base

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

Parametri per i Risultati

  • searchResults (array, opzionale): Array di oggetti con title e type per i risultati di ricerca
  • searchFilters (array, opzionale): Array di oggetti con title e type per i filtri di ricerca

Parametri per la Variante Mobile Modal

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

Accessibilità

WCAG 2.1

Livello AA — criteri pertinenti:

  • 1.3.1 Informazioni e correlazioni — la label è sempre associata al campo di input tramite for / id; il form usa role="search" per identificare la funzione di ricerca
  • 2.1.1 Tastiera — tutti i controlli (input, pulsante cerca, pulsante cancella) sono accessibili da tastiera
  • 3.3.2 Etichette o istruzioni — ogni campo ha una <label> visibile o nascosta disponibile per screen reader
  • 4.1.2 Nome, ruolo, valore — il form usa role="search"; i button icon-only hanno nomi accessibili tramite testo rtds-sr-only; nella variante mobile-modal, aria-expanded indica lo stato della modale

Struttura e semantica

  • Il componente è renderizzato come <div data-search-bar> contenente un <form role="search"> con label, input autocomplete e pulsanti
  • <label> — associata al campo tramite for/id; rimane disponibile per gli screen reader anche quando nascosta tramite rtds-sr-only
  • <button type="button"> (cancella) — ha <span class="rtds-sr-only"> come nome accessibile; l’icona ha aria-hidden="true"
  • <button type="submit"> — il testo visibile costituisce il nome accessibile

Attributi ARIA

Attributo Valori Condizione
role "search" sempre presente sul <form>
aria-expanded "true" / "false" presente sul trigger della variante mobile-modal
aria-hidden "true" / "false" gestito dinamicamente sulla modale mobile
Tasto Azione
Tab / Shift+Tab Sposta il focus tra label, input, pulsante cancella e pulsante cerca
Enter Invia il form di ricerca
Escape Chiude la modale mobile (variante mobile-modal)

Gestione del focus

  • Focus trap: presente nella variante mobile-modal — il focus rimane all’interno della modale quando è aperta
  • Focus restoration: alla chiusura della modale, il focus torna al trigger button
  • Focus visibile: lo stile :focus-visible è sempre visibile e non viene rimosso via CSS; la classe is-opened-by-keyboard / is-opened-by-pointer può personalizzare il focus ring in base al tipo di interazione

Screen reader

  • La label, anche quando nascosta visivamente, è sempre disponibile per gli screen reader tramite rtds-sr-only
  • Il form role="search" viene annunciato come landmark di ricerca
  • Nella variante mobile-modal, aria-expanded sul trigger indica lo stato aperto/chiuso della modale

Esempi di utilizzo

<!-- Esempio base -->
<div data-search-bar>
  <form role="search" action="/search" method="get">
    <label for="site-search" class="rtds-sr-only">Cerca</label>
    <input type="text" id="site-search" name="q" placeholder="Search">
    <button type="submit">Cerca</button>
  </form>
</div>

<!-- Esempio con opzioni autocomplete -->
<div data-search-bar data-options='["Opzione 1", "Opzione 2", "Opzione 3"]'>
  <form role="search" action="/search" method="get">
    <label for="autocomplete-search" class="rtds-sr-only">Cerca</label>
    <!-- Input inizializzato da Accessible Autocomplete JS -->
  </form>
</div>

Note

  • Il componente utilizza la libreria Accessible Autocomplete per l’autocompletamento
  • La variante mobile-modal rileva il tipo di interazione (puntatore/tastiera) e aggiunge classi CSS per personalizzare il focus ring