See "img upload" in sidebar snet/molecules for variants, or go to info panel and look for it in "references" (@snet-img-upload)
Refer to modal frame found in view tab for complete structure without content.
A modal's default size is 640px
Modifier classes for modals
A modal with a content loading component behaves as follows:
        
        <div class="sds-modal modal fade " id="imgConfirmation" aria-labelledby="imgConfirmationTitle" aria-modal="true" role="dialog" tabindex="-1">
    <div class="sds-modal__dialog modal-dialog modal-dialog-centered modal-sm">
        <div class="sds-modal__content modal-content">
            <div class="sds-modal__header modal-header">
                <img class="align-self-end" src="https://design.spuerkeess.lu/media/illustrations/svg240x240/spot/sdsillu-emailb.svg" alt="" width="80">
                <button type="button" class="sds-btn -iconBtn -btnSecondary -ghost sds-modal__close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true" class="sds-icon sds-icon-close"></span>
                </button>
            </div>
            <div class="sds-modal__body modal-body">
                <div class="sds-stackLg">
                    <div class="h3" id="imgConfirmationTitle">Recevoir votre offre immobilière par e-mail</div>
                    <p class="sds-textBodyLg">Un email vient de vous être envoyé, si vous ne le trouvez pas, il pourrait être dans vos spams.</p>
                </div>
            </div>
            <div class="sds-modal__footer modal-footer justify-content-center">
                <button type="button" class="sds-btn -btnPrimary" data-dismiss="modal">
                    <span class="sds-btn__text">
                        Fermer
                    </span>
                </button>
            </div>
        </div>
    </div>
</div>
        
    
        {% extends "@snet-modal-base-frame" %}
{% block header %}
	<img class="align-self-end" src="{{ ("https://design.spuerkeess.lu/media" + illustrationsSDSPath + svg240xPath  + "/spot/sdsillu-emailb.svg") }}" alt="" width="80">
{% endblock %}
{% block body %}
<div class="{{ namespace }}stackLg">
	<div class="h3" id="{{ modalTitleID }}">Recevoir votre offre immobilière par e-mail</div>
	<p class="{{ namespace }}textBodyLg">Un email vient de vous être envoyé, si vous ne le trouvez pas, il pourrait être dans vos spams.</p>
</div>
{% endblock %}
{% block footer %}
	{% render "@btn-primary", {
		text: "Fermer",
		attrs: {
			"data-dismiss": "modal"
		}
	} ,true %}
{% endblock %}
    
                                export default class ModalTrap {
	constructor() {
		// select all modals with the dialog role and modal classe.
		// this ensures that only modals with the right attributes are selected
		this.modals = document.querySelectorAll("[role='dialog'].modal");
		// select all modal trigger buttons that don't have a data-dismiss attribute
		// buttons with data-dismiss and data-toggle are typically only used within modals to trigger another modal
		// we want to exclude these from the selection
		this.modalToggles = document.querySelectorAll("[data-toggle='modal']:not([data-dismiss='modal'])");
		// list of focusable elements inside a modal
		this.focusableElements = [
			"a[href]",
			"button:not([disabled])",
			"textarea:not([disabled])",
			"input:not([disabled])",
			"select:not([disabled])",
			"[tabindex]:not([tabindex='-1'])"
		];
		// this will ensure that any modal with the dialog-scrollable modifier gets the intended behaviour
		// all other modals will get ignored
		this.modalScrollBody = ".modal-dialog-scrollable .modal-body"
		// modal trigger element with same data-target value as id of triggered modal
		this.modalTriggerElement = null;
		// Used to store keydown handlers per modal
		this.focusHandlers = new WeakMap();
		this.init();
	}
	init() {
		// loop over all modal trigger buttons
		this.modalToggles.forEach(btn => {
			// if data target value is erroneous or doesn't match any modal ID, return false
			const targetSelector = btn.getAttribute('data-target');
			if ((targetSelector === "#") || (!targetSelector)) return;
			// on modal trigger interaction, set the modal trigger element's value to that of its DOM representation
			// and store it
			btn.addEventListener('click', (e) => {
				e.preventDefault();
				this.modalTriggerElement = e.target;
			});
		});
		// loop over all modals
		this.modals.forEach(modal => {
			const closeBtn = modal.querySelector('.sds-modal__close');
			// when a modal shown event is fired:
			$(modal).on('shown.bs.modal', (e) => {
				// check if a close button exists and focus it if so
				if (closeBtn) {
					closeBtn.focus();
				}
				else {
					// if no close is present focus first focusable element instead
					let focusableElements = this.getFocusableElements(modal);
					let first = focusableElements[0];
					first.focus()
				}
				// check for a scrollable body element inside the modal
				// set focus trap inside modal while open
				this.checkScrollabilityAndSetFocusTrap(modal);
				/*
				 * if the modal gets shown through the bootstrap method instead of
				 * through a trigger element interaction, store the value of the trigger element
				 * so it can be focused when closing the modal
				 *
				 * this will ensure that no matter how the modal is shown,
				 * there's always an element to put the focus back on when closed
				 *
				 * closing a modal after navigating from modal to modal will also put the focus back on
				 * the initial modal trigger element or corresponding modal's trigger element, if applicable
				 * for this purpose, modal trigger elements inside modals that have the data-dismiss="modal" attribute are ignored
				 */
				let lastFocusedElement = document.querySelector("[data-toggle='modal']:not([data-dismiss='modal'])[data-target='#"+ e.target.getAttribute("id") +"']");
				if (lastFocusedElement) {
					this.modalTriggerElement = lastFocusedElement;
				}
			});
			$(modal).on('hidden.bs.modal', (e) => {
				// if it exists focus the modal's trigger element upon closing the modal
				if (this.modalTriggerElement) {
					this.modalTriggerElement.focus();
				}
			});
		});
	}
	checkScrollabilityAndSetFocusTrap(modal) {
		const modalBodySelector = this.modalScrollBody;
		// check if the modal scrollable modifier is applied to the current modal
		// if not, the last element to receive focus will be the modal itself
		const modalBody = modal.querySelector(modalBodySelector) ? modal.querySelector(modalBodySelector) : modal;
		// add or remove tabindex depending on whether the modal body area is scrollable
		function setScrollability () {
			const isScrollable = modalBody.scrollHeight > modalBody.clientHeight;
			if (isScrollable) {
				// Ensure it's focusable
				if (!modalBody.hasAttribute('tabindex')) {
					modalBody.setAttribute('tabindex', '0');
				}
			} else {
				if (modalBody.hasAttribute('tabindex')) {
					modalBody.removeAttribute('tabindex');
				}
			}
		}
		// check if modal body is scriollable or not on page load
		setScrollability();
		// Initial trap
		this.trapFocus(modal);
		// Observe scrollability changes
		const observer = new ResizeObserver(() => {
			// check if modal body becomes scrollable on resize
			setScrollability();
			this.trapFocus(modal);
		});
		// Required to detect resizing/scrollability
		observer.observe(modalBody);
	}
	// trap the kesyboard focus navigfation inside the modal while it is shown
	trapFocus(modal) {
		const focusable = this.getFocusableElements(modal);
		// abort if no focusable elements are found inside modal
		if (!focusable.length) return;
		// define first and last focusable elements inside modal
		const first = focusable[0];
		const last = focusable[focusable.length - 1];
		// Remove old handler if exists
		if (this.focusHandlers.has(modal)) {
			modal.removeEventListener('keydown', this.focusHandlers.get(modal));
		}
		// move focus from first to last and vice-versa when using tab or shift+tab
		const handleKeydown = (e) => {
			if (e.key !== 'Tab') return;
			if (e.shiftKey && document.activeElement === first) {
				e.preventDefault();
				last.focus();
			} else if (!e.shiftKey && document.activeElement === last) {
				e.preventDefault();
				first.focus();
			}
		};
		modal.addEventListener('keydown', handleKeydown);
		this.focusHandlers.set(modal, handleKeydown);
	}
	// function to get list of focusable elements in modal
	getFocusableElements(container) {
		// stringify list of focusable element to pass it to querySelectorAll
		const selector = this.focusableElements.join();
		return container.querySelectorAll(selector);
	}
}