1
0
mirror of https://github.com/locomotivemtl/locomotive-boilerplate.git synced 2026-01-15 00:55:08 +08:00

2 Commits

Author SHA1 Message Date
Chauncey McAskill
f400ca9622 Add Modal.js
Add accessible modal dialog component.

Features:
- Dialog supports being shown on page load via `data-modal-autoshow` attribute.
- Dialog supports being shown at most once via the `data-modal-show-once` attribute.
- Dialog persists dismissal via `localStorage`, by default. This can be switched to `sessionStoage` via `data-modal-show-once="session"` attribute value.
- When Load handles "loading" event, executes `Modal.hide` on all shown modals.

Required:
- NPM dependency a11y-dialog v7.3.0

Added:
- JS class `Modal` that uses and enhances a11y-dialog.
- CSS component `.c-dialog` via partial '_modal.scss' with basic styles.
- Basic HTML example of `data-module-modal`.

Note:
- Dialog styles adapted from https://codesandbox.io/s/a11y-dialog-pnwqu?file=/src/styles.css
2021-11-01 16:51:33 -04:00
Chauncey McAskill
1898a94373 Add overlay z-index scale and function
Added ordered z-index map function to improve incrementation of page layout z-indexes to mitigate complicated and exagerated z-index numbers across components.

Based on OZMap: https://rafistrauss.com/blog/ordered_z_index_maps

Usage:

```scss
.c-site-header_container {
  z-index: overlay-zindex(fixed);
}

.c-dialog_container {
  z-index: overlay-zindex(modal);
}
```

Added:
- Default scale: dropdown, sticky, fixed, offcanvas-backdrop, offcanvas, modal, popover, tooltip, transition.
- Function `overlay-zindex()` to fetch the z-index for the corresponding element.
2021-11-01 16:07:30 -04:00
17 changed files with 831 additions and 16 deletions

View File

@@ -1,2 +1,3 @@
export {default as Load} from './modules/Load'; export {default as Load} from './modules/Load';
export {default as Modal} from './modules/Modal';
export {default as Scroll} from './modules/Scroll'; export {default as Scroll} from './modules/Scroll';

View File

@@ -14,6 +14,10 @@ export default class extends module {
} }
}); });
this.load.on('loading', (transition, oldContainer) => {
this.call('hide', null, 'Modal');
});
load.on('loaded', (transition, oldContainer, newContainer) => { load.on('loaded', (transition, oldContainer, newContainer) => {
this.call('destroy', oldContainer, 'app'); this.call('destroy', oldContainer, 'app');
this.call('update', newContainer, 'app'); this.call('update', newContainer, 'app');

View File

@@ -0,0 +1,515 @@
/**
* Module to display a modal dialog.
*
* Uses {@link https://github.com/KittyGiraudel/a11y-dialog A11y Dialog}.
*
* ### Usage
*
* ```html
* <!-- 1. The dialog container -->
* <div
* id="your-dialog-id"
* data-module-modal="your-dialog-id"
* aria-labelledby="your-dialog-title-id"
* aria-hidden="true"
* >
* <!-- 2. The dialog overlay -->
* <div data-a11y-dialog-hide></div>
* <!-- 3. The actual dialog -->
* <div role="document">
* <!-- 4. The close button -->
* <button type="button" data-a11y-dialog-hide aria-label="Close dialog">
* &times;
* </button>
* <!-- 5. The dialog title -->
* <h1 id="your-dialog-title-id">Your dialog title</h1>
* <!-- 6. Dialog content -->
* </div>
* </div>
*
* <!-- 7. The a11y-dialog modal trigger -->
* <button type="button" data-a11y-dialog-show="your-dialog-id">
* Open the dialog
* </button>
* ```
*
* The dialog container must have a unique name for the `data-module-modal`
* attribute for cross-component interaction.
*
* The `id` attribute is recommended but optional if `data-module-modal`
* is present with a unique name.
*
* If the dialog container uses the `data-a11y-dialog` attribute for automatic
* instantiation through HTML, this component will not initialize.
*
* ### Features
*
* #### Automatically showing dialog
*
* The modal supports being shown on page load via the
* `data-modal-autoshow` attribute:
*
* ```html
* <div data-module-modal="your-dialog-id"
* data-modal-autoshow
* aria-labelledby="your-dialog-title-id"
* aria-hidden="true"
* >
* ```
*
* The modal will be shown during the module's `init()` process.
*
* #### Showing dialog once
*
* The modal supports being shown at most once via the
* `data-modal-show-once` attribute:
*
* ```html
* <div data-module-modal="your-dialog-id"
* data-modal-show-once
* aria-labelledby="your-dialog-title-id"
* aria-hidden="true"
* >
* ```
*
* By default, it uses {@see window.localStorage} to persist dismissal.
* With a "session" value (`data-modal-show-once="session"`), the component
* will use {@see window.sessionStorage}.
*/
import A11yDialog from 'a11y-dialog'
import { module } from 'modujs';
import { html, isDebug } from '../utils/environment';
/**
* Component to display a modal dialog.
*
* @property {?A11yDialog} dialog - The {@see A11yDialog} instance.
* @property {string} moduleName - The module class name.
* @property {string} moduleID - The module class instance ID.
*/
export default class extends module
{
/**
* Whether the dialog should be shown on page load.
*
* @var {boolean}
*/
autoShow = false;
/**
* The element to add `contextShowClass` to. Defaults to `<html>`.
*
* @var {Element}
*/
contextElement = html;
/**
* The CSS class name to apply to `contextElement` to mark the dialog as shown.
*
* @var {string}
*/
contextShowClass = 'has-modal-open';
/**
* Whether to log information about the modal.
*
* @var {boolean}
*/
debug = isDebug;
/**
* The storage key to remember the dialog was dismissed.
*
* @var {?string}
*/
dismissedStoreKey;
/**
* Whether the dialog can be shown at most once.
*
* @var {boolean}
*/
showOnce = false;
/**
* The storage object; either `localStorage` or `sessionStorage`.
*
* @var {?Storage}
*/
showOnceStore = window.localStorage;
/**
* Whether the dialog was shown.
*
* @var {boolean}
*/
wasShown = false;
/**
* Creates a new Modal component.
*
* @param {object} options - The module options.
* @param {string} options.name - The module class name.
* @param {string} options.dataName - The module data attribute name.
* @throws {TypeError} If the module ID or module class is missing.
*/
constructor(options) {
super(options);
this.events = {
click: {
dismiss: 'hide',
hide: 'hide',
show: 'show',
toggle: 'toggle',
},
submit: 'onSubmit',
submitend: 'hide',
};
const moduleAttr = `data-module-${options.dataName}`;
this.moduleName = options.name;
this.moduleID = this.el.getAttribute(moduleAttr);
if (!this.moduleID) {
throw new TypeError(
`${this.moduleName} must have an ID on attribute ${moduleAttr}`
);
}
if (!this.el.hasAttribute('id')) {
this.el.setAttribute('id', this.moduleID);
}
this._onHide = this.onHide.bind(this);
this._onShow = this.onShow.bind(this);
this._onSubmit = this.onSubmit.bind(this);
this.resolveShowOnce();
}
/**
* Creates the A11y Dialog instance and initializes the Modal component.
*
* If the `data-a11y-dialog` attribute is defined on the
* {@see this.el dialog element}, the Modal component will
* be ignored.
*
* If the Modal component was previously dismissed,
* the component will be ignored.
*
* @return {void}
*/
init() {
if (this.el.hasAttribute('data-a11y-dialog')) {
const dialogID = (this.el.getAttribute('data-a11y-dialog') || this.moduleID);
this.debug && console.warn(`${this.moduleName} [${dialogID}] not initialized because of automatic instantiation through HTML`);
return;
}
if (this.showOnce && this.wasShown) {
this.debug && console.log(`${this.moduleName} [${this.moduleID}] not initialized because the dialog was previously dismissed`);
return;
}
this.dialog = this.createDialog();
/**
* Assigning the this class and the dialog to the dialog container
* element for easy access from the web console.
*/
this.el.appModal = this;
this.el.a11yDialog = this.dialog;
this.addDialogEventListeners();
if (this.el.hasAttribute(`${this.mAttr}-autoshow`)) {
this.dialog.show();
}
}
/**
* @inheritdoc
*/
mUpdate(modules) {
super.mUpdate(modules);
this.refreshDialogOpeners();
}
/**
* Creates a new A11y Dialog instance.
*
* @protected
* @return {A11yDialog}
*/
createDialog() {
return new A11yDialog(this.el);
}
/**
* Destroys the A11y Dialog instance and Modal component.
*
* @return {void}
*/
destroy() {
this.destroyDialog();
delete this.el.appModal;
}
/**
* Destroys only the A11y Dialog instance.
*
* @return {void}
*/
destroyDialog() {
if (this.dialog) {
this.dialog.destroy();
}
delete this.el.a11yDialog;
}
/**
* Dismisses the modal.
*
* Marks the modal as dismissed which means it
* should not be shown for the foreseeable future.
*
* @protected
* @param {boolean} [destroy=false] - Whether to destroy the modal or not.
* @return {void}
*/
dismissDialog(destroy = false) {
this.wasShown = true;
if (this.showOnceStore && this.dismissedStoreKey) {
this.showOnceStore.setItem(this.dismissedStoreKey, this.getDismissedStoreValue());
} else {
this.debug && console.warn(`${this.moduleName} [${dialogID}] does not have a 'showOnceStore' or a 'dismissedStoreKey' for persisting dismissal`);
}
if (destroy) {
this.destroyDialog();
}
}
/**
* Registers event listeners on the A11y Dialog.
*
* @protected
* @return {void}
*/
addDialogEventListeners() {
if (this.dialog) {
this.dialog.on('hide', this._onHide);
this.dialog.on('show', this._onShow);
}
}
/**
* Unregisters event listeners on the A11y Dialog.
*
* @protected
* @return {void}
*/
removeDialogEventListeners() {
if (this.dialog) {
this.dialog.off('hide', this._onHide);
this.dialog.off('show', this._onShow);
}
}
/**
* @return {self}
*/
hide() {
if (this.dialog) {
this.dialog.hide();
}
return this;
}
/**
* @return {self}
*/
show() {
if (this.dialog) {
this.dialog.show();
}
return this;
}
/**
* @return {self}
*/
toggle() {
if (this.dialog) {
if (this.dialog.shown) {
this.dialog.hide();
} else {
this.dialog.show();
}
}
return this;
}
/**
* Returns the storage key to remember the dialog was dismissed.
*
* @return {string}
*/
getDismissedStoreKey() {
return `${this.moduleName}.${this.moduleID}.dismissed`;
}
/**
* Returns the storage value to remember the dialog was dismissed.
*
* @return {string}
*/
getDismissedStoreValue() {
return (new Date()).toISOString();
}
/**
* Returns a list of kebab-case form module names.
*
* @return {string[]}
*/
getFormModuleNames() {
return [
'form',
];
}
/**
* Fires when the dialog has finished being hidden from the user.
*
* This method will trigger dismissal of the dialog if to be
* {@see this.showOnce shown at most once}.
*
* This method will remove the {@see this.contextShowClass CSS context show class}.
*
* @listens A11yDialog#hide
*
* @protected
* @param {Element} dialogEl - The dialog container element.
* @param {Event} event - The dialog hide event.
* @return {void}
*/
onHide(dialogEl, event) {
if (this.showOnce && !this.wasShown) {
this.dismissDialog(true);
}
if (this.contextElement && this.contextShowClass) {
this.contextElement.classList.remove(this.contextShowClass);
}
}
/**
* Fires when the dialog has been made visible to the user.
*
* This method will add the {@see this.contextShowClass CSS context show class}.
*
* @listens A11yDialog#show
*
* @protected
* @param {Element} dialogEl - The dialog container element.
* @param {Event} event - The dialog show event.
* @return {void}
*/
onShow(dialogEl, event) {
if (this.contextElement && this.contextShowClass) {
this.contextElement.classList.add(this.contextShowClass);
}
}
/**
* Handles form submission inside the modal.
*
* This event listener is used to handle forms that do not use a custom
* form module where the modal will dispatch a custom "submitend" event.
*
* @listens form#submit
* @fires form#submitend
*
* @protected
* @param {Event} event - The submit event.
* @return {void}
*/
onSubmit(event) {
const selectors = this.getFormModuleNames()
.map((name) => `:not([data-module-${name}])`)
.join('');
if (event.target.matches(selectors)) {
this.debug && console.log('Modal.onSubmit')
const submitEndEvent = new CustomEvent('submitend', {
bubbles: true
});
event.target.dispatchEvent(submitEndEvent);
}
}
/**
* Refreshes all opener event listeners of the A11y Dialog instance.
*
* @protected
* @return {void}
*/
refreshDialogOpeners() {
if (this.dialog) {
// Remove the click event listener from all dialog openers
this.dialog._openers.forEach((opener) => {
opener.removeEventListener('click', this.dialog._show);
});
// Keep a collection of dialog openers, each of which will be bound a click
// event listener to open the dialog
this.dialog._openers = document.querySelectorAll('[data-a11y-dialog-show="' + this.dialog._id + '"]');
this.dialog._openers.forEach((opener) => {
opener.addEventListener('click', this.dialog._show);
});
}
}
/**
* Configures the "show at most once" feature.
*
* @protected
* @return {void}
*/
resolveShowOnce() {
const showOnceAttr = `${this.mAttr}-show-once`;
this.showOnce = this.el.hasAttribute(showOnceAttr);
if (!this.showOnce) {
return;
}
switch (this.el.getAttribute(showOnceAttr)) {
case 'session':
this.showOnceStore = window.sessionStorage;
break;
case 'local':
this.showOnceStore = window.localStorage;
break;
}
this.dismissedStoreKey = this.getDismissedStoreKey();
if (this.showOnceStore) {
this.wasShown = this.showOnceStore.getItem(this.dismissedStoreKey);
}
}
}

View File

@@ -0,0 +1,83 @@
// =============================================================================
// Modal Dialog
// =============================================================================
// Adapted from https://codesandbox.io/s/a11y-dialog-pnwqu?file=/src/styles.css
// =============================================================================
// Component settings
// =============================================================================
$dialog-container-zindex: overlay-zindex(modal) !default;
$dialog-backdrop-background-color: rgba(0, 0, 0, 0.9) !default;
$dialog-content-background-color: rgb(255, 255, 255) !default;
// Basic component styles
// =============================================================================
@keyframes fade-in {
from {
opacity: 0;
}
}
@keyframes slide-up {
from {
transform: translateY(10%);
}
}
.has-modal-open {
overflow-y: hidden;
}
.c-dialog_container {
display: flex;
overflow: scroll;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: $dialog-container-zindex;
&[aria-hidden='true'] {
display: none;
}
}
.c-dialog_overlay {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: $dialog-backdrop-background-color;
animation: fade-in 200ms both;
}
.c-dialog_content {
background-color: $dialog-content-background-color;
margin: auto;
z-index: 2;
position: relative;
animation: fade-in 400ms 200ms both, slide-up 400ms 200ms both;
padding: 1em;
max-width: 90%;
width: 600px;
border-radius: 2px;
@media screen and (min-width: $from-small) {
padding: 2em;
}
}
.c-dialog_close {
position: absolute;
top: 0.5em;
right: 0.5em;
@media screen and (min-width: $from-small) {
top: 1em;
right: 1em;
}
}

View File

@@ -50,6 +50,7 @@
@import "components/heading"; @import "components/heading";
@import "components/button"; @import "components/button";
@import "components/form"; @import "components/form";
@import "components/modal";
// Templates // Templates
// ========================================================================== // ==========================================================================

View File

@@ -1,6 +1,6 @@
// ========================================================================== // =============================================================================
// Settings / Config // Settings / Config
// ========================================================================== // =============================================================================
// Context // Context
// ============================================================================= // =============================================================================
@@ -46,10 +46,25 @@ $unit: 60px;
$unit-small: 30px; $unit-small: 30px;
// Container // Container
// ========================================================================== // =============================================================================
$container-width: 2000px; $container-width: 2000px;
$padding: $unit; $padding: $unit;
// Z-Indexes
// =============================================================================
$overlay-zindexes: (
dropdown: 100,
sticky: 120,
fixed: 130,
offcanvas-backdrop: 140,
offcanvas: 145,
modal-backdrop: 150,
modal: 155,
popover: 170,
tooltip: 180,
transition: 190,
) !default;
// Breakpoints // Breakpoints
// ============================================================================= // =============================================================================
$from-tiny: 500px !default; $from-tiny: 500px !default;

View File

@@ -89,6 +89,54 @@
@return true; @return true;
} }
//
// Retrieves the overlay component z-index.
//
// Adapted from OZMap.
//
// 1. The elements of the global z-index map may contain `null`
// (to indicate that the z-index value should be set automatically).
// 2. The elements of the global z-index map may contain
// a hardcoded number, to be used as the z-index for that element.
// - Note that the value must be greater than any preceding element,
// to maintain the order.
//
// @link https://rafistrauss.com/blog/ordered_z_index_maps/ OZMap
//
// @param {String} $list-key - The global z-index map key.
// @param {Number} $modifier - A positive or negative modifier to apply
// to the returned z-index value.
// @throw Error if the $list-key does not exist or value is unacceptable.
// @return {Number} The next available z-index value.
//
@function overlay-zindex($list-key, $modifier: 0) {
@if map-has-key($overlay-zindexes, $key: $list-key) {
$accumulator: 0;
@each $key, $val in $overlay-zindexes {
@if ($val == null) {
$accumulator: $accumulator + $overlay-zindex-increment;
$val: $accumulator;
} @else {
@if ($val <= $accumulator) {
//* If the z-index is not greater than the elements preceding it,
//* the whole element-order paradigm is invalidated
@error "z-index for #{$key} must be greater than the preceding value!";
}
$accumulator: $val;
}
@if ($key == $list-key) {
@return ($accumulator + $modifier);
}
}
} @else {
@error "#{$list-key} doesn't exist in the $overlay-zindexes map";
}
}
$overlay-zindex-increment: 1 !default;
// //
// Resolve whether a rule is important or not. // Resolve whether a rule is important or not.
// //
@@ -124,5 +172,3 @@
@function is-frontend() { @function is-frontend() {
@return ('frontend' == $context); @return ('frontend' == $context);
} }
$context: 'frontend' !default;

27
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "@locomotivemtl/boilerplate", "name": "@locomotivemtl/boilerplate",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"a11y-dialog": "^7.3.0",
"locomotive-scroll": "^4.1.3", "locomotive-scroll": "^4.1.3",
"modujs": "^1.4.2", "modujs": "^1.4.2",
"modularload": "^1.2.6", "modularload": "^1.2.6",
@@ -78,6 +79,14 @@
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
"dev": true "dev": true
}, },
"node_modules/a11y-dialog": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.3.0.tgz",
"integrity": "sha512-xrpSBbOOtGHT+gXcTk9uXfvfFx1xd3LebBhjpjd5xedzeM2FPqNeT6BvLjB+ej8oEH2fCpDHKn5JIlmiNyV5bA==",
"dependencies": {
"focusable-selectors": "^0.3.1"
}
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -2315,6 +2324,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/focusable-selectors": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/focusable-selectors/-/focusable-selectors-0.3.1.tgz",
"integrity": "sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw=="
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.4", "version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
@@ -6915,6 +6929,14 @@
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
"dev": true "dev": true
}, },
"a11y-dialog": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.3.0.tgz",
"integrity": "sha512-xrpSBbOOtGHT+gXcTk9uXfvfFx1xd3LebBhjpjd5xedzeM2FPqNeT6BvLjB+ej8oEH2fCpDHKn5JIlmiNyV5bA==",
"requires": {
"focusable-selectors": "^0.3.1"
}
},
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -8671,6 +8693,11 @@
"locate-path": "^3.0.0" "locate-path": "^3.0.0"
} }
}, },
"focusable-selectors": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/focusable-selectors/-/focusable-selectors-0.3.1.tgz",
"integrity": "sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw=="
},
"follow-redirects": { "follow-redirects": {
"version": "1.14.4", "version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",

View File

@@ -14,6 +14,7 @@
"build": "node --experimental-json-modules --no-warnings build/build.js" "build": "node --experimental-json-modules --no-warnings build/build.js"
}, },
"dependencies": { "dependencies": {
"a11y-dialog": "^7.3.0",
"locomotive-scroll": "^4.1.3", "locomotive-scroll": "^4.1.3",
"modujs": "^1.4.2", "modujs": "^1.4.2",
"modularload": "^1.2.6", "modularload": "^1.2.6",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,7 @@
<ul> <ul>
<li><a href="images.html">Images</a></li> <li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li> <li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="modal.html">Modal</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -25,6 +25,7 @@
<ul> <ul>
<li><a href="images.html">Images</a></li> <li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li> <li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="modal.html">Modal</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -44,6 +44,7 @@
<ul> <ul>
<li><a href="images.html">Images</a></li> <li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li> <li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="modal.html">Modal</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

119
www/modal.html Normal file
View File

@@ -0,0 +1,119 @@
<!doctype html>
<html class="is-loading" lang="en" data-page="page">
<head>
<meta charset="utf-8">
<title>Modal | Locomotive Boilerplate</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<meta name="msapplication-TileColor" content="#ffffff">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print" onload="this.media='all'; this.onload=null; this.isLoaded=true">
<style type="text/css">
.o-link-like {
background-color: transparent;
text-decoration: underline;
border: 0;
margin: 0;
padding: 0;
font: inherit;
cursor: pointer;
color: #1A0DAB;
}
.o-link-like:hover,
.o-link-like:active {
color: #13097C;
}
</style>
</head>
<body data-module-load>
<div data-load-container>
<div class="o-scroll" data-module-scroll="main">
<header data-scroll-section>
<a href="/"><h1>Locomotive Boilerplate</h1></a>
<nav>
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="modal.html">Modal</a></li>
</ul>
</nav>
</header>
<main data-scroll-section>
<div class="o-container">
<h1 class="c-heading -h1">Modal</h1>
<p>
This page demonstrates our Modal component that enhances Kitty Giraudel's
<a href="https://github.com/HugoGiraudel/a11y-dialog">a11ty-dialog</a>
script to create accessible dialog windows.
</p>
<p>
To see this dialog in action, you just need to
<button class="o-link-like" data-a11y-dialog-show="my-dialog">open the dialog window</button>.
Once its open, you should not be able to interact with other links
on the main page. The focus is said to be “trapped” inside the dialog
until the user explicitely decides to leave it.
</p>
</div>
</main>
<div class="c-dialog_container"
data-module-modal="my-dialog"
aria-hidden="true"
aria-labelledby="my-dialog-title"
aria-describedby="my-dialog-description">
<div class="c-dialog_overlay" data-a11y-dialog-hide></div>
<div class="c-dialog_content" role="document">
<button
data-a11y-dialog-hide
class="c-dialog_close c-button"
aria-label="Close this dialog window"
>
&times;
</button>
<h1 id="my-dialog-title">Subscribe to our newsletter</h1>
<p id="my-dialog-description">
Fill in the ridiculously small form below to receive our ridiculously
cool newsletter!
</p>
<form class="c-form">
<div class="c-form_item">
<label class="c-form_label" for="email">Email (required)</label>
<input class="c-form_input"
type="email"
name="EMAIL"
id="email"
placeholder="john.doe@gmail.com"
required
/>
</div>
<button class="c-button" type="submit" name="button">Sign up</button>
</form>
</div>
</div>
<footer data-scroll-section>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate" title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>
</div>
</div>
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js" crossorigin="anonymous"></script>
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController" crossorigin="anonymous"></script>
<script src="assets/scripts/vendors.js" defer></script>
<script src="assets/scripts/app.js" defer></script>
</body>
</html>