Files
Reem-Notas/app/javascript/controllers/carrossel_controller.js

115 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// app/javascript/controllers/carrossel_controller.js
// Stimulus — paginação em "carrossel" para listas longas: mostra N itens por vez
// com setas grandes e o indicador "110 de 12".
//
// PADRÃO EM TELA PEQUENA, LISTA INTEIRA EM TELA GRANDE. Em celular e monitor
// pequeno, uma lista de 30 cards vira rolagem infinita e o rodapé/ações da tela
// somem de vista; num monitor grande a mesma lista cabe e paginar só atrapalha
// (Ctrl+F do navegador não acha o que está escondido). O corte é por largura de
// janela — `maxLargura` — e é reavaliado no resize/rotação da tela.
//
// Os itens já vêm todos no HTML; aqui só escondemos os de fora da página. Por
// isso não serve para listas de centenas de itens (aí é paginação no servidor).
//
// Uso:
// <div data-controller="carrossel"
// data-carrossel-tamanho-value="10" <- itens por página
// data-carrossel-max-largura-value="1366"> <- acima disso, mostra tudo
// <div data-carrossel-target="item">…</div> <- cada item
// …
// <div data-carrossel-target="controles" class="hidden">
// <button data-action="carrossel#anterior" data-carrossel-target="anterior">
// <span data-carrossel-target="indicador"></span>
// <button data-action="carrossel#proxima" data-carrossel-target="proximo">
// </div>
// </div>
//
// Um item com data-carrossel-inicial="true" (ex.: o motorista com a composição
// aberta) define em qual página o carrossel abre — senão o clique dele "sumia".
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["item", "controles", "indicador", "anterior", "proximo"]
static values = {
tamanho: { type: Number, default: 10 },
maxLargura: { type: Number, default: 1366 }
}
connect() {
this.pagina = this.paginaInicial()
this._aoRedimensionar = () => this.render()
window.addEventListener("resize", this._aoRedimensionar)
this.render()
}
disconnect() {
window.removeEventListener("resize", this._aoRedimensionar)
}
proxima() { this.ir(this.pagina + 1) }
anterior() { this.ir(this.pagina - 1) }
ir(destino) {
this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
this.render()
// Volta ao topo da lista: sem isso, na 2ª página o leitor continua vendo o
// fim da anterior e parece que nada mudou.
this.element.scrollIntoView({ behavior: "smooth", block: "start" })
}
// Tela grande: sem paginação (a lista inteira aparece).
get paginando() {
return window.innerWidth <= this.maxLarguraValue &&
this.itemTargets.length > this.tamanhoValue
}
get totalPaginas() {
return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1)
}
paginaInicial() {
const i = this.itemTargets.findIndex((el) => el.dataset.carrosselInicial === "true")
return i < 0 ? 0 : Math.floor(i / this.tamanhoValue)
}
render() {
const total = this.itemTargets.length
if (!this.paginando) {
this.itemTargets.forEach((item) => item.classList.remove("hidden"))
if (this.hasControlesTarget) this.controlesTarget.classList.add("hidden")
return
}
// Uma tela menor pode ter reduzido o total de páginas desde a última vez.
this.pagina = Math.min(this.pagina, this.totalPaginas - 1)
const inicio = this.pagina * this.tamanhoValue
const fim = inicio + this.tamanhoValue
this.itemTargets.forEach((item, i) => {
item.classList.toggle("hidden", i < inicio || i >= fim)
})
if (!this.hasControlesTarget) return
this.controlesTarget.classList.remove("hidden")
if (this.hasIndicadorTarget) {
// "110 de 12" e não "página 1 de 2": quem lê quer saber quantos itens
// existem, não quantas páginas a tela tem.
this.indicadorTarget.textContent = `${inicio + 1}${Math.min(fim, total)} de ${total}`
}
if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0)
if (this.hasProximoTarget) this.alternar(this.proximoTarget, this.pagina >= this.totalPaginas - 1)
}
// Botão de ponta: desabilitado de verdade (não só apagado), para o toque não
// "funcionar sem fazer nada".
alternar(botao, desligado) {
botao.disabled = desligado
botao.classList.toggle("opacity-30", desligado)
botao.classList.toggle("pointer-events-none", desligado)
}
}