Ir para o conteúdo

Botões

Os botões são a afordância tapável mais básica do tempest-core. Há dois: Button (com texto) e IconButton (só ícone). Os dois compartilham a API de variantes de ergonomia Chakra (variant / size / color_scheme) ancorada em Material 3 — você descreve a intenção e um resolver puro produz o Style concreto a partir dos tokens do Theme. 🚀

O que você aprende aqui

  • As quatro variantes e para qual tratamento M3 cada uma abaixa.
  • Os quatro tamanhos e por que um botão pequeno nunca fura o alvo de toque.
  • Como o color_scheme escolhe a família de cor.
  • Como o botão resolve e assa seu Style, e como ler a tabela por estado.

Button

Um botão tapável com rótulo de texto. No caso mínimo, você só passa label — o resto tem padrão sensato (SOLID / MD / primary):

from tempest_core import Button

salvar = Button(label="Salvar")

Esse único Button(label="Salvar") já é um botão preenchido, primário, de densidade média — pronto para os renderizadores. Passe um on_click para reagir ao toque:

from tempest_core import Button

salvar = Button(
    label="Salvar",
    on_click=lambda e: app.set_state(salvando=True),  # (1)!
    variant="solid",
    size="md",
    color_scheme="primary",
)
  1. O handler pode ser síncrono ou async — o runtime agenda os awaitables no event loop. Ele recebe um TapEvent (veja Referência da API).

Props

Prop Tipo Padrão O que faz
label str (obrigatório) O texto mostrado no botão.
on_click EventHandler \| None None Handler chamado no toque; sync ou async. Recebe TapEvent.
variant Variant SOLID O tratamento visual (solid / outline / ghost / link).
size Size \| dict[str, Size] MD A densidade — um Size só ou um mapa por breakpoint.
color_scheme str "primary" A família de papéis M3 (primary / secondary / tertiary / error / neutral).
theme Theme Theme() O tema cujos tokens resolvem a variante. Não entra na IR.
media MediaQueryData \| None None Snapshot de viewport para resolver um size responsivo. Não entra na IR.

theme e media são entradas de build, não props da IR

Os dois são usados só na hora de resolver o Style e ficam de fora dos props do nó (prop_exclude_names). Um Theme inteiro por nó incharia a árvore e o payload serializado da ponte — o style resolvido já carrega o efeito deles.

Variantes

O variant escolhe a ênfase; o resolver H1 (resolve_variant) o mapeia para um tratamento Material 3. O color_scheme decide qual família de cor pinta esse tratamento.

Variant Tratamento M3 Fundo Conteúdo Borda
SOLID filled button cor do papel on_* legível
OUTLINE outlined button transparente cor do papel mesma cor do papel
GHOST text button (sem sublinhado) transparente cor do papel
LINK texto inline sublinhado transparente cor do papel
from tempest_core import Button, Row

barra = Row(
    children=[
        Button(label="Salvar", variant="solid"),  # ênfase máxima
        Button(label="Cancelar", variant="outline"),  # ênfase média
        Button(label="Pular", variant="ghost"),  # ênfase baixa
        Button(label="Saiba mais", variant="link"),  # inline
    ]
)

Escala de ênfase

SOLIDOUTLINEGHOSTLINK é uma escala de maior para menor ênfase. Reserve SOLID para a ação primária da tela; use GHOST para ações secundárias que não devem competir com ela.

Tamanhos e o alvo de toque

size aceita um Size único (XS / SM / MD / LG) ou um mapa por breakpoint para densidade responsiva:

from tempest_core import Button
from tempest_core import Size

# Compacto no mobile, folgado a partir do breakpoint "md".
responsivo = Button(label="Enviar", size={"base": Size.SM, "md": Size.LG})

Um botão pequeno reduz densidade, nunca o alvo de toque

Todos os tamanhos garantem min_height = 48.0 (MIN_TOUCH_TARGET, o alvo de 48dp do Material). Diminuir o size reduz o padding/tipografia visual, mas a área tapável nunca fica abaixo de 48dp — acessibilidade motora fica preservada por construção.

Como o Style é resolvido e assado

Na construção, o Button roda um model_validator(mode="after") que:

  1. captura seu style explícito como override;
  2. resolve o Style base de variant / size / color_scheme contra o theme (via resolve_variant);
  3. mescla o override por cima do base (os campos setados do override vencem);
  4. assa o resultado em .style, então os renderizadores consomem um Style plano, sem saber que houve resolução.
from tempest_core import Button
from tempest_core import Style

# O override ganha nos campos que ele seta; o resto vem da variante resolvida.
custom = Button(label="Perigo", color_scheme="error", style=Style(radius=999.0))

Override sempre por cima

Isso mantém a retrocompatibilidade: Button(label=...) sem style dá o botão da variante; Button(label=..., style=…) estiliza à mão por cima. Você nunca perde a variante ao setar um campo pontual.

Tabela por estado (hover / press / disabled / focus)

resolve_variant é puro e mora no engine — mas os botões reais têm estados de interação. O método state_styles() devolve o Style resolvido para cada ComponentState, já com o override do chamador mesclado por cima:

from tempest_core import Button
from tempest_core import ComponentState

botao = Button(label="Salvar", color_scheme="primary")
estados = botao.state_styles()

estados[ComponentState.DEFAULT]  # repouso
estados[ComponentState.HOVER]  # ponteiro em cima (state layer M3)
estados[ComponentState.PRESSED]  # sendo tocado
estados[ComponentState.DISABLED]  # inativo (opacidade reduzida)
estados[ComponentState.FOCUS]  # foco de teclado/leitor

A resolução é pura; só o mapeamento evento→estado mora no renderizador

O core produz a tabela de estados de forma determinística. Aplicar a state layer do Material 3 no evento certo de ponteiro/foco é a única parte que vive nos renderizadores (Qt / Compose) — mantém o core sem tocar em pixels.

IconButton

Um botão só de ícone, quadrado/circular. Ele é um botão, então reusa resolve_variant igual ao Button — e então fixa width/height no min_height resolvido (uma caixa quadrada de pelo menos 48dp) e um radius circular, usando só campos de Style existentes (nenhum campo novo). O padrão é a variante GHOST (a mais discreta, focada no ícone):

from tempest_core import IconButton

fechar = IconButton(icon="close", label="Fechar diálogo")  # (1)!
ajustes = IconButton(icon="settings", color_scheme="primary", label="Abrir ajustes")
  1. O icon é um valor curado de Icons (ou sua string), ou um nome de ícone de plataforma qualquer.

Props

Prop Tipo Padrão O que faz
icon Icons \| str (obrigatório) O ícone — valor curado de Icons ou nome de plataforma.
on_click EventHandler \| None None Handler no toque; sync ou async.
variant Variant GHOST O tratamento visual — padrão GHOST.
size Size \| dict[str, Size] MD A densidade.
color_scheme str "primary" A família de papéis M3.
label str "" O nome acessível (a11y / Semantics) do botão sem texto.
theme Theme Theme() O tema. Não entra na IR.
media MediaQueryData \| None None Snapshot de viewport. Não entra na IR.

Sempre passe label num IconButton

Um botão só de ícone não tem texto visível, então o label carrega o nome acessível (contentDescription / accessible label) que os renderizadores roteiam para a superfície de acessibilidade do nó. Sem ele, o botão fica mudo para leitores de tela. Não é opcional na prática — é acessibilidade.

Geometria quadrada/circular

O IconButton fixa a geometria via _squareify: width e height recebem o min_height resolvido (≥ 48dp), e radius recebe metade disso (um círculo). Tudo com campos de Style já existentes — a área de toque nunca cai abaixo de 48dp, como no Button. A tabela por estado (state_styles()) devolve cada estado já fixado nessa geometria.

Recapitulando

  • Dois botões, uma API: Button (texto) e IconButton (ícone) resolvem via resolve_variant a partir de variant / size / color_scheme.
  • Variantes: SOLID (filled) → OUTLINE (outlined) → GHOST (text) → LINK (inline), de maior para menor ênfase.
  • Tamanhos: XS / SM / LG / MD ou mapa por breakpoint; min_height garante o alvo de toque de 48dp em qualquer tamanho.
  • Style resolvido e assado: o override do chamador sempre é mesclado por cima da variante resolvida.
  • state_styles() dá a tabela por ComponentState; só o mapeamento evento→estado mora no renderizador.
  • IconButton é quadrado/circular, GHOST por padrão, e exige label para acessibilidade.