Skip to content

Commit

Permalink
Add modal organism (Fixes #78) (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgibson authored and craigcook committed May 23, 2018
1 parent 3b38506 commit 890d80e
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 2 deletions.
2 changes: 1 addition & 1 deletion gulp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ module.exports = {
},
{
match: `${src}/js/**/*.js`,
tasks: ['js:lint', 'js:concat']
tasks: ['js:lint', 'js:concat', 'js:copy']
},
{
match: `${src}/sass/**/*.scss`,
Expand Down
158 changes: 158 additions & 0 deletions src/assets/js/protocol/protocol-modal.js
Original file line number Diff line number Diff line change
@@ -1 +1,159 @@
// Protocol Modal - Hello World
// create namespace
if (typeof Mozilla === 'undefined') {
var Mozilla = {};
}

Mozilla.Modal = (function(w) {
'use strict';

var open = false;
var body = w.document.body;
var html = w.document.documentElement;
var options = {};
var pageContentParent;
var pageContent;
var modal;

/*
origin: element that triggered the modal
content: content to display in the modal
options: object of optional params:
title: title to display at the top of the modal
onCreate: function to fire after modal has been created
onDestroy: function to fire after modal has been closed
allowScroll: boolean - allow/restrict page scrolling when modal is open
closeText: string to use for close button a11y.
*/
var _createModal = function(origin, content, opts) {
options = opts;

var isSmallViewport = window.innerWidth < 760;

// Make sure modal is closed (if one exists)
if (open) {
_closeModal();
}

// Create new modal
var title = (options && options.title) ? options.title : '';
var closeText = (options && options.closeText) ? options.closeText : '';

var modalFragment =
'<div class="mzp-c-modal" role="dialog" aria-labelledby="' + origin.getAttribute('id') + '" tabindex="-1">' +
' <div class="mzp-c-modal-window">' +
' <div class="mzp-c-modal-inner">' +
' <header><h2>' + title + '</h2></header>' +
' <div class="mzp-c-modal-close">' +
' <button type="button" class="mzp-c-modal-button-close">' + closeText + '</button>' +
' </div>' +
' </div>' +
' </div>' +
'</div>';

if ((options && !options.allowScroll) || isSmallViewport) {
html.classList.add('mzp-is-noscroll');
} else {
html.classList.remove('mzp-is-noscroll');
}

// Add modal to page
body.insertAdjacentHTML('beforeend', modalFragment);

modal = document.querySelector('.mzp-c-modal');

pageContent = content;
pageContentParent = content.parentNode;

var modalInner = document.querySelector('.mzp-c-modal-inner');
modalInner.appendChild(content);

content.classList.add('mzp-c-modal-overlay-contents');

// close modal on clicking close button or background.
var closeButton = document.querySelector('.mzp-c-modal-button-close');
closeButton.addEventListener('click', _closeModal, false);
closeButton.setAttribute('title', closeText);

// close modal on clicking the background (but not bubbled event).
document.querySelector('.mzp-c-modal .mzp-c-modal-window').addEventListener('click', function (e) {
if (e.target === this) {
_closeModal();
}
}, false);

modal.focus();

// close with escape key
document.addEventListener('keyup', _onDocumentKeyUp, false);

// prevent focusing out of modal while open
document.addEventListener('focus', _onDocumentFocus, false);

// remember which element opened the modal for later focus
origin.classList.add('mzp-c-modal-origin');

open = true;

// execute (optional) open callback
if (options && typeof(options.onCreate) === 'function') {
options.onCreate();
}
};

var _onDocumentKeyUp = function(e) {
if (e.keyCode === 27 && open) {
_closeModal();
}
};

var _onDocumentFocus = function(e) {
// .contains must be called on the underlying HTML element, not the jQuery object
if (open && !modal.contains(e.target)) {
e.stopPropagation();
modal.focus();
}
};

var _closeModal = function(e) {
if (e) {
e.preventDefault();
}

// return modal content to the page
pageContentParent.appendChild(pageContent);

// remove modal from the page
modal.parentNode.removeChild(modal);

// allow page to scroll again
html.classList.remove('mzp-is-noscroll');

// restore focus to element that opened the modal
var origin = document.querySelector('.mzp-c-modal-origin');
origin.focus();
origin.classList.remove('mzp-c-modal-origin');

open = false;

// unbind document listeners
document.removeEventListener('focus', _onDocumentFocus, false);

// execute (optional) callback
if (options && typeof(options.onDestroy) === 'function') {
options.onDestroy();
}

// free up options
options = {};
};

return {
createModal: function(origin, content, opts) {
_createModal(origin, content, opts);
},
closeModal: function() {
_closeModal();
}
};
})(window);
1 change: 1 addition & 0 deletions src/assets/sass/docs/protocol.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ $image-path: '/assets/protocol/protocol/img';
@import '../protocol/components/card';
@import '../protocol/components/button';
@import '../protocol/components/newsletter-form';
@import '../protocol/components/modal';
125 changes: 125 additions & 0 deletions src/assets/sass/protocol/components/_modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

@import '../includes/lib';
@import '../includes/animations';

html.mzp-is-noscroll {
overflow: hidden;
height: 100%;

body {
height: 100%;
overflow: hidden;
}

.mzp-c-modal {
position: absolute;
}

@media #{$media-md} {
height: auto;

body {
height: auto;
}

.mzp-c-modal {
position: fixed;
}
}
}

.mzp-c-modal {
@include animation(mzp-a-fade-in 300ms ease-in 0ms 1 normal both);
background: $color-primary;
background: rgba(0, 0, 0, .85);
bottom: 0;
height: 101%;
left: 0;
overflow: auto;
position: fixed;
right: 0;
top: 0;
width: 100%;
z-index: 9999999;
}

.mzp-c-modal-window {
padding: $padding-lg;
}

.mzp-c-modal-inner {
@include clearfix;
background: rgba(0, 0, 0, .9);
max-width: 1200px;
padding: $padding-xl;
position: relative;

&> header {
@include bidi(((padding-right, $padding-xl * 2, padding-left, 0),));

h2 {
@include text-display-sm;
@include open-sans;
color: #fff;
}
}

@media #{$media-lg} {
margin: $margin-lg auto ($margin-xl * 2);
}
}

.mzp-c-modal-close {
@include bidi(((right, $spacing-sm, left, auto),));
cursor: pointer;
position: absolute;
top: 9px;
z-index: 99;

.mzp-c-modal-button-close {
@include image-replaced;
background: transparent url('#{$image-path}/icons/ui/close.svg') center center no-repeat;
@include background-size(30px 30px);
border: none;
height: 42px;
min-width: 0;
padding: 0;
width: 42px;

&:hover,
&:focus {
@include transition(transform .1s ease-in-out);
@include transform(scale(1.1));
}

&:focus {
outline: 1px dotted $color-secondary;
}
}
}

.mzp-c-modal-overlay-contents {
@include text-body-lg;
@include light-links;
background: transparent;
margin: 0 auto;
padding-top: $padding-lg;
color: $color-secondary;

img, video {
max-width: 100%;
margin-bottom: $margin-xl;
}
}

// Utility class for hiding content that is to be used in the modal.
.js .mzp-u-modal-content {
display: none;
}

.js .mzp-c-modal-inner .mzp-u-modal-content {
display: block;
}
40 changes: 40 additions & 0 deletions src/assets/sass/protocol/includes/_animations.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


@-webkit-keyframes mzp-a-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

@keyframes mzp-a-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

@-webkit-keyframes mzp-a-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

@keyframes mzp-a-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
14 changes: 14 additions & 0 deletions src/assets/sass/protocol/includes/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,17 @@
top: 0;
}
}

// Light color links for dark backgrounds.
@mixin light-links {
a:link,
a:visited {
color: $color-secondary;
}

a:hover,
a:focus,
a:active {
color: darken($color-secondary, 10%);
}
}
1 change: 1 addition & 0 deletions src/pages/docs/naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ categories:
<li>`is-` to indicate a current state, e.g. `.mzp-is-active`, `.mzp-is-collapsed` (typically assigned by JS and not hard-coded).</li>
<li>`has-` to indicate that a component contains some other component, when the parent gets some styling to accommodate the child, e.g. `.mzp-has-submenu`, `.mzp-has-image`.</li>
<li>`js-` used as a behavior hook for JavaScript, e.g. `.mzp-js-sticky`, `.mzp-js-collapsible`, `.mzp-js-toggle`. Indicates potential for a change of state and usually shouldn’t have any styling.</li>
<li>`a-` for a CSS animation names, e.g. `mzp-a-fade-in`, `mzp-a-fade-out`.</li>
</ul>

Our names are all lowercase and hyphen-separated or “kebab-case,” e.g.
Expand Down
1 change: 1 addition & 0 deletions src/patterns/atoms/links/text.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ notes: |
tips: |
- Keep link text short, just a few words.
- The text in a link should be descriptive of the destination or offer some clear indication of where the link leads.
- You can use the `light-links` mixin for dark background where links require higher text contrast.
nonos: |
- Don’t use “click here” as link text.
- Don‘t use text links (the `a` element) to trigger scripted actions unless the link also has a real destination in case JavaScript isn’t available. The `href` attribute should have a real URL, not `#` or `javascript:void(0)`. For script-only actions use a `button` element. Buttons do things; links go places.
Expand Down
Loading

0 comments on commit 890d80e

Please sign in to comment.