<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">
<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="" aria-controls="" 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 u-z-index-foreground js-toggle-modal" aria-expanded="false" data-a11y-toggle="" aria-controls="" data-modal-close>
<span class="a-button__text">Stäng</span>
</button>
</div>
</div>
</div>
<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 u-z-index-foreground 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": "",
"is_hidden": false,
"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"
}
@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: var(--snow-color);
> 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 m(dynamic-width) {
width: 100%;
max-width: calc(100vw);
min-width: 0;
max-height: calc(100% - #{rhythm(2)});
padding: 0;
overflow: auto;
svg {
pointer-events: 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 e(graph-button) {
padding: 0;
position: relative;
&:hover,
&:focus {
@extend %input-focus;
}
&::after {
content: attr(data-zoom);
color: $color-snow;
padding: rhythm(1) rhythm(2) rhythm(1) rhythm(4);
display: block;
position: absolute;
top: 0;
left: 0;
border-top-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
width: auto;
line-height: 1;
height: $icon-size-large * 1.4;
background-color: rgba($color-cyberspace, 0.7);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' id='icon-search' viewbox='0 0 32 32' width='32' height='32' fill='%23ffffff'%3E%3Cpath d='M24,21.8l8,8L29.9,32l-8-8c-5.9,4.6-14.3,3.6-19-2.2S-0.7,7.6,5.1,2.9S19.3-0.7,24,5.1C27.9,10,27.9,16.9,24,21.8L24,21.8z M13.4,23.9c5.8,0,10.5-4.7,10.5-10.5S19.2,3,13.4,3S3,7.7,3,13.4S7.7,23.9,13.4,23.9z'/%3E%3C/svg%3E");
background-position: rhythm(1) center;
background-size: $icon-size-medium $icon-size-medium;
background-repeat: no-repeat;
}
}
@include e(button-fixed) {
position: fixed !important;
top: rhythm(2) !important;
right: rhythm(3) !important;
z-index: z_index(foreground) !important;
}
@include e(overlay-button) {
position: fixed !important;
color: $color-snow;
display: flex;
align-items: center;
text-align: center;
justify-content: center;
padding: 0;
z-index: z_index(foregroundMinus) !important;
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
background: rgba(0, 0, 0, 0.5);
text-shadow: 0 0 rhythm(0.5) $color-cyberspace;
font-family: $font-family-headings;
border: 0;
width: 100vw;
&.is-hidden {
display: none;
}
@include bp-up(xxl) {
display: none;
}
}
@include b(field-group) {
margin-bottom: rhythm(1);
}
}
/**
* Close overlay on graph modal
*/
const graphModals = document.querySelectorAll('.js-modal-graph');
const graphModalButtons = document.querySelectorAll('.js-modal-graph-button');
const addMultipleListeners = (el, types, listener, options, useCapture) => {
types.forEach((type) => el.addEventListener(type, listener, options, useCapture));
};
function closeOverlay(graphModalButton) {
// Close overlay button on 'click' on itself
graphModalButton.addEventListener('click', () => {
graphModalButton.classList.add('is-hidden');
graphModalButton.blur();
});
[].forEach.call(graphModals, (graphModal) => {
const closeButton = graphModal.querySelector('.js-toggle-modal');
closeButton.addEventListener('click', () => {
graphModalButton.classList.remove('is-hidden');
});
// Close overlay button on 'wheel', 'scroll', 'touchstart' on modal container
addMultipleListeners(
graphModal,
['wheel', 'scroll', 'touchstart'],
() => {
graphModalButton.classList.add('is-hidden');
console.log('fire!');
},
);
});
}
if (graphModalButtons && graphModals) {
[].forEach.call(graphModalButtons, closeOverlay);
}
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');
active.el.setAttribute('data-a11y-toggle-open', 'true');
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');
active.el.removeAttribute('data-a11y-toggle-open');
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);
document.querySelector('body').classList.add('prevent-scroll');
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);
document.querySelector('body').classList.remove('prevent-scroll');
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);
}
createModal();
attach();
export {
clearQueue,
open,
close,
};
No notes defined.