<style media="screen">
    :root {
        --m-modal-button-primary-color: #e0bff5;
        --m-modal-button-primary-hover-color: #c27fec;
        --m-modal-button-primary-border-color: #c27fec;
        --m-modal-button-primary-text-color: #1f2a36;
        --m-modal-button-secondary-color: #ff9fb4;
        --m-modal-button-secondary-hover-color: #ff4069;
        --m-modal-button-secondary-border-color: #ff4069;
        --m-modal-button-secondary-text-color: #1f2a36;
    }
</style>
<div id="modal-container" class="m-modal m-modal--has-overlay" aria-hidden="true">
    <div class="m-modal__container ">
        <button type="button" class="a-button a-button--standalone-icon a-button--transparent js-toggle-modal" aria-expanded="false" data-a11y-toggle="modal-container" aria-controls="modal-container" data-modal-close>
            <span class="a-button__text u-visuallyhidden">Stäng</span>
            <svg class="icon a-button__icon">
                <use xlink:href="#icon-close"></use>
            </svg>
        </button>
        <div class="m-modal__content">
            <h4>Du har sparat FooCoding Graduation!</h4>
            <p>En påminnelse kommer skickas till din adress <a href="https://internetstiftelsen.se/om-webbplatsen/integritetspolicy-for-besokare-pa-internetstiftelsens-webbplatser/" class="u-link">15 minuter</a> innan streamen startar.</p>
        </div>
        <div class="m-modal__buttons u-m-t-2">
            <button type="button" class="a-button m-modal__button-primary js-toggle-modal" aria-expanded="false" data-a11y-toggle="modal-container" aria-controls="modal-container" data-modal-close>
                <span class="a-button__text">Stäng</span>
            </button>
        </div>
    </div>
</div>
<button type="button" class="a-button a-button--lemon" data-open-demo-modal>
    <span class="a-button__text">Öppna modal</span>
</button>

<button type="button" class="a-button a-button--lemon" data-modal-open="modal-container">
    <span class="a-button__text">Open predefined modal</span>
</button>
<style media="screen">
:root {
	--m-modal-button-primary-color: {{button_primary_color}};
	--m-modal-button-primary-hover-color: {{button_primary_hover_color}};
	--m-modal-button-primary-border-color: {{button_primary_border_color}};
	--m-modal-button-primary-text-color: {{button_primary_text_color}};
	--m-modal-button-secondary-color: {{button_secondary_color}};
	--m-modal-button-secondary-hover-color: {{button_secondary_hover_color}};
	--m-modal-button-secondary-border-color: {{button_secondary_border_color}};
	--m-modal-button-secondary-text-color: {{button_secondary_text_color}};
}
</style>
<div id="modal-container" class="m-modal m-modal--has-overlay" {{#if is_hidden}}aria-hidden="true"{{/if}}>
	<div class="m-modal__container {{#if is_form}}m-modal__container--form{{/if}}">
		<button type="button" class="a-button a-button--standalone-icon a-button--transparent js-toggle-modal" aria-expanded="false" data-a11y-toggle="{{aria_controls}}" aria-controls="{{aria_controls}}" data-modal-close>
			<span class="a-button__text u-visuallyhidden">{{button_text}}</span>
			<svg class="icon a-button__icon">
				<use xlink:href="#icon-close"></use>
			</svg>
		</button>
		<div class="m-modal__content">
			{{#unless is_form}}
				<h4>{{title}}</h4>
				<p>{{{text}}}</p>
			{{/unless}}
			{{#if is_form}}
			 	<h1>{{title}}</h1>
			{{/if}}
		</div>
		{{#if is_form}}
		<form class="m-modal__form">
			<div class="field-group">
			<div class="m-modal__content--form-email">
				<label for="label">E-post</label>
					<input type="text" id="name" placeholder="hej@mail.com" autocomplete="off" class="a-input ">
			</div>
			<div class="m-modal__input-wrapper">
				<div class="m-modal__content--form-name">
					<label for="label">Förnamn</label>
					<input type="text" id="name" placeholder="Anna" autocomplete="off" class="a-input ">
				</div>
				<div class="m-modal__content--form-lname">
					<label for="label">Efternamn</label>
					<input type="text" id="name" placeholder="Johansson" autocomplete="off" class="a-input ">
				</div>
			</div>
			<div class="m-modal__content--form-username">
				<label for="label">Användarnamn</label>
				<input type="text" id="name" placeholder="tex kattsmurfen89" autocomplete="off" class="a-input ">
			</div>
			<div class="modal__content--form-password u-m-b-default">
			 <label for="password">Lösenord</label>
				<input type="password" id="password" placeholder="" autocomplete="off" class="a-input has-icon ">
				<button class="input-icon js-toggle-input-type">
					<svg class="icon">
						<use xlink:href="#icon-read"></use>
					</svg>
					<span class="u-visuallyhidden">Visa lösenordet</span>
				</button>
				</div>

				<fieldset>
					<legend class="label-legend">{{check_text}}</legend>
					<div class="checkbox checkbox--inline row no-gutters u-m-b-2">
						<div class="grid-auto">
							<input type="checkbox" id="radiobutton-0" name="Checkbox default" value="0" class="a-checkbox u-visuallyhidden">
							<label for="radiobutton-0"><span>Stockholm</span></label>
						</div>
						<div class="grid-auto">
							<input type="checkbox" id="radiobutton-1" name="Checkbox default" value="0" class="a-checkbox u-visuallyhidden">
							<label for="radiobutton-1"><span>Malmö</span></label>
						</div>
					</div>
				</fieldset>

				<div class="checkbox">
					<input type="checkbox" id="radiobutton-2" name="Checkbox default" value="0" class="a-checkbox u-visuallyhidden">
					<label for="radiobutton-2"><span>{{{terms_text}}}</span></label>
				</div>
			</div>
			</form>
		{{/if}}
		<div class="m-modal__buttons u-m-t-2">
			<button type="button" class="a-button m-modal__button-primary js-toggle-modal" aria-expanded="false" data-a11y-toggle="{{aria_controls}}" aria-controls="{{aria_controls}}" data-modal-close>
				<span class="a-button__text">{{button_text}}</span>
			</button>
		</div>
		{{#if is_form}}
		<div class="m-modal__content--form-text u-m-t-2">{{{bottom_text}}}</div>
		{{/if}}
	</div>
</div>
{{#if is_hidden}}
	<button type="button" class="a-button a-button--lemon" data-open-demo-modal>
		<span class="a-button__text">{{btn_text}}</span>
	</button>

	<button type="button" class="a-button a-button--lemon" data-modal-open="modal-container">
		<span class="a-button__text">Open predefined modal</span>
	</button>
{{/if}}
{
  "title": "Du har sparat FooCoding Graduation!",
  "text": "En påminnelse kommer skickas till din adress <a href=\"https://internetstiftelsen.se/om-webbplatsen/integritetspolicy-for-besokare-pa-internetstiftelsens-webbplatser/\" class=\"u-link\">15 minuter</a> innan streamen startar.",
  "button_text": "Stäng",
  "aria_controls": "modal-container",
  "is_hidden": true,
  "button_primary_color": "#e0bff5",
  "button_primary_hover_color": "#c27fec",
  "button_primary_border_color": "#c27fec",
  "button_primary_text_color": "#1f2a36",
  "button_secondary_color": "#ff9fb4",
  "button_secondary_hover_color": "#ff4069",
  "button_secondary_border_color": "#ff4069",
  "button_secondary_text_color": "#1f2a36",
  "btn_text": "Öppna modal",
  "aria_expanded": false,
  "data_a11y_toggle": ""
}
  • Content:
    @include molecule(modal) {
    	display: flex;
    	position: fixed;
    	z-index: z_index(foreground);
    	top: 0;
    	right: 0;
    	bottom: 0;
    	left: 0;
    	align-items: center;
    	justify-content: center;
    
    	&[aria-hidden='true'] {
    		display: none;
    	}
    
    	// Adjust close button position
    	@include m(has-overlay) {
    		&::after {
    			content: '';
    			position: absolute;
    			z-index: z_index(foregroundMinus);
    			top: 0;
    			right: 0;
    			bottom: 0;
    			left: 0;
    			background: rgba(0, 0, 0, 0.5);
    		}
    	}
    
    	@include e(container) {
    		display: flex;
    		position: relative;
    		z-index: z_index(foreground);
    		flex-direction: column;
    		width: 100%;
    		max-height: calc(100vh - #{rhythm(2)});
    		margin: rhythm(1);
    		padding: rhythm(4) rhythm(2) rhythm(4) rhythm(2);
    		overflow-y: auto;
    		border-radius: $border-radius;
    		background: $color-snow;
    
    		> button {
    			position: absolute;
    			z-index: z_index(background);
    			top: rhythm(1);
    			right: rhythm(1);
    		}
    
    		@include bp-up(sm) {
    			padding: rhythm(4) rhythm(4) rhythm(6) rhythm(4);
    			width: auto;
    			min-width: rem(576px);
    		}
    
    		@include bp-up(md) {
    			padding: rhythm(8) rhythm(8) rhythm(6) rhythm(8);
    		}
    
    		@include bp-up(lg) {
    			max-width: rem(961px);
    		}
    
    		@include m(no-max-width) {
    			max-width: none;
    		}
    	}
    
    	@include bp-up(md) {
    		@include e(input-wrapper) {
    			display: flex;
    			justify-content: space-between;
    			margin-bottom: rhythm(2);
    		}
    	}
    
    	@include e(content) {
    		position: relative;
    		margin-right: rhythm(2);
    
    		@include bp-up(sm) {
    			margin-right: 0;
    		}
    
    		@include m(form-name) {
    			flex: 0 0 45%;
    		}
    
    		@include m(form-lname) {
    			flex: 0 0 45%;
    		}
    
    		@include e(input-wrapper) {
    			display: flex;
    			justify-content: space-between;
    			margin-bottom: rhythm(2);
    		}
    
    		@include bp-up(md) {
    
    			@include m(form-email) {
    				margin-bottom: rhythm(2);
    			}
    
    			@include m(form-username) {
    				margin-bottom: rhythm(2);
    			}
    		}
    		@include m(form-check) {
    			display: flex;
    		}
    
    		@include m(form-text) {
    			display: flex;
    			justify-content: center;
    			margin-top: rhythm(2);
    		}
    	}
    
    	@include e(buttons) {
    		display: flex;
    		flex-shrink: 0;
    		justify-content: flex-end;
    	}
    
    	@include e(button-primary) {
    		color: var(--m-modal-button-primary-color) !important;
    		box-shadow: 0 0 0 1px inset var(--m-modal-button-primary-border-color) !important;
    
    		> [class*='text'] {
    			color: var(--m-modal-button-primary-text-color) !important;
    		}
    
    		&:hover,
    		&:focus {
    			color: var(--m-modal-button-primary-hover-color) !important;
    		}
    	}
    
    	@include e(button-secondary) {
    		color: var(--m-modal-button-secondary-color) !important;
    		box-shadow: 0 0 0 1px inset var(--m-modal-button-secondary-border-color) !important;
    
    		> [class*='text'] {
    			color: var(--m-modal-button-secondary-text-color) !important;
    		}
    
    		&:hover,
    		&:focus {
    			color: var(--m-modal-button-secondary-hover-color) !important;
    		}
    	}
    
    	@include b(field-group) {
    		margin-bottom: rhythm(1);
    	}
    }
    
  • URL: /components/raw/modal/_modal.scss
  • Filesystem Path: src/molecules/modal/_modal.scss
  • Size: 2.9 KB
  • Content:
    import focusTrap from '../../focusTrap';
    import className from '../../assets/js/className';
    
    /**
     * Modal action
     * @typedef {Object} ModalAction
     * @property {string} icon - icon name
     * @property {string} color - styleguide color
     * @property {string} modifier - styleguide modifier
     * @property {string} text - action content
     * @property {string} url - action link url
     * @property {string} target - action link target
     * @property {string} key - action click handler key shortcut
     * @property {function} onClick - action click handler
     * @property {object} attrs – action element attributes
     */
    
    /**
     * Modal content
     * @typedef {Object} ModalContent
     * @property {string} title - Modal title
     * @property {string} content - Modal content
     * @property {ModalAction[]} actions - Modal actions
     */
    
    /**
     * Modal settings
     * @typedef {Object} ModalSettings
     * @property {boolean} replaceCurrent - Replace currently displayed modal
     * @property {boolean} skipIfCurrent - Skip if currently displaying modal
     * @property {function} onClose - onClose callback
     * @property {function} onOpen - onOpen callback
     */
    
    const queue = [];
    const globalOnCloseCallbacks = [];
    const globalOnOpenCallbacks = [];
    let active = null;
    let incrementId = 0;
    let modal = null;
    let modalContent = null;
    let modalActions = null;
    let modalClose = null;
    let keyHandlers = {};
    
    /**
     * Increase increment ID and return the latest
     *
     * @returns {number}
     */
    function getId() {
    	incrementId += 1;
    
    	return incrementId;
    }
    
    function objectToAttributes(obj) {
    	return Object.entries(obj)
    		.filter(([, value]) => value !== undefined)
    		.map(([key, value]) => ((value !== null) ? `${key}=${value}` : key))
    		.join(' ');
    }
    
    /**
     * Create an action DOM node and append to modal actions container.
     *
     * @param {ModalAction} action
     */
    function addAction(action) {
    	const icon = (action.icon) ? `
    		<svg class="icon ${className('a-button__icon')}">
    			<use xlink:href="#icon-${action.icon}"></use>
    		</svg>
    	` : '';
    
    	let cls = `${className('a-button')} u-m-l-2`;
    
    	if (action.color) {
    		cls += ` ${className(`a-button--${action.color}`)}`;
    	}
    
    	if (action.modifier) {
    		cls += ` ${className(`m-modal__button-${action.modifier}`)}`;
    	}
    
    	if (action.icon) {
    		cls += ` ${className('a-button--icon')}`;
    	}
    
    	const tag = (action.url) ? 'a' : 'button';
    	const button = `
    		<${tag} ${objectToAttributes({ ...action.attrs, href: action.url, target: action.target })} class="${cls}">
    			<span class="${className('a-button__text')}">${action.text}</span>
    			${icon}
    		</${tag}>
    	`;
    
    	const dummy = document.createElement('div');
    
    	dummy.innerHTML = button;
    
    	const el = dummy.firstElementChild;
    	modalActions.appendChild(el);
    
    	if (action.onClick) {
    		el.addEventListener('click', (e) => {
    			// eslint-disable-next-line no-use-before-define
    			action.onClick(e, modal, close);
    		});
    	}
    }
    
    function handleKeyUp(e) {
    	Object.entries(keyHandlers).forEach(([key, handler]) => {
    		if (e.key.toLowerCase() === key) {
    			// eslint-disable-next-line no-use-before-define
    			handler(e, modal, close);
    		}
    	});
    }
    
    /**
     * Global onOpen handler
     *
     * @param {Function} cb
     * @returns {number}
     */
    export function onOpen(cb) {
    	const index = globalOnOpenCallbacks.push(cb) - 1;
    
    	return () => {
    		globalOnOpenCallbacks.splice(index, 1);
    	};
    }
    
    function dispatchOnOpenHandlers(el, id) {
    	globalOnOpenCallbacks.forEach((cb) => cb(el, id));
    }
    
    /**
     * Display the active modal.
     */
    function display() {
    	if (active.content.nodeName) {
    		// Content is a custom modal
    		active.el = active.content;
    	} else {
    		active.el = modal;
    		modalContent.innerHTML = `
    			<h1>${active.content.title}</h1>
    			${active.content.content}
    		`;
    
    		if (active.content.actions) {
    			modalActions.innerHTML = '';
    			active.content.actions.forEach(addAction);
    
    			modalActions.classList.remove('u-hide');
    		} else {
    			modalActions.classList.add('u-hide');
    		}
    	}
    
    	focusTrap(active.el);
    
    	active.el.setAttribute('aria-hidden', 'false');
    
    	if (active.settings.onOpen) {
    		active.settings.onOpen(active.id, active.el);
    	}
    
    	dispatchOnOpenHandlers(active.el, active.id);
    
    	setTimeout(() => {
    		if (active.el.focusTrap) {
    			active.el.focusTrap.activate();
    		}
    	}, 1);
    
    	// Just to make sure
    	keyHandlers = {};
    
    	if (active.content.actions) {
    		active.content.actions.forEach((action) => {
    			if (action.key && action.onClick) {
    				keyHandlers[action.key] = action.onClick;
    			}
    		});
    	}
    
    	document.addEventListener('keyup', handleKeyUp);
    }
    
    /**
     * Dispatch the next modal in queue.
     */
    function dispatch() {
    	if (!modal || active || !queue.length) {
    		return;
    	}
    
    	active = queue.shift();
    
    	display();
    }
    
    /**
     * Global onClose handler
     *
     * @param {Function} cb
     * @returns {number}
     */
    export function onClose(cb) {
    	const index = globalOnCloseCallbacks.push(cb) - 1;
    
    	return () => {
    		globalOnCloseCallbacks.splice(index, 1);
    	};
    }
    
    function dispatchOnCloseHandlers(el, id) {
    	globalOnCloseCallbacks.forEach((cb) => cb(el, id));
    }
    
    /**
     * Close currently active modal
     * and dispatch next in queue.
     */
    function close() {
    	if (active) {
    		active.el.setAttribute('aria-hidden', 'true');
    
    		if (active.settings.onClose) {
    			active.settings.onClose(active.id);
    		}
    
    		dispatchOnCloseHandlers(active.el, active.id);
    
    		document.removeEventListener('keyup', handleKeyUp);
    
    		if (active.el.focusTrap) {
    			active.el.focusTrap.deactivate();
    		}
    
    		keyHandlers = {};
    		active = null;
    	}
    
    	setTimeout(() => {
    		dispatch();
    	}, 1);
    }
    
    /**
     * Create the modal skeleton and add it to the DOM.
     * Done once and cached.
     */
    function createModal() {
    	if (modal) {
    		return;
    	}
    
    	const id = 'iisModal';
    	const dummy = document.createElement('div');
    
    	dummy.innerHTML = `
    		<div id="${id}" class="${className('m-modal m-modal--has-overlay')}" data-container="true" aria-hidden="true" aria-labelledby="${id}-close">
    			<div class="${className('m-modal__container')}">
    				<button type="button" class="${className('a-button a-button--standalone-icon a-button--transparent')}" data-modal-close aria-expanded="false" data-a11y-toggle="${id}" aria-controls="${id}" id="${id}-close">
    					<span class="${className('a-button__text')} u-visuallyhidden">Stäng</span>
    					<svg class="icon ${className('a-button__icon')}">
    						<use xlink:href="#icon-close"></use>
    					</svg>
    				</button>
    				<div class="${className('m-modal__content')}" data-modal-content></div>
    				<div class="${className('m-modal__buttons')} u-m-t-2 u-hide" data-modal-actions></div>
    			</div>
    		</div>
    	`;
    
    	modal = dummy.firstElementChild;
    	modalContent = modal.querySelector('[data-modal-content]');
    	modalActions = modal.querySelector('[data-modal-actions]');
    	modalClose = modal.querySelector('[data-modal-close]');
    
    	document.body.appendChild(modal);
    
    	modalClose.addEventListener('click', close);
    
    	setTimeout(() => {
    		dispatch();
    	}, 1);
    }
    
    /**
     * Clear the current modal queue
     */
    function clearQueue() {
    	queue.length = 0;
    }
    
    /**
     * Open a modal.
     *
     * @param {ModalContent|HTMLElement} content
     * @param {ModalSettings} settings
     */
    function open(content, settings = {}) {
    	if (active && settings.skipIfCurrent) {
    		return;
    	}
    
    	queue.push({
    		id: getId(),
    		content,
    		settings,
    	});
    
    	if (settings.replaceCurrent) {
    		close();
    	} else {
    		dispatch();
    	}
    }
    
    function delegate(e) {
    	const openModal = e.target.closest('[data-modal-open]');
    
    	if (openModal) {
    		e.preventDefault();
    		e.stopPropagation();
    
    		const id = openModal.getAttribute('data-modal-open');
    		const modalEl = document.getElementById(id);
    
    		if (modalEl) {
    			open(modalEl, {
    				replaceCurrent: openModal.hasAttribute('data-modal-replace'),
    				skipIfCurrent: openModal.hasAttribute('data-modal-skip'),
    			});
    
    			[].forEach.call(document.querySelectorAll(`[aria-controls="${id}"]`), (el) => {
    				el.setAttribute('aria-expanded', 'true');
    			});
    		}
    
    		return false;
    	}
    
    	const closeModal = e.target.closest('[data-modal-close]');
    
    	if (closeModal) {
    		e.preventDefault();
    		e.stopPropagation();
    
    		const id = closeModal.getAttribute('data-modal-close') || (active && active.el.id);
    
    		if (active && active.el.id === id) {
    			close();
    
    			[].forEach.call(document.querySelectorAll(`[aria-controls="${id}"]`), (el) => {
    				el.setAttribute('aria-expanded', 'false');
    			});
    		}
    
    		return false;
    	}
    
    	return true;
    }
    
    /**
     * Attach global listeners
     */
    function attach() {
    	document.body.addEventListener('click', delegate);
    }
    
    window.addEventListener('load', () => {
    	createModal();
    	attach();
    });
    
    export {
    	clearQueue,
    	open,
    	close,
    };
    
  • URL: /components/raw/modal/modal.js
  • Filesystem Path: src/molecules/modal/modal.js
  • Size: 8.5 KB

No notes defined.