<!-- Mobile Search Trigger and Modal -->
<div class="rtds-search-bar__mobile-wrapper">
    <!-- Mobile Search Trigger -->
    <div class="rtds-main-heading__mobile-search rtds-flex lg:rtds-hidden">
        <button type="button" aria-haspopup="dialog" aria-controls="searchModalMobile" aria-expanded="false" id="searchModalMobileTrigger" class="rtds-btn rtds-btn--outline rtds-btn--icon hover:rtds-button-primary-hover focus:rtds-button-primary-hover active:rtds-button-primary-hover hover:rtds-text-white focus:rtds-text-white active:rtds-text-white">
            <span class="rtds-sr-only">Cerca nel sito - apri la modale</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#mini--magnifying-glass" />
            </svg>

        </button>
    </div>

    <!-- Mobile Search Modal -->
    <div id="searchModalMobile" class="rtds-search-bar__modal rtds-fixed rtds-items-start rtds-content-center rtds-inset-0 rtds-background-01 rtds-overflow-y-auto rtds-h-full rtds-w-full lg:rtds-hidden" style="display:none;" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="mobileSearchModalTitle" data-trigger-id="searchModalMobileTrigger" data-close-button-id="closeMobileSearchModal">
        <div class="rtds-relative rtds-w-full rtds-min-h-full rtds-bg-background-01 rtds-p-4">
            <div class="rtds-flex rtds-flex-col rtds-gap-3 rtds-max-w-full">
                <!-- Modal Header -->
                <div class="rtds-flex rtds-justify-between rtds-items-center rtds-px-0 rtds-py-0">
                    <h2 id="mobileSearchModalTitle" class="rtds-text-lg rtds-font-bold rtds-content-01">Cerca nel sito</h2>
                    <button type="button" class="rtds-btn 
    rtds-btn--icon rtds-btn--icon-square rtds-border-0 hover:rtds-background-primary hover:rtds-text-white" id="closeMobileSearchModal"><svg class="rtds-icon rtds-fill-current rtds-w-10 rtds-h-10" aria-hidden="true" focusable="false" role="img">
                            <use href="../../icons.svg#mini--x-mark" />
                        </svg>
                        <span class="rtds-sr-only">

                            Chiudi modale di ricerca
                        </span>

                    </button>

                </div>

                <!-- Search Bar Component -->
                <div class="rtds-w-full">
                    <div class="rtds-search-bar rtds-search-bar--mobile-modal" 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="mobileSearchBar" class="rtds-search-bar__label rtds-sr-only">
                                    Cerca
                                </label>

                                <div class="rtds-search-bar__input-wrapper">
                                    <div class="rtds-search-bar__autocomplete" id="mobileSearchBarContainer" 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>
            </div>
        </div>
    </div>
</div>
<!-- Mobile Search Trigger and Modal -->
<div class="rtds-search-bar__mobile-wrapper">
    <!-- Mobile Search Trigger -->
    <div class="rtds-main-heading__mobile-search rtds-flex lg:rtds-hidden">
        <button 
            type="button" 
            aria-haspopup="dialog" 
            aria-controls="{{ modalId|default('searchModalMobile') }}" 
            aria-expanded="false"
            id="{{ triggerId|default('searchModalMobileTrigger') }}" 
            class="rtds-btn rtds-btn--outline rtds-btn--icon hover:rtds-button-primary-hover focus:rtds-button-primary-hover active:rtds-button-primary-hover hover:rtds-text-white focus:rtds-text-white active:rtds-text-white"
        >
            <span class="rtds-sr-only">{{ triggerLabel|default('Cerca nel sito - apri la modale') }}</span>
            {% render '@icon--small', { id: 'mini--magnifying-glass', size: 'rtds-w-6 rtds-h-6' }, true %}
        </button>
    </div>

    <!-- Mobile Search Modal -->
    <div
        id="{{ modalId|default('searchModalMobile') }}"
        class="rtds-search-bar__modal rtds-fixed rtds-items-start rtds-content-center rtds-inset-0 rtds-background-01 rtds-overflow-y-auto rtds-h-full rtds-w-full lg:rtds-hidden"
        style="display:none;"
        aria-hidden="true"
        role="dialog"
        aria-modal="true"
        aria-labelledby="{{ modalTitleId|default('mobileSearchModalTitle') }}"
        {% if triggerId %}data-trigger-id="{{ triggerId }}"{% endif %}
        {% if closeButtonId %}data-close-button-id="{{ closeButtonId }}"{% endif %}>
        <div class="rtds-relative rtds-w-full rtds-min-h-full rtds-bg-background-01 rtds-p-4">
            <div class="rtds-flex rtds-flex-col rtds-gap-3 rtds-max-w-full">
                <!-- Modal Header -->
                <div class="rtds-flex rtds-justify-between rtds-items-center rtds-px-0 rtds-py-0">
                    <h2 id="{{ modalTitleId|default('mobileSearchModalTitle') }}" class="rtds-text-lg rtds-font-bold rtds-content-01">{{ modalTitle|default('Cerca nel sito') }}</h2>
                    {% render '@button--icon-square', {
                        buttonType: 'button',
                        id: closeButtonId|default('closeMobileSearchModal'),
                        icon: 'mini--x-mark',
                        iconSize: 'rtds-w-10 rtds-h-10',
                        labelHidden: true,
                        label: closeButtonLabel|default('Chiudi modale di ricerca'),
                        classes: 'rtds-border-0 hover:rtds-background-primary hover:rtds-text-white'
                    }, true %}
                </div>

                <!-- Search Bar Component -->
                <div class="rtds-w-full">
                    {% render '@search-bar', {
                        searchId: searchId|default('mobileSearchBar'),
                        label: label|default('Cerca'),
                        submitLabel: submitLabel|default('Cerca'),
                        showClearButton: showClearButton|default(true),
                        placeholder: placeholder|default('Search'),
                        allResultsHref: allResultsHref|default('/search?q='),
                        allResultsLabel: allResultsLabel|default('Tutti i risultati'),
                        classes: 'rtds-search-bar--mobile-modal'
                    }, true %}
                </div>
            </div>
        </div>
    </div>
</div>
{
  "label": "Cerca",
  "searchId": "mobileSearchBar",
  "submitLabel": "Cerca",
  "showClearButton": true,
  "showLabel": false,
  "formAction": "/search",
  "formMethod": "get",
  "placeholder": "Search",
  "allResultsHref": "/search?q=",
  "allResultsLabel": "Tutti i risultati",
  "modalId": "searchModalMobile",
  "modalTitleId": "mobileSearchModalTitle",
  "modalTitle": "Cerca nel sito",
  "triggerId": "searchModalMobileTrigger",
  "triggerLabel": "Cerca nel sito - apri la modale",
  "closeButtonId": "closeMobileSearchModal",
  "closeButtonLabel": "Chiudi modale di ricerca"
}
  • 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