Tabs
Organism
Tabs alternam visões relacionadas sem trocar o objeto principal da página.
Use Tabs quando o operador precisa alternar entre recortes irmãos: visão geral, evidências, histórico, auditoria, fontes ou configurações do mesmo caso. Tabs não devem esconder estado crítico nem substituir filtros operacionais.
Exemplos copiáveis
React
React
import * as React from "react"
type PulsoTab = { id: string; label: string; badge?: string | number; disabled?: boolean; content: React.ReactNode }
type PulsoTabsProps = {
label: string
tabs: PulsoTab[]
defaultValue?: string
value?: string
onValueChange?: (value: string) => void
}
export function PulsoTabs({ label, tabs, defaultValue, value, onValueChange }: PulsoTabsProps) {
const firstEnabledTab = tabs.find((tab) => !tab.disabled)?.id
const [internalValue, setInternalValue] = React.useState(defaultValue ?? firstEnabledTab ?? tabs[0]?.id)
const selectedValue = value ?? internalValue
const selectedTab = tabs.find((tab) => tab.id === selectedValue) ?? tabs[0]
function selectTab(tab: PulsoTab) {
if (tab.disabled) return
setInternalValue(tab.id)
onValueChange?.(tab.id)
}
return (
<div className="grid gap-4">
<div role="tablist" aria-label={label} className="inline-flex w-fit rounded-[var(--radius-lg)] border border-[var(--border)] bg-[var(--muted)] p-1">
{tabs.map((tab) => {
const selected = tab.id === selectedValue
return (
<button key={tab.id} type="button" role="tab" id={`${tab.id}-tab`} aria-selected={selected} aria-controls={`${tab.id}-panel`} disabled={tab.disabled} tabIndex={selected ? 0 : -1} onClick={() => selectTab(tab)} className={["inline-flex min-h-9 items-center gap-2 rounded-[var(--radius-md)] px-3 text-sm font-medium", selected ? "bg-[var(--card)] text-[var(--fg)] shadow-sm" : "text-[var(--fg-muted)] hover:text-[var(--fg)]"].join(" ")}>
{tab.label}{tab.badge ? <span className="rounded-full border border-[var(--border)] px-1.5 py-0.5 font-mono text-[11px]">{tab.badge}</span> : null}
</button>
)
})}
</div>
<section role="tabpanel" id={`${selectedTab.id}-panel`} aria-labelledby={`${selectedTab.id}-tab`} className="rounded-[var(--radius-lg)] border border-[var(--border)] bg-[var(--card)] p-4">
{selectedTab.content}
</section>
</div>
)
}<PulsoTabs
label="Visões do caso"
defaultValue="overview"
tabs={[
{ id: "overview", label: "Resumo", content: <p>Estado operacional e próxima decisão.</p> },
{ id: "evidence", label: "Evidências", badge: 8, content: <p>Fontes auditáveis.</p> },
]}
/>Regras
- Cada aba representa uma visão irmã, não uma ação.
- Estado crítico não pode ficar escondido apenas em aba inativa.
- Use teclado: setas, Home e End.
- Não use Tabs para risk levels; risco é dado operacional.
- Badges mostram quantidade; severidade precisa de texto/estado explícito.
Last updated on