diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 43a59a62..3c8386dc 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -93,6 +93,9 @@ body { display: inline !important; } +/** File input styles + ******************************************************************************/ + input[type=file]::file-selector-button { -moz-appearance: none; -webkit-appearance: none; @@ -119,17 +122,11 @@ input[type=file]::file-selector-button:hover { color: #363636; } -details .dropdown-menu { - display: block !important; -} +/** General `details` element styles + ******************************************************************************/ -details.dropdown[open] summary.dropdown-trigger::before { - content: ""; - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; +summary::-webkit-details-marker { + display: none; } summary::marker { @@ -151,6 +148,57 @@ summary { margin-top: 1em; } +/** Details dropdown + ******************************************************************************/ + +details.dropdown[open] summary.dropdown-trigger::before { + content: ""; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +details .dropdown-menu { + display: block !important; +} + +details .dropdown-menu button { + /* Fix weird Safari defaults */ + box-sizing: border-box; +} + +details.dropdown .dropdown-menu button:focus-visible, +details.dropdown .dropdown-menu a:focus-visible { + outline-style: auto; + outline-offset: -2px; +} + +@media only screen and (max-width: 768px) { + details.dropdown[open] summary.dropdown-trigger::before { + background-color: rgba(0, 0, 0, 0.5); + z-index: 30; + } + + details .dropdown-menu { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex !important; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 100; + } + + details .dropdown-menu > * { + pointer-events: all; + } +} + /** Shelving ******************************************************************************/ diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 2e446a85..c79471fe 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -51,6 +51,12 @@ let BookWyrm = new class { 'click', this.duplicateInput.bind(this) + )) + document.querySelectorAll('details.dropdown') + .forEach(node => node.addEventListener( + 'toggle', + this.handleDetailsDropdown.bind(this) + )) } @@ -480,4 +486,101 @@ let BookWyrm = new class { textareaEl.parentNode.appendChild(copyButtonEl) } + + /** + * Handle the details dropdown component. + * + * @param {Event} event - Event fired by a `details` element + * with the `dropdown` class name, on toggle. + * @return {undefined} + */ + handleDetailsDropdown(event) { + const detailsElement = event.target; + const summaryElement = detailsElement.querySelector('summary'); + const menuElement = detailsElement.querySelector('.dropdown-menu'); + const htmlElement = document.querySelector('html'); + + if (detailsElement.open) { + // Focus first menu element + menuElement.querySelectorAll( + 'a[href]:not([disabled]), button:not([disabled])' + )[0].focus(); + + // Enable focus trap + menuElement.addEventListener('keydown', this.handleFocusTrap); + + // Close on Esc + detailsElement.addEventListener('keydown', handleEscKey); + + // Clip page if Mobile + if (this.isMobile()) { + htmlElement.classList.add('is-clipped'); + } + } else { + summaryElement.focus(); + + // Disable focus trap + menuElement.removeEventListener('keydown', this.handleFocusTrap); + + // Unclip page + if (this.isMobile()) { + htmlElement.classList.remove('is-clipped'); + } + } + + function handleEscKey(event) { + if (event.key !== 'Escape') { + return; + } + + summaryElement.click(); + } + } + + /** + * Check if windows matches mobile media query. + * + * @return {Boolean} + */ + isMobile() { + return window.matchMedia("(max-width: 768px)").matches; + } + + /** + * Focus trap handler + * + * @param {Event} event - Keydown event. + * @return {undefined} + */ + 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(); + } + } + } }(); diff --git a/bookwyrm/static/js/check_all.js b/bookwyrm/static/js/check_all.js deleted file mode 100644 index fd29f2cd..00000000 --- a/bookwyrm/static/js/check_all.js +++ /dev/null @@ -1,34 +0,0 @@ - -(function() { - 'use strict'; - - /** - * Toggle all descendant checkboxes of a target. - * - * Use `data-target="ID_OF_TARGET"` on the node on which the event is listened - * to (checkbox, button, link…), where_ID_OF_TARGET_ should be the ID of an - * ancestor for the checkboxes. - * - * @example - * - * @param {Event} event - * @return {undefined} - */ - function toggleAllCheckboxes(event) { - const mainCheckbox = event.target; - - document - .querySelectorAll(`#${mainCheckbox.dataset.target} [type="checkbox"]`) - .forEach(checkbox => checkbox.checked = mainCheckbox.checked); - } - - document - .querySelectorAll('[data-action="toggle-all"]') - .forEach(input => { - input.addEventListener('change', toggleAllCheckboxes); - }); -})(); diff --git a/bookwyrm/templates/components/dropdown.html b/bookwyrm/templates/components/dropdown.html index b3710271..afa6d050 100644 --- a/bookwyrm/templates/components/dropdown.html +++ b/bookwyrm/templates/components/dropdown.html @@ -7,6 +7,7 @@ class=" dropdown control {% if right %}is-right{% endif %} + has-text-left " > -