diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css
index aa49e640..f8d3b531 100644
--- a/bookwyrm/static/css/bookwyrm.css
+++ b/bookwyrm/static/css/bookwyrm.css
@@ -8,6 +8,41 @@ body {
flex-direction: column;
}
+button {
+ border: none;
+ margin: 0;
+ padding: 0;
+ width: auto;
+ overflow: visible;
+ background: transparent;
+
+ /* inherit font, color & alignment from ancestor */
+ color: inherit;
+ font: inherit;
+ text-align: inherit;
+
+ /* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
+ line-height: normal;
+
+ /* Corrects font smoothing for webkit */
+ -webkit-font-smoothing: inherit;
+ -moz-osx-font-smoothing: inherit;
+
+ /* Corrects inability to style clickable `input` types in iOS */
+ -webkit-appearance: none;
+}
+
+button::-moz-focus-inner {
+ /* Remove excess padding and border in Firefox 4+ */
+ border: 0;
+ padding: 0;
+}
+
+/* Better accessibility for keyboard users */
+*:focus-visible {
+ outline-style: auto !important;
+}
+
.image {
overflow: hidden;
}
@@ -29,10 +64,30 @@ body {
overflow-x: auto;
}
+/* stylelint-disable no-descending-specificity */
+.modal-card:focus {
+ outline-style: auto;
+}
+
+.modal-card:focus:not(:focus-visible) {
+ outline-style: initial;
+}
+
+.modal-card:focus-visible {
+ outline-style: auto;
+}
+/* stylelint-enable no-descending-specificity */
+
.modal-card.is-fullwidth {
min-width: 75% !important;
}
+@media only screen and (min-width: 769px) {
+ .modal-card.is-thin {
+ width: 350px !important;
+ }
+}
+
.modal-card-body {
max-height: 70vh;
}
diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js
index c79471fe..9fbd4dec 100644
--- a/bookwyrm/static/js/bookwyrm.js
+++ b/bookwyrm/static/js/bookwyrm.js
@@ -45,6 +45,12 @@ let BookWyrm = new class {
'change',
this.disableIfTooLarge.bind(this)
));
+
+ document.querySelectorAll('button[data-modal-open]')
+ .forEach(node => node.addEventListener(
+ 'click',
+ this.handleModalButton.bind(this)
+ ));
document.querySelectorAll('[data-duplicate]')
.forEach(node => node.addEventListener(
@@ -420,6 +426,97 @@ let BookWyrm = new class {
}
}
+ /**
+ * Handle the modal component.
+ *
+ * @param {Event} event - Event fired by an element
+ * with the `data-modal-open` attribute
+ * pointing to a modal by its id.
+ * @return {undefined}
+ *
+ * See https://github.com/bookwyrm-social/bookwyrm/pull/1633
+ * for information about using the modal.
+ */
+ handleModalButton(event) {
+ const modalButton = event.currentTarget;
+ const targetModalId = modalButton.dataset.modalOpen;
+ const htmlElement = document.querySelector('html');
+ const modal = document.getElementById(targetModalId);
+
+ if (!modal) {
+ return;
+ }
+
+ // Helper functions
+ function handleModalOpen(modalElement) {
+ htmlElement.classList.add('is-clipped');
+ modalElement.classList.add('is-active');
+ modalElement.getElementsByClassName('modal-card')[0].focus();
+
+ const closeButtons = modalElement.querySelectorAll("[data-modal-close]");
+
+ closeButtons.forEach((button) => {
+ button.addEventListener(
+ 'click',
+ function() { handleModalClose(modalElement) }
+ );
+ });
+
+ document.addEventListener(
+ 'keydown',
+ function(event) {
+ if (event.key === 'Escape') {
+ handleModalClose(modalElement);
+ }
+ }
+ );
+
+ modalElement.addEventListener('keydown', handleFocusTrap)
+ }
+
+ function handleModalClose(modalElement) {
+ modalElement.removeEventListener('keydown', handleFocusTrap)
+ htmlElement.classList.remove('is-clipped');
+ modalElement.classList.remove('is-active');
+ modalButton.focus();
+ }
+
+ function handleFocusTrap(event) {
+ if (event.key !== 'Tab') {
+ return;
+ }
+
+ const focusableEls = event.currentTarget.querySelectorAll(
+ [
+ 'a[href]:not([disabled])',
+ 'button:not([disabled])',
+ 'textarea:not([disabled])',
+ 'input:not([type="hidden"]):not([disabled])',
+ 'select:not([disabled])',
+ 'details:not([disabled])',
+ '[tabindex]:not([tabindex="-1"]):not([disabled])'
+ ].join(',')
+ );
+ const firstFocusableEl = focusableEls[0];
+ const lastFocusableEl = focusableEls[focusableEls.length - 1];
+
+ if (event.shiftKey ) /* Shift + tab */ {
+ if (document.activeElement === firstFocusableEl) {
+ lastFocusableEl.focus();
+ event.preventDefault();
+ }
+ } else /* Tab */ {
+ if (document.activeElement === lastFocusableEl) {
+ firstFocusableEl.focus();
+ event.preventDefault();
+ }
+ }
+ }
+
+ // Open modal
+ handleModalOpen(modal);
+ }
+
/**
* Display pop up window.
*
diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html
index d7710a2a..43e8eb22 100644
--- a/bookwyrm/templates/layout.html
+++ b/bookwyrm/templates/layout.html
@@ -38,7 +38,7 @@
-