5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-05-03 08:57:05 +00:00

Merge branch 'issue-15097' into 'rails'

feat: dropdown #15097

Closes #15097

See merge request sutty/sutty!243
This commit is contained in:
fauno 2024-04-19 22:07:29 +00:00
commit 0ca6689b53
3 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,106 @@
import { Controller } from "stimulus";
// https://getbootstrap.com/docs/4.6/components/dropdowns/#single-button
export default class extends Controller {
static targets = ["dropdown", "button", "item"];
// Al iniciar el controlador
connect() {
// Llevar la cuenta del item con foco
this.data.set("item", -1);
// Gestionar las teclas
this.keydownEvent = this.keydown.bind(this);
this.element.addEventListener("keydown", this.keydownEvent);
// Gestionar el foco
this.focusinEvent = this.focusin.bind(this);
}
// Al eliminar el controlador (al pasar a otra página)
disconnect() {
// Eliminar la gestión de teclas
this.element.removeEventListener("keydown", this.keydownEvent);
// Eliminar la gestión del foco
document.removeEventListener("focusin", this.focusinEvent);
}
// Mostrar u ocultar
toggle(event) {
(this.buttonTarget.ariaExpanded === "false") ? this.show() : this.hide();
}
// Mostrar
show() {
this.buttonTarget.ariaExpanded = "true";
this.element.classList.add("show");
this.dropdownTarget.classList.add("show");
// Activar la gestión del foco
document.addEventListener("focusin", this.focusinEvent);
}
// Ocultar
hide() {
this.buttonTarget.ariaExpanded = "false";
this.element.classList.remove("show");
this.dropdownTarget.classList.remove("show");
// Volver al inicio el foco de items
this.data.set("item", -1);
// Desactivar la gestión del foco
document.removeEventListener("focusin", this.focusinEvent);
}
// Gestionar el foco
focusin(event) {
const item = this.itemTargets.find(x => x === event.target);
// Si el foco se coloca sobre elementos del controlador, no hacer
// nada
if (event.target === this.buttonTarget || item) {
// Si es un item, el comportamiento de las flechas verticales y el
// Tab tiene que ser igual
if (item) this.data.set("item", this.itemTargets.indexOf(item));
return;
}
// De lo contrario, ocultar
this.hide();
}
// Gestionar las teclas
keydown(event) {
const initial = parseInt(this.data.get("item"));
let item = initial;
switch (event.keyCode) {
case 27:
// Esc cierra el menú y devuelve el foco
this.hide();
this.buttonTarget.focus();
break;
case 38:
// Moverse hacia arriba con tope en el primer item
if (item > -1) item--;
break;
case 40:
// Moverse hacia abajo con tope en el último ítem, si el
// dropdown estaba cerrado, abrirlo.
if (item === -1) this.show();
if (item <= this.itemTargets.length) item++;
break;
}
// Si cambió la posición del ítem, darle foco y actualizar el
// contador.
if (initial !== item) {
this.itemTargets[item]?.focus();
this.data.set("item", item);
}
}
}

View file

@ -0,0 +1,34 @@
-#
@param :text [String] Contenido del botón
@param :button_classes [Array] Clases para el botón
@param :dropdown_classes [Array] Clases para el listado
@yield Un bloque que renderiza components/dropdown_item
- button_classes = local_assigns[:button_classes]&.join(' ')
- dropdown_classes = local_assigns[:dropdown_classes]&.join(' ')
.btn-group{
data: {
controller: 'dropdown'
}
}
%button.btn.dropdown-toggle{
type: 'button',
class: button_classes,
data: {
toggle: 'true',
display: 'static',
action: 'dropdown#toggle',
target: 'dropdown.button'
},
aria: {
expanded: 'false'
}
}
= text
.dropdown-menu{
class: dropdown_classes,
data: {
target: 'dropdown.dropdown'
}
}
= yield

View file

@ -0,0 +1,4 @@
-#
@param :text [String] Contenido del link
@param :path [String] Link
= link_to text, path, class: 'dropdown-item', data: { target: 'dropdown.item' }