<div class="rtds-dialog rtds-modal-dialog--inverse ">
    <div role="dialog" id="dialog1" aria-labelledby="dialog1Label" aria-modal="true" class="rtds-hidden">

        <div class="rtds-dialog__header">
            <h2 id="dialog1Label" class="rtds-dialog__title rtds-heading-3">

                Titolo modale
            </h2>

            <button type="button" class="rtds-btn 
    rtds-btn--icon rtds-btn--l rtds-dialog__close-button" id="close-dialog-button" onclick="closeDialog(this)"><svg class="rtds-icon rtds-fill-current rtds-w-6 rtds-h-6 md:rtds-w-8 md:rtds-h-8" aria-hidden="true" focusable="false" role="img">
                    <use href="../../icons.svg#mini--x-mark" />
                </svg>
                <span class="rtds-sr-only">

                    Chiudi la modale corrente
                </span>

            </button>

        </div>

        <div class="rtds-dialog__content" tabindex="0">

            Qui error necessitatibus nam rerum corporis et culpa iste eos excepturi ipsam vel sequi voluptatem ad distinctio accusantium sed galisum voluptas. Quo voluptatem dicta non totam possimus est voluptatem magnam. Et exercitationem molestiae ut veniam consectetur rem maxime odit.

        </div>

        <div class="rtds-dialog__actions">

            <button type="button" class="rtds-btn 
    rtds-btn--primary
     rtds-btn--outlined" onclick="closeDialog(this)">

                Annulla

            </button>

            <button type="button" class="rtds-btn 
    rtds-btn--primary
     rtds-btn--primary">

                Salva

            </button>

        </div>

    </div>
</div>
{% extends '@modal-dialog' %}

{% block classes %} rtds-modal-dialog--inverse {{ classes }}{% endblock classes %}
{
  "dialogId": "dialog1",
  "label": "Titolo modale",
  "content": "Qui error necessitatibus nam rerum corporis et culpa iste eos excepturi ipsam vel sequi voluptatem ad distinctio accusantium sed galisum voluptas. Quo voluptatem dicta non totam possimus est voluptatem magnam. Et exercitationem molestiae ut veniam consectetur rem maxime odit.",
  "titleClasses": "rtds-heading-3",
  "hasActions": true,
  "buttons": [
    {
      "label": "Annulla",
      "callback": "closeDialog(this)",
      "classes": "rtds-btn--outlined"
    },
    {
      "label": "Salva",
      "classes": "rtds-btn--primary"
    }
  ],
  "closeButton": {
    "variant": "--icon",
    "context": {
      "label": "Chiudi la modale corrente",
      "icon": "mini--x-mark",
      "id": "close-dialog-button",
      "classes": "rtds-btn--l rtds-dialog__close-button",
      "iconSize": "rtds-w-6 rtds-h-6 md:rtds-w-8 md:rtds-h-8",
      "callback": "closeDialog(this)"
    }
  }
}
  • Content:
    /**
     * MODAL DIALOG
     *
    */
    @layer components {
        :where(.rtds-dialog) [role="dialog"] {
            @apply rtds-flex rtds-flex-col rtds-bg-white md:rtds-rounded-md md:rtds-overflow-hidden rtds-min-h-[100vh] md:rtds-min-h-[13rem] md:rtds-max-h-[90vh] md:rtds-absolute md:rtds-shadow-lg md:rtds-left-[50vw] md:rtds-top-[50vh] md:-rtds-translate-x-[50%] md:-rtds-translate-y-[50%];
        }   
    
        :where(.rtds-dialog-backdrop.is-active) [role="dialog"] {
            @apply rtds-flex;
        }
    
        /* HEADER */
        .rtds-dialog__header {
            @apply rtds-flex rtds-justify-between rtds-px-4 rtds-py-3 lg:rtds-px-6 lg:rtds-py-4 rtds-gap-2 rtds-items-center rtds-border-b rtds-border-gray-01 rtds-bg-white;
        }
    
        :where(.rtds-modal-dialog--inverse) .rtds-dialog__header  {
            @apply rtds-background-primary rtds-content-inverse;
        }
    
        :where(.rtds-modal-dialog--s) .rtds-dialog__header  {
            @apply lg:rtds-px-4 lg:rtds-py-2;
        }
    
        .rtds-dialog__content {
            @apply rtds-bg-white rtds-flex-1 rtds-overflow-y-auto;        
        }
    
    
        .rtds-dialog__content:focus-visible {
            box-shadow: inset 0 0 0 3px var(--tw-ring-color);
        }
    
        .rtds-dialog__content,
        .rtds-dialog__actions {
            @apply rtds-px-4 rtds-py-3 lg:rtds-px-6 lg:rtds-py-4;
        }
    
        .rtds-dialog__actions {
            @apply rtds-pt-4 rtds-flex rtds-gap-2 sm:min-h-480:rtds-sticky rtds-bottom-0 rtds-border-t rtds-border-gray-01 rtds-bg-white;
        }
    
        :where(.rtds-modal-dialog--s) .rtds-dialog__actions {
            @apply lg:rtds-p-4 ;
        }
    
        .rtds-dialog__actions .rtds-btn {
            @apply rtds-flex-1 lg:rtds-flex-none;
        }
    
        .rtds-dialog__close-button {
            @apply rtds-bg-transparent rtds-border-transparent focus:rtds-bg-transparent focus:rtds-border-transparent;
        }
    
    
        @media screen and (min-width: 768px) {
            :where(.rtds-dialog) [role="dialog"] {
                min-width: calc(768px - (15px * 2));
            /* == breakpoint - left+right margin */
            }
        }
    
        /* dialog::backdrop, */
        .rtds-dialog-backdrop {
            @apply rtds-hidden rtds-fixed rtds-overflow-y-auto rtds-w-full rtds-h-full rtds-top-0 rtds-right-0 rtds-bottom-0 rtds-left-0 rtds-z-40 rtds-bg-black/25
        }
    
        .rtds-dialog-backdrop.is-active {
            @apply rtds-block;
        }
    
        .no-scroll {
            overflow-y: auto !important;
        }
    
        /* this is added to the body when a dialog is open */
        .has-dialog {
            overflow: hidden !important;
        }
    
    }
  • URL: /components/raw/modal-dialog/modal-dialog.css
  • Filesystem Path: components/04-organisms/modal-dialog/modal-dialog.css
  • Size: 2.5 KB
  • Content:
    /*
     *   This content is licensed according to the W3C Software License at
     *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     */
    
    'use strict';
    
    var aria = aria || {};
    
    aria.Utils = aria.Utils || {};
    
    (function () {
      /*
       * When util functions move focus around, set this true so the focus listener
       * can ignore the events.
       */
      aria.Utils.IgnoreUtilFocusChanges = false;
    
      aria.Utils.dialogOpenClass = 'has-dialog';
    
      /**
       * @description Set focus on descendant nodes until the first focusable element is
       *       found.
       * @param element
       *          DOM node for which to find the first focusable descendant.
       * @returns {boolean}
       *  true if a focusable element is found and focus is set.
       */
      aria.Utils.focusFirstDescendant = function (element) {
        for (var i = 0; i < element.childNodes.length; i++) {
          var child = element.childNodes[i];
          if (
            aria.Utils.attemptFocus(child) ||
            aria.Utils.focusFirstDescendant(child)
          ) {
            return true;
          }
        }
        return false;
      }; // end focusFirstDescendant
    
      /**
       * @description Find the last descendant node that is focusable.
       * @param element
       *          DOM node for which to find the last focusable descendant.
       * @returns {boolean}
       *  true if a focusable element is found and focus is set.
       */
      aria.Utils.focusLastDescendant = function (element) {
        for (var i = element.childNodes.length - 1; i >= 0; i--) {
          var child = element.childNodes[i];
          if (
            aria.Utils.attemptFocus(child) ||
            aria.Utils.focusLastDescendant(child)
          ) {
            return true;
          }
        }
        return false;
      }; // end focusLastDescendant
    
      /**
       * @description Set Attempt to set focus on the current node.
       * @param element
       *          The node to attempt to focus on.
       * @returns {boolean}
       *  true if element is focused.
       */
      aria.Utils.attemptFocus = function (element) {
        if (!aria.Utils.isFocusable(element)) {
          return false;
        }
    
        aria.Utils.IgnoreUtilFocusChanges = true;
        try {
          element.focus();
        } catch (e) {
          // continue regardless of error
        }
        aria.Utils.IgnoreUtilFocusChanges = false;
        return document.activeElement === element;
      }; // end attemptFocus
    
      /* Modals can open modals. Keep track of them with this array. */
      aria.OpenDialogList = aria.OpenDialogList || new Array(0);
    
      /**
       * @returns {object} the last opened dialog (the current dialog)
       */
      aria.getCurrentDialog = function () {
        if (aria.OpenDialogList && aria.OpenDialogList.length) {
          return aria.OpenDialogList[aria.OpenDialogList.length - 1];
        }
      };
    
      aria.closeCurrentDialog = function () {
        var currentDialog = aria.getCurrentDialog();
        if (currentDialog) {
          currentDialog.close(true);
          return true;
        }
        return false;
      };
    
      aria.handleEscape = function (event) {
        var key = event.which || event.keyCode;
    
        if (key === aria.KeyCode.ESC) {
          // Se il focus è all'interno di un dropdown dopo la chiusura della modale
          const activeElement = document.activeElement;
          if (activeElement && activeElement.closest('.rtds-dropdown-menu__list')) {
            const dropdownMenu = activeElement.closest('.rtds-dropdown-menu');
            if (dropdownMenu) {
              const dropdownList = dropdownMenu.querySelector('.rtds-dropdown-menu__list');
              const dropdownTrigger = dropdownMenu.querySelector('.rtds-dropdown-trigger');
              if (dropdownList && dropdownTrigger) {
                dropdownList.classList.add('rtds-hidden');
                dropdownTrigger.setAttribute('aria-expanded', 'false');
                dropdownTrigger.focus();
                event.stopPropagation();
                return;
              }
            }
          }
          
          // Gestione normale della chiusura della modale
          if (aria.closeCurrentDialog()) {
            event.stopPropagation();
          }
        }
      };
    
      document.addEventListener('keyup', aria.handleEscape);
    
      /**
       * @class
       * @description Dialog object providing modal focus management.
       *
       * Assumptions: The element serving as the dialog container is present in the
       * DOM and hidden. The dialog container has role='dialog'.
       * @param dialogId
       *          The ID of the element serving as the dialog container.
       * @param focusAfterClosed
       *          Either the DOM node or the ID of the DOM node to focus when the
       *          dialog closes.
       * @param focusFirst
       *          Optional parameter containing either the DOM node or the ID of the
       *          DOM node to focus when the dialog opens. If not specified, the
       *          first focusable element in the dialog will receive focus.
       */
      aria.Dialog = function (dialogId, focusAfterClosed, focusFirst) {
        this.dialogNode = document.getElementById(dialogId);
        if (this.dialogNode === null) {
          throw new Error('No element found with id="' + dialogId + '".');
        }
    
        var validRoles = ['dialog', 'alertdialog'];
        var isDialog = (this.dialogNode.getAttribute('role') || '')
          .trim()
          .split(/\s+/g)
          .some(function (token) {
            return validRoles.some(function (role) {
              return token === role;
            });
          });
        if (!isDialog) {
          throw new Error(
            'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.'
          );
        }
    
        // Wrap in an individual backdrop element if one doesn't exist
        // Native <dialog> elements use the ::backdrop pseudo-element, which
        // works similarly.
        var backdropClass = 'rtds-dialog-backdrop';
        if (this.dialogNode.parentNode.classList.contains(backdropClass)) {
          this.backdropNode = this.dialogNode.parentNode;
        } else {
          this.backdropNode = document.createElement('div');
          this.backdropNode.className = backdropClass;
          this.dialogNode.parentNode.insertBefore(
            this.backdropNode,
            this.dialogNode
          );
          this.backdropNode.appendChild(this.dialogNode);
        }
        this.backdropNode.classList.add('is-active');
    
        // Disable scroll on the body element
        document.body.classList.add(aria.Utils.dialogOpenClass);
    
        if (typeof focusAfterClosed === 'string') {
          this.focusAfterClosed = document.getElementById(focusAfterClosed);
        } else if (typeof focusAfterClosed === 'object') {
          this.focusAfterClosed = focusAfterClosed;
        } else {
          throw new Error(
            'the focusAfterClosed parameter is required for the aria.Dialog constructor.'
          );
        }
    
        if (typeof focusFirst === 'string') {
          this.focusFirst = document.getElementById(focusFirst);
        } else if (typeof focusFirst === 'object') {
          this.focusFirst = focusFirst;
        } else {
          this.focusFirst = null;
        }
    
        // Bracket the dialog node with two invisible, focusable nodes.
        // While this dialog is open, we use these to make sure that focus never
        // leaves the document even if dialogNode is the first or last node.
        var preDiv = document.createElement('div');
        this.preNode = this.dialogNode.parentNode.insertBefore(
          preDiv,
          this.dialogNode
        );
        this.preNode.tabIndex = 0;
        var postDiv = document.createElement('div');
        this.postNode = this.dialogNode.parentNode.insertBefore(
          postDiv,
          this.dialogNode.nextSibling
        );
        this.postNode.tabIndex = 0;
    
        // If this modal is opening on top of one that is already open,
        // get rid of the document focus listener of the open dialog.
        if (aria.OpenDialogList.length > 0) {
          aria.getCurrentDialog().removeListeners();
        }
    
        this.addListeners();
        aria.OpenDialogList.push(this);
        this.dialogNode.className = 'default-dialog'; // make visible
    
        if (this.focusFirst) {
          this.focusFirst.focus();
        } else {
          aria.Utils.focusFirstDescendant(this.dialogNode);
        }
    
        this.lastFocus = document.activeElement;
      }; // end Dialog constructor
    
      //aria.Dialog.prototype.clearDialog = function () {
        // Array.prototype.map.call(
        //   this.dialogNode.querySelectorAll('input'),
        //   function (input) {
        //     input.value = '';
        //   }
        // );
      //};
    
      /**
       * @description
       *  Hides the current top dialog,
       *  removes listeners of the top dialog,
       *  restore listeners of a parent dialog if one was open under the one that just closed,
       *  and sets focus on the element specified for focusAfterClosed.
       */
      aria.Dialog.prototype.close = function (isKeyboardClose) {
        aria.OpenDialogList.pop();
        this.removeListeners();
        aria.Utils.remove(this.preNode);
        aria.Utils.remove(this.postNode);
        this.dialogNode.className = 'rtds-hidden';
        this.backdropNode.classList.remove('is-active');
    
        // Gestione speciale per trigger all'interno di dropdown solo se chiuso da tastiera
        if (isKeyboardClose && this.focusAfterClosed && this.focusAfterClosed.closest('.rtds-dropdown-menu__list')) {
          const dropdownMenu = this.focusAfterClosed.closest('.rtds-dropdown-menu');
          if (dropdownMenu) {
            const dropdownList = dropdownMenu.querySelector('.rtds-dropdown-menu__list');
            if (dropdownList) {
              dropdownList.classList.remove('rtds-hidden');
              // Gestione dell'attributo aria-expanded del trigger
              const dropdownTrigger = dropdownMenu.querySelector('.rtds-dropdown-trigger');
              if (dropdownTrigger) {
                dropdownTrigger.setAttribute('aria-expanded', 'true');
              }
            }
          }
        } else if (this.focusAfterClosed && this.focusAfterClosed.closest('.rtds-dropdown-menu__list')) {
          // Se chiuso da mouse, chiudi il dropdown
          const dropdownMenu = this.focusAfterClosed.closest('.rtds-dropdown-menu');
          if (dropdownMenu) {
            const dropdownList = dropdownMenu.querySelector('.rtds-dropdown-menu__list');
            const dropdownTrigger = dropdownMenu.querySelector('.rtds-dropdown-trigger');
            if (dropdownList && dropdownTrigger) {
              dropdownList.classList.add('rtds-hidden');
              dropdownTrigger.setAttribute('aria-expanded', 'false');
            }
          }
        }
    
        this.focusAfterClosed.focus();
    
        // If a dialog was open underneath this one, restore its listeners.
        if (aria.OpenDialogList.length > 0) {
          aria.getCurrentDialog().addListeners();
        } else {
          document.body.classList.remove(aria.Utils.dialogOpenClass);
        }
      }; // end close
    
      /**
       * @description
       *  Hides the current dialog and replaces it with another.
       * @param newDialogId
       *  ID of the dialog that will replace the currently open top dialog.
       * @param newFocusAfterClosed
       *  Optional ID or DOM node specifying where to place focus when the new dialog closes.
       *  If not specified, focus will be placed on the element specified by the dialog being replaced.
       * @param newFocusFirst
       *  Optional ID or DOM node specifying where to place focus in the new dialog when it opens.
       *  If not specified, the first focusable element will receive focus.
       */
      aria.Dialog.prototype.replace = function (
        newDialogId,
        newFocusAfterClosed,
        newFocusFirst
      ) {
        aria.OpenDialogList.pop();
        this.removeListeners();
        aria.Utils.remove(this.preNode);
        aria.Utils.remove(this.postNode);
        this.dialogNode.className = 'rtds-hidden';
        this.backdropNode.classList.remove('is-active');
    
        var focusAfterClosed = newFocusAfterClosed || this.focusAfterClosed;
        new aria.Dialog(newDialogId, focusAfterClosed, newFocusFirst);
      }; // end replace
    
      aria.Dialog.prototype.addListeners = function () {
        document.addEventListener('focus', this.trapFocus, true);
      }; // end addListeners
    
      aria.Dialog.prototype.removeListeners = function () {
        document.removeEventListener('focus', this.trapFocus, true);
      }; // end removeListeners
    
      aria.Dialog.prototype.trapFocus = function (event) {
        if (aria.Utils.IgnoreUtilFocusChanges) {
          return;
        }
        var currentDialog = aria.getCurrentDialog();
        if (currentDialog.dialogNode.contains(event.target)) {
          currentDialog.lastFocus = event.target;
        } else {
          aria.Utils.focusFirstDescendant(currentDialog.dialogNode);
          if (currentDialog.lastFocus == document.activeElement) {
            aria.Utils.focusLastDescendant(currentDialog.dialogNode);
          }
          currentDialog.lastFocus = document.activeElement;
        }
      }; // end trapFocus
    
      window.openDialog = function (dialogId, focusAfterClosed, focusFirst) {
        new aria.Dialog(dialogId, focusAfterClosed, focusFirst);
      };
    
      window.closeDialog = function (closeButton) {
        var topDialog = aria.getCurrentDialog();
        if (topDialog && topDialog.dialogNode.contains(closeButton)) {
          topDialog.close(false);
        }
      }; // end closeDialog
    
      window.replaceDialog = function (
        newDialogId,
        newFocusAfterClosed,
        newFocusFirst
      ) {
        var topDialog = aria.getCurrentDialog();
        if (topDialog.dialogNode.contains(document.activeElement)) {
          topDialog.replace(newDialogId, newFocusAfterClosed, newFocusFirst);
        }
      }; // end replaceDialog
    
      // Gestione automatica dei modali con classe rtds-dialog--visible
      document.addEventListener('DOMContentLoaded', function() {
        var visibleDialogs = document.querySelectorAll('.rtds-dialog--visible');
        visibleDialogs.forEach(function(dialog) {
          var dialogElement = dialog.querySelector('[role="dialog"]');
          if (dialogElement) {
            openDialog(dialogElement.id, document.body);
          }
        });
      });
    })();
    'use strict';
    /**
     * @namespace aria
     */
    
    var aria = aria || {};
    
    /**
     * @description
     *  Key code constants
     */
    aria.KeyCode = {
      BACKSPACE: 8,
      TAB: 9,
      RETURN: 13,
      SHIFT: 16,
      ESC: 27,
      SPACE: 32,
      PAGE_UP: 33,
      PAGE_DOWN: 34,
      END: 35,
      HOME: 36,
      LEFT: 37,
      UP: 38,
      RIGHT: 39,
      DOWN: 40,
      DELETE: 46,
    };
    
    aria.Utils = aria.Utils || {};
    
    // Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
    aria.Utils.matches = function (element, selector) {
      if (!Element.prototype.matches) {
        Element.prototype.matches =
          Element.prototype.matchesSelector ||
          Element.prototype.mozMatchesSelector ||
          Element.prototype.msMatchesSelector ||
          Element.prototype.oMatchesSelector ||
          Element.prototype.webkitMatchesSelector ||
          function (s) {
            var matches = element.parentNode.querySelectorAll(s);
            var i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {
              // empty
            }
            return i > -1;
          };
      }
    
      return element.matches(selector);
    };
    
    aria.Utils.remove = function (item) {
      if (item.remove && typeof item.remove === 'function') {
        return item.remove();
      }
      if (
        item.parentNode &&
        item.parentNode.removeChild &&
        typeof item.parentNode.removeChild === 'function'
      ) {
        return item.parentNode.removeChild(item);
      }
      return false;
    };
    
    aria.Utils.isFocusable = function (element) {
      if (element.tabIndex < 0) {
        return false;
      }
    
      if (element.disabled) {
        return false;
      }
    
      switch (element.nodeName) {
        case 'A':
          return !!element.href && element.rel != 'ignore';
        case 'INPUT':
          return element.type != 'hidden';
        case 'BUTTON':
        case 'SELECT':
        case 'TEXTAREA':
          return true;
        default:
          return false;
      }
    };
    
    aria.Utils.getAncestorBySelector = function (element, selector) {
      if (!aria.Utils.matches(element, selector + ' ' + element.tagName)) {
        // Element is not inside an element that matches selector
        return null;
      }
    
      // Move up the DOM tree until a parent matching the selector is found
      var currentNode = element;
      var ancestor = null;
      while (ancestor === null) {
        if (aria.Utils.matches(currentNode.parentNode, selector)) {
          ancestor = currentNode.parentNode;
        } else {
          currentNode = currentNode.parentNode;
        }
      }
    
      return ancestor;
    };
    
    aria.Utils.hasClass = function (element, className) {
      return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
    };
    
    aria.Utils.addClass = function (element, className) {
      if (!aria.Utils.hasClass(element, className)) {
        element.className += ' ' + className;
      }
    };
    
    aria.Utils.removeClass = function (element, className) {
      var classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)');
      element.className = element.className.replace(classRegex, ' ').trim();
    };
    
    aria.Utils.bindMethods = function (object /* , ...methodNames */) {
      var methodNames = Array.prototype.slice.call(arguments, 1);
      methodNames.forEach(function (method) {
        object[method] = object[method].bind(object);
      });
    };
    
  • URL: /components/raw/modal-dialog/modal-dialog.js
  • Filesystem Path: components/04-organisms/modal-dialog/modal-dialog.js
  • Size: 16.6 KB

Componente Modal Dialog

Componente per la gestione di finestre modali accessibili e personalizzabili.

Comportamento e Funzionalità

Il componente Modal Dialog è progettato per gestire finestre modali con supporto completo per l’accessibilità. Include:

  • Gestione delle etichette per la modale
  • Supporto per pulsanti di chiusura
  • Personalizzazione dei pulsanti di azione
  • Supporto per icone personalizzate
  • Gestione del contenuto dinamico
  • Supporto per layout responsive

Accessibilità

Il componente è stato progettato seguendo le best practice di accessibilità WCAG 2.1:

Etichette e Struttura

  • La modale è sempre associata a un’etichetta (aria-labelledby) che descrive il suo scopo
  • La modale è implementata con il ruolo dialog e l’attributo aria-modal="true"
  • Il titolo della modale è strutturato semanticamente con <h2>
  • I pulsanti di azione sono implementati in modo accessibile

Gestione del Focus e della Tastiera

Lo script implementa una gestione avanzata del focus e della navigazione da tastiera:

Gestione del Focus

  • Trapping del Focus: Il focus è intrappolato all’interno della modale quando è aperta
  • Focus Iniziale: Al momento dell’apertura, il focus viene automaticamente spostato sul primo elemento interattivo
  • Focus Finale: Alla chiusura, il focus viene ripristinato sull’elemento che ha attivato la modale
  • Nodi di Supporto: Vengono creati nodi invisibili prima e dopo la modale per garantire che il focus rimanga all’interno

Gestione della Tastiera

  • Tasto ESC: La modale può essere chiusa con il tasto ESC
  • Tab Navigation: La navigazione con Tab è gestita per mantenere il focus all’interno della modale
  • Shift+Tab: La navigazione inversa è supportata e gestita correttamente

Gestione degli Stati

  • Backdrop: Viene aggiunto un overlay semitrasparente per indicare visivamente lo stato di modale aperta
  • Scroll Lock: Lo scroll della pagina viene bloccato quando la modale è aperta
  • Stack di Modali: Supporto per modali annidate con gestione corretta dello stato di ciascuna

Gestione degli Eventi

  • Eventi di Focus: Monitoraggio degli eventi di focus per garantire che rimanga all’interno della modale
  • Eventi di Chiusura: Gestione degli eventi di chiusura per ripristinare lo stato della pagina
  • Eventi di Tastiera: Gestione degli eventi della tastiera per la navigazione e l’interazione

Supporto per Screen Reader

  • Annunci di Stato: Gli screen reader vengono notificati quando la modale viene aperta o chiusa
  • Contesto Semantico: La struttura semantica è mantenuta per una corretta interpretazione da parte degli screen reader
  • Ordine di Lettura: L’ordine di lettura è logico e segue il flusso naturale dell’interfaccia

Markup e Struttura

Il componente è strutturato come segue:

<div class="rtds-dialog">
    <div role="dialog" id="[dialogId]" aria-labelledby="[dialogId]Label" aria-modal="true">
        <div class="rtds-dialog__header">
            <h2 id="[dialogId]Label" class="rtds-dialog__title rtds-heading-3">
                [Icona opzionale]
                [Titolo modale]
            </h2>
            [Pulsante di chiusura opzionale]
        </div>
        <div class="rtds-dialog__content">
            [Contenuto della modale]
        </div>
        <div class="rtds-dialog__actions">
            [Pulsanti di azione]
        </div>
    </div>
</div>

Configurazioni per lo Sviluppo in Nunjucks

Parametri Disponibili

Identificazione e Contenuto

  • dialogId: ID univoco per la modale (obbligatorio)
  • label: Titolo della modale
  • content: Contenuto testuale della modale
  • leftIcon: ID dell’icona da mostrare a sinistra del titolo (opzionale)

Interfaccia Utente

  • closeButton: Booleano per mostrare/nascondere il pulsante di chiusura
  • buttons: Array di oggetti contenenti i pulsanti di azione
    • label: Testo del pulsante
    • callback: Funzione JavaScript da eseguire al click
    • classes: Classi CSS aggiuntive per il pulsante
    • type: Tipo di pulsante (“button-icon-left” o “button-icon-right” per pulsanti con icone)

Varianti

Il componente include diverse possibilità di personalizzazione:

  1. Default: Modale base con pulsante di chiusura e pulsanti di azione
  2. Con Icona: Modale con icona a sinistra del titolo
  3. Filtri: Variante specifica per modali di filtro con pulsanti personalizzati