From 1b90b887e01403ac914ae2e9b3996348777c5ac7 Mon Sep 17 00:00:00 2001 From: Ned Zimmerman Date: Mon, 10 Feb 2020 16:27:19 -0700 Subject: [PATCH] feat: add dialog (resolves #106) (#213) * Restore WIP code * Restore dialog partial * feat: add dialog (unstyled) * fix: add inert polyfill in a Chromium-compatible way * feat: add title/body support to dialog * feat: responsive sizes --- rollup.config.js | 2 - src/assets/scripts/Pinecone/Dialog/index.js | 119 ++++++++++++++++++ src/assets/scripts/Pinecone/index.js | 3 +- src/assets/scripts/pinecone.js | 12 ++ src/assets/styles/components/_dialog.scss | 50 ++++++++ src/assets/styles/pinecone.scss | 1 + .../02-molecules/06-dialog/dialog.config.js | 10 ++ .../02-molecules/06-dialog/dialog.njk | 2 + src/components/_preview.njk | 1 - 9 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 src/assets/scripts/Pinecone/Dialog/index.js create mode 100644 src/assets/styles/components/_dialog.scss create mode 100644 src/components/02-molecules/06-dialog/dialog.config.js create mode 100644 src/components/02-molecules/06-dialog/dialog.njk diff --git a/rollup.config.js b/rollup.config.js index 7b043cad..2d3cfbf3 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -11,7 +11,6 @@ export default [ format: 'umd', }, plugins: [babel()], - external: [ 'wicg-inert' ] }, // CommonJS (for Node) and ES module (for bundlers) build. @@ -21,6 +20,5 @@ export default [ { file: pkg.main, format: 'cjs' }, { file: pkg.module, format: 'es' }, ], - external: [ 'wicg-inert' ] }, ]; diff --git a/src/assets/scripts/Pinecone/Dialog/index.js b/src/assets/scripts/Pinecone/Dialog/index.js new file mode 100644 index 00000000..fd0379a7 --- /dev/null +++ b/src/assets/scripts/Pinecone/Dialog/index.js @@ -0,0 +1,119 @@ +/** + * Dialog Handler. + */ + +/** + * Dialog class. + */ +class Dialog { + /** + * Constructor. + * + * @param {DomNode} btn + * @param {Object} options + */ + constructor( btn, options ) { + this.btn = btn; + this.config = { + ...{ + callback: + /** + * Callback for when one was not provided. + */ + () => { + console.error( 'No callback provided.' ); // eslint-disable-line + } + }, + ...options + }; + + this.invokeDialog = this.invokeDialog.bind( this ); + this.handleClick = this.handleClick.bind( this ); + this.addEventListeners(); + } + + /** + * + * + */ + handleClick() { + this.invokeDialog( this.config.callback ); + } + + /** + * Invoke dialog. + * + * @param {Function} callback + */ + invokeDialog( callback ) { + const elems = document.querySelectorAll( 'body > *' ); + Array.prototype.forEach.call( elems, ( elem ) => { + elem.setAttribute( 'inert', 'inert' ); + } ); + + const unique = +new Date(); + + const dialog = document.createElement( 'div' ); + dialog.setAttribute( 'role', 'dialog' ); + dialog.setAttribute( 'aria-labelledby', `q-${unique}` ); + dialog.setAttribute( 'aria-describedby', `q-${unique + 1}` ); + let innerHtml = `

${this.config.title}

`; + if ( this.config.question ) { + innerHtml += `

${this.config.question}

`; + } + innerHtml += ` +
+ + +
+ `; + dialog.innerHTML = innerHtml; + const overlay = document.createElement( 'div' ); + overlay.setAttribute( 'inert', 'inert' ); + overlay.classList.add( 'overlay' ); + + /** + * Handle close event. + */ + const close = () => { + Array.prototype.forEach.call( elems, elem => { + if ( elem !== dialog ) { + elem.removeAttribute( 'inert' ); + } + } ); + dialog.parentNode.removeChild( dialog ); + overlay.parentNode.removeChild( overlay ); + trigger.focus(); + }; + + const confirm = dialog.querySelector( '.confirm' ); + const dismiss = dialog.querySelector( '.dismiss' ); + const trigger = this.btn; + + document.body.appendChild( overlay ); + document.body.appendChild( dialog ); + + dismiss.focus(); + + confirm.onclick = () => { + close(); + callback(); + }; + dismiss.onclick = () => close(); + dialog.addEventListener( 'keydown', e => { + if ( 27 == e.keyCode ) { + e.preventDefault(); + close(); + } + } ); + } + + /** + * Add event listeners. + */ + addEventListeners() { + this.btn.addEventListener( 'click', this.handleClick, false ); + } +} + +export default Dialog; diff --git a/src/assets/scripts/Pinecone/index.js b/src/assets/scripts/Pinecone/index.js index 005a0ab0..0e83264d 100644 --- a/src/assets/scripts/Pinecone/index.js +++ b/src/assets/scripts/Pinecone/index.js @@ -1,6 +1,7 @@ import Accordion from './Accordion/index.js'; import Card from './Card/index.js'; import DeselectAll from './DeselectAll/index.js'; +import Dialog from './Dialog/index.js'; import DisclosureButton from './DisclosureButton/index.js'; import FilterList from './FilterList/index.js'; import Icon from './Icon/index.js'; @@ -10,4 +11,4 @@ import NestedCheckbox from './NestedCheckbox/index.js'; import Notification from './Notification/index.js'; import SearchToggle from './SearchToggle/index.js'; -export default { Accordion, Card, DeselectAll, DisclosureButton, FilterList, Icon, Menu, MenuButton, NestedCheckbox, Notification, SearchToggle }; +export default { Accordion, Card, DeselectAll, Dialog, DisclosureButton, FilterList, Icon, Menu, MenuButton, NestedCheckbox, Notification, SearchToggle }; diff --git a/src/assets/scripts/pinecone.js b/src/assets/scripts/pinecone.js index 592f0788..33787a0d 100644 --- a/src/assets/scripts/pinecone.js +++ b/src/assets/scripts/pinecone.js @@ -1,5 +1,7 @@ /* global Pinecone */ +import 'wicg-inert'; + const menu = document.querySelector( '.menu' ); const menuToggle = document.querySelector( '.menu-toggle' ); @@ -99,3 +101,13 @@ const searchToggle = document.querySelector( '.search-toggle' ); if ( searchToggle ) { new Pinecone.SearchToggle( searchToggle, searchToggle.nextElementSibling ); } + +const dialogBtn = document.getElementById( 'invoke-dialog' ); +if ( dialogBtn ) { + new Pinecone.Dialog( dialogBtn, { + title: 'Remove resource?', + question: 'Are you sure you want to remove this resource from your favorites?', + confirm: 'Yes, remove', + dismiss: 'No, don’t remove' + } ); +} diff --git a/src/assets/styles/components/_dialog.scss b/src/assets/styles/components/_dialog.scss new file mode 100644 index 00000000..4a1516b3 --- /dev/null +++ b/src/assets/styles/components/_dialog.scss @@ -0,0 +1,50 @@ +.button--invoke-dialog + * { + display: none; +} + +[role="dialog"] { + background-color: $white; + border-radius: rem(3); + box-shadow: 0 rem(3) rem(6) rgba(0, 0, 0, 0.16); + height: auto; + left: $gutter; + padding: $gutter; + position: fixed; + top: 20vh; + width: calc(100% - 2 * #{$gutter}); + + .buttons { + margin-top: rem(16); + } + + .buttons button { + max-width: 100%; + width: 100%; + } + + .buttons button + button { + margin-top: rem(16); + } +} + +.overlay { + background: $black; + height: 100%; + left: 0; + opacity: 0.7; + position: fixed; + top: 0; + width: 100%; +} + +@include breakpoint-up(md) { + [role="dialog"] { + left: calc((100% - 32rem) / 2); + padding: rem(68) rem(92); + width: 32rem; + + .buttons { + margin-top: $gutter; + } + } +} diff --git a/src/assets/styles/pinecone.scss b/src/assets/styles/pinecone.scss index 609e74f2..aee9f6d8 100644 --- a/src/assets/styles/pinecone.scss +++ b/src/assets/styles/pinecone.scss @@ -27,6 +27,7 @@ @import "components/disclosure-button"; @import "components/filter-sort"; @import "components/card"; +@import "components/dialog"; @import "components/info-card"; @import "components/menu"; @import "components/menu--home"; diff --git a/src/components/02-molecules/06-dialog/dialog.config.js b/src/components/02-molecules/06-dialog/dialog.config.js new file mode 100644 index 00000000..cce6f5ef --- /dev/null +++ b/src/components/02-molecules/06-dialog/dialog.config.js @@ -0,0 +1,10 @@ +module.exports = { + title: 'Dialog', + status: 'wip', + context: { + dialogTitle: 'Remove favorite', + dialogContent: 'Are your sure you want to remove this resource from your favorites?', + dialogConfirm: 'Yes, remove', + dialogCancel: 'No, don’t remove' + } +}; diff --git a/src/components/02-molecules/06-dialog/dialog.njk b/src/components/02-molecules/06-dialog/dialog.njk new file mode 100644 index 00000000..e8dd1741 --- /dev/null +++ b/src/components/02-molecules/06-dialog/dialog.njk @@ -0,0 +1,2 @@ +{% if not standAlone %}
{% endif %} +{% render '@button--borderless', {label: 'Remove favorite', id: 'invoke-dialog', icon: 'delete', iconPosition: 'start', standAlone: true}, true %} diff --git a/src/components/_preview.njk b/src/components/_preview.njk index b2365000..08a6c97a 100644 --- a/src/components/_preview.njk +++ b/src/components/_preview.njk @@ -40,7 +40,6 @@
{% if _target.context.inFooter %}{{ yield | safe }}{% else %}{% render '@footer' %}{% endif %}
{% endif %} -