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 -twoOptions" id="twoOptions" aria-labelledby="twoOptionsTitle" aria-modal="true" role="dialog" tabindex="-1">
    <div class="sds-modal__dialog modal-dialog modal-lg modal-xl modal-dialog-centered">
        <div class="sds-modal__content modal-content">
            <div class="sds-modal__header modal-header">
                <div class="sds-modal__title modal-title" id="twoOptionsTitle">Deux choix s’offrent à vous</div>
                <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="row align-items-stretch justify-content-center align-items-stretch-immediate text-center">
                    <div class="col-5">
                        <div class="d-flex flex-column align-items-center justify-content-start sds-stackLg">
                            <img class="img-fluid" src="../../media/illustrative-icons/sdsillu-messagesent.svg" alt="">
                            <p class="flex-grow-1">Vous pouvez débloquer une nouvelle tranche de votre prêt Cedies directement en ligne. Pas besoin de prendre un rendez-vous.</p>
                            <button type="button" class="sds-btn -btnPrimary">
                                <span class="sds-btn__text">
                                    Passer par le Store
                                </span>
                            </button>
                        </div>
                    </div>
                    <div class="col-1 d-flex align-items-center justify-content-center">
                        <div class="sds-modal__verticalDivider"></div>
                    </div>
                    <div class="col-5">
                        <div class="d-flex flex-column align-items-center justify-content-start sds-stackLg">
                            <img class="img-fluid" src="../../media/illustrative-icons/sdsillu-plugcable.svg" alt="">
                            <p class="flex-grow-1">Si vous ne désirez pas liquider votre tranche de prêt CEDIES vous-même via le Store, vous pouvez prendre un rendez-vous avec votre conseiller.</p>
                            <button type="button" class="sds-btn -btnPrimary">
                                <span class="sds-btn__text">
                                    Prendre rendez-vous
                                </span>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
        
    
        {% extends "@snet-modal-base-frame" %}
{% block header %}
    <div class="{{ namespace }}modal__title modal-title" id="{{ modalTitleID }}">{{ title }}</div>
{% endblock %}
{% block body %}
    <div class="row align-items-stretch justify-content-center align-items-stretch-immediate text-center">
		<div class="col-5">
			<div class="d-flex flex-column align-items-center justify-content-start {{ namespace }}stackLg">
				<img class="img-fluid" src="{{ imgOptionOne | path }}" alt="">
				<p class="flex-grow-1">{{ textOption }}</p>
				{% render "@btn-primary",{text: textBtnOptionOne },true %}
			</div>
		</div>
		<div class="col-1 d-flex align-items-center justify-content-center">
			<div class="{{ namespace }}modal__verticalDivider"></div>
		</div>
		<div class="col-5">
			<div class="d-flex flex-column align-items-center justify-content-start {{ namespace }}stackLg">
				<img class="img-fluid" src="{{ imgOptionTwo | path }}" alt="">
				<p class="flex-grow-1">{{ textOption2 }}</p>
				{% render "@btn-primary",{text: textBtnOptionTwo },true %}
			</div>
		</div>
	</div>
{% endblock %}
{% block footer %}
{% 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);
	}
}