Tucano v0.37.2

Paginação

Feita para o Paginator do Django: você passa o número da página e o total, e ela monta o resto. Os itens são links com href de verdade, que preservam filtro, busca e ordem da URL — então abrir em outra aba, o voltar do navegador e o buscador continuam funcionando.

Exemplos

Nesta página os cliques não navegam: cada exemplo troca a página atual no lugar, para dar para experimentar.

Poucas páginas

Sem reticência, e a ponta inativa sai do caminho.

Vizinhança maior

data-around="2" mostra duas páginas de cada lado da atual.

Mais páginas nas pontas

data-edges="2" mantém as duas primeiras e as duas últimas.

Outros textos e parâmetro

data-prev-text, data-next-text e data-param="p" na URL.

Centralizada

class="tuc-pagination is-center" junto do data-tuc-pagination.

À direita

is-end, para ficar sob a coluna de valores de uma tabela.

Como usar

Com os números que o Paginator já tem. O elemento é trocado pela navegação no carregamento e a cada htmx:afterSwap; com uma página só, nada é renderizado — não há para onde ir.

No template do Django

{% for contract in page_obj %}
  …
{% endfor %}

<div data-tuc-pagination
     data-page="{{ page_obj.number }}"
     data-pages="{{ page_obj.paginator.num_pages }}"></div>
from django.core.paginator import Paginator

def contracts(request):
    qs = Contract.objects.filter(customer__name__icontains=request.GET.get("q", "")).order_by("-created_at")
    page_obj = Paginator(qs, 25).get_page(request.GET.get("page"))
    return render(request, "contracts/list.html", {"page_obj": page_obj})

Numa página em /contracts/?q=padaria&sort=amount, o link da página 3 sai como /contracts/?q=padaria&sort=amount&page=3: só o parâmetro da página muda. É isso que deixa a busca e a ordem de pé ao virar a página, sem o template ter de remontar a query string.

AtributoPadrãoPara quê
data-page1A página atual
data-pages1O total de páginas
data-parampageNome do parâmetro na query string
data-around1Páginas visíveis de cada lado da atual
data-edges1Páginas visíveis em cada ponta
data-prev-textAnteriorTexto da ponta de voltar
data-next-textPróximaTexto da ponta de avançar

Com HTMX

Como os itens já são links, o hx-boost num contêiner acima basta para a troca de página virar requisição parcial — e a paginação que volta no trecho novo é inicializada sozinha. Não é preciso onChange.

Em JavaScript

Para quem monta a lista no cliente. O atalho Tucano.pagination() devolve o elemento pronto para inserir; a classe devolve a instância, com o elemento em node.

const pag = new Tucano.Pagination({
  page: 1,
  pages: 12,
  label: 'Paginação dos contratos',   // aria-label da navegação
  onChange: (page, p) => {            // com onChange, o clique não navega
    load(page);
    p.setPage(page);                  // redesenha com a nova atual
  },
});
document.querySelector('#footer').append(pag.node);

pag.href(3);          // '/contracts/?q=padaria&page=3' — o link de uma página
pag.opts.pages = 15;
pag.render();         // redesenha depois de mudar as opções
pag.destroy();        // remove o elemento

// Só o elemento, sem guardar a instância
document.querySelector('#footer').append(Tucano.pagination({ page: 2, pages: 5 }));

O alinhamento é classe no .tuc-pagination: pelo JavaScript, pag.node.classList.add('is-center'), ou is-end. No template o elemento marcado recebe a navegação dentro dele, então escreva a classe junto do atributo:

<div class="tuc-pagination is-center" data-tuc-pagination
     data-page="{{ page_obj.number }}"
     data-pages="{{ page_obj.paginator.num_pages }}"></div>

Quais números aparecem

Com muitas páginas não cabe tudo. Ficam as pontas, a atual e a vizinhança dela; o resto vira reticência. O cálculo é exportado como Tucano.pageWindow(), e devolve null onde entra a reticência:

Tucano.pageWindow(5, 20);                 // [1, null, 4, 5, 6, null, 20]
Tucano.pageWindow(3, 7);                  // [1, 2, 3, 4, null, 7]
Tucano.pageWindow(10, 30, { around: 2 }); // [1, null, 8, 9, 10, 11, 12, null, 30]

Um buraco de uma página só nunca vira reticência: mostrar o número ocupa o mesmo espaço e dá um destino a mais para clicar. Por isso, com página 3 de 7, o 2 aparece em vez de ….

Numa lista paginada pelo servidor — o caso de todo CRUD Django — o número da página mora na URL. Trocar os links por onclick quebraria de uma vez o botão do meio, o voltar do navegador e a indexação.

Os itens são o botão do sistema

Cada item é um .tuc-btn is-ghost, e a página atual é is-outline. Altura, raio, foco e toque já estão resolvidos no botão, e um segundo desenho de botão na mesma tela é o que denuncia biblioteca remendada. A atual ganha contorno e não preenchimento: numa barra com dez alvos, dez botões sólidos brigariam entre si.

A ponta desativada é <span>, não <a> sem href

Um link que não leva a lugar nenhum continua no caminho do Tab e é anunciado como link. O <span> com aria-disabled simplesmente sai do caminho, e o .tuc-btn desenha o estado desativado sem uma regra a mais.

No celular (abaixo de 40rem) as palavras das pontas saem e ficam as setas: o espaço vale mais para os números. Antes de o script montar, o elemento vazio já reserva a altura de um controle, para o conteúdo abaixo não subir.

Teclado e acessibilidade

São links numa <nav>, e o teclado é o do navegador.

TeclaAção
Tab Shift+TabAnda entre as páginas; a ponta desativada e a reticência ficam fora do caminho
EnterVai para a página focada

A navegação é role="navigation" com aria-label — "Paginação" por padrão, trocável pela opção label quando houver duas na mesma tela. A página atual leva aria-current="page", e a reticência, aria-hidden.

API

Gerada do código a cada build — se algo não está aqui, não existe.

Marcação
[data-tuc-pagination]
Em JS
new Tucano.Pagination(opcoes) Tucano.pageWindow() Tucano.pagination()
Atributos
data-around data-edges data-next-text data-page data-pages data-param data-prev-text
Métodos
href render setPage destroy

Opções

OpçãoPadrãoPara quê
page1
pages1
param'page'
around1paginas visiveis de cada lado da atual
edges1paginas visiveis nas pontas
prevTextundefineddefault: setTexts ("Anterior")
nextTextundefineddefault: setTexts ("Próxima")
labelundefineddefault: setTexts ("Paginação")
onChangenull