mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
Consolidate dropdown controllers (#600)
* Basic listbox and popover controllers with temporary example * Separate select and menu controllers
This commit is contained in:
parent
0a0289846e
commit
4f0b2de4ef
14 changed files with 298 additions and 150 deletions
75
app/javascript/controllers/menu_controller.js
Normal file
75
app/javascript/controllers/menu_controller.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
/**
|
||||
* A "menu" can contain arbitrary content including non-clickable items, links, buttons, and forms.
|
||||
*
|
||||
* - If you need a form-enabled "select" element, use the "listbox" controller instead.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
static targets = ["button", "content"];
|
||||
|
||||
connect() {
|
||||
this.show = false;
|
||||
this.contentTarget.classList.add("hidden"); // Initially hide the popover
|
||||
this.buttonTarget.addEventListener("click", this.toggle);
|
||||
this.element.addEventListener("keydown", this.handleKeydown);
|
||||
document.addEventListener("click", this.handleOutsideClick);
|
||||
document.addEventListener("turbo:load", this.handleTurboLoad);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.element.removeEventListener("keydown", this.handleKeydown);
|
||||
this.buttonTarget.removeEventListener("click", this.toggle);
|
||||
document.removeEventListener("click", this.handleOutsideClick);
|
||||
document.removeEventListener("turbo:load", this.handleTurboLoad);
|
||||
this.close();
|
||||
}
|
||||
|
||||
// If turbo reloads, we maintain the state of the menu
|
||||
handleTurboLoad = () => {
|
||||
if (!this.show) this.close();
|
||||
};
|
||||
|
||||
handleOutsideClick = (event) => {
|
||||
if (this.show && !this.element.contains(event.target)) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
handleKeydown = (event) => {
|
||||
switch (event.key) {
|
||||
case " ":
|
||||
event.preventDefault(); // Prevent the default action to avoid scrolling
|
||||
if (document.activeElement === this.buttonTarget) {
|
||||
this.toggle();
|
||||
}
|
||||
case "Escape":
|
||||
this.close();
|
||||
this.buttonTarget.focus(); // Bring focus back to the button
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
this.show = !this.show;
|
||||
this.contentTarget.classList.toggle("hidden", !this.show);
|
||||
if (this.show) {
|
||||
this.focusFirstElement();
|
||||
}
|
||||
};
|
||||
|
||||
close() {
|
||||
this.show = false;
|
||||
this.contentTarget.classList.add("hidden");
|
||||
}
|
||||
|
||||
focusFirstElement() {
|
||||
const focusableElements =
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
const firstFocusableElement =
|
||||
this.contentTarget.querySelectorAll(focusableElements)[0];
|
||||
if (firstFocusableElement) {
|
||||
firstFocusableElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue