extraído de component-vault
Trilha de Navegação
Caminho até a página atual (“Botões → Botão”), com cada parte clicável, separador à escolha e a página atual em destaque. Aceita o Link do roteador. Quebra de linha em telas estreitas.
Variações
Playground
Props
Geradas dos tipos TypeScript do componente; a descrição vem dos comentários /** */.
<TrilhaDeNavegacao>
Trilha com o caminho até a página atual ("Botões → Botão"), com cada parte clicável. O último item é a página atual: fica em destaque e é anunciado como tal.
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
| itensobrigatória | ItemDaTrilha[] | — | |
| separador | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | "→" | Símbolo entre os itens (não é lido por leitores de tela). |
| rotulo | string | "Caminho" | Nome da região para leitores de tela. |
| componenteDeLink | ComponentType<PropsDoLink> | "a" | "a" | Componente usado nos links, como o `Link` do roteador (padrão: `<a>`). |
Código
stories.tsx tem os exemplos usados no preview e no playground.
trilha-de-navegacao/TrilhaDeNavegacao.tsx
import type { ComponentType, ReactNode } from "react";
export type ItemDaTrilha = {
rotulo: ReactNode;
/** Para onde o item leva. Sem `href`, o item aparece como texto. */
href?: string;
};
type PropsDoLink = { href: string; className?: string; "aria-current"?: "page"; children: ReactNode };
/**
* Trilha com o caminho até a página atual ("Botões → Botão"), com cada parte clicável.
* O último item é a página atual: fica em destaque e é anunciado como tal.
*/
export function TrilhaDeNavegacao({
itens,
separador = "→",
rotulo = "Caminho",
componenteDeLink: Link = "a",
}: {
itens: ItemDaTrilha[];
/** Símbolo entre os itens (não é lido por leitores de tela). */
separador?: ReactNode;
/** Nome da região para leitores de tela. */
rotulo?: string;
/** Componente usado nos links, como o `Link` do roteador (padrão: `<a>`). */
componenteDeLink?: ComponentType<PropsDoLink> | "a";
}) {
return (
<nav aria-label={rotulo}>
<ol className="flex flex-wrap items-center gap-2 text-sm">
{itens.map((item, indice) => {
const atual = indice === itens.length - 1;
return (
<li key={indice} className="flex items-center gap-2">
{indice > 0 && (
<span aria-hidden="true" className="text-muted/70">
{separador}
</span>
)}
{item.href ? (
<Link
href={item.href}
aria-current={atual ? "page" : undefined}
className={`rounded-sm underline-offset-4 outline-accent hover:underline focus-visible:outline-2 ${
atual ? "font-medium text-ink" : "text-muted hover:text-ink"
}`}
>
{item.rotulo}
</Link>
) : (
<span aria-current={atual ? "page" : undefined} className={atual ? "font-medium text-ink" : "text-muted"}>
{item.rotulo}
</span>
)}
</li>
);
})}
</ol>
</nav>
);
}
trilha-de-navegacao/stories.tsx
import { defineStories } from "@/registry/stories";
import { TrilhaDeNavegacao } from "./TrilhaDeNavegacao";
const CAMINHOS = {
curto: [
{ rotulo: "Botões", href: "#botoes" },
{ rotulo: "Botão", href: "#botao" },
],
longo: [
{ rotulo: "Início", href: "#inicio" },
{ rotulo: "Projetos", href: "#projetos" },
{ rotulo: "Site novo", href: "#site-novo" },
{ rotulo: "Configurações", href: "#configuracoes" },
],
};
export default defineStories({
args: { caminho: "curto", separador: "→" },
controls: {
caminho: { type: "select", options: ["curto", "longo"] },
separador: { type: "text" },
},
render: (a) => (
<TrilhaDeNavegacao itens={CAMINHOS[a.caminho as keyof typeof CAMINHOS]} separador={a.separador} />
),
stories: [
{ name: "Trilha" },
{ name: "Caminho longo", args: { caminho: "longo", separador: "/" } },
{
name: "Último item sem link",
render: () => (
<TrilhaDeNavegacao
separador="›"
itens={[{ rotulo: "Catálogo", href: "#catalogo" }, { rotulo: "Comparar" }]}
/>
),
},
{
name: "Quebra em telas estreitas",
render: () => (
<div className="w-60">
<TrilhaDeNavegacao itens={CAMINHOS.longo} />
</div>
),
},
],
});
Para usar em outro projeto, copie também
Os componentes de que este depende e o tema. O CSS do tema é um arquivo só: importe depois do Tailwind.
Tema escolhido no cabeçalho (com a ponte para o Tailwind)
Ver o CSS do tema
tema-editorial.css · Editorial
/* Tema "Editorial", gerado no Component Vault.
Importe depois de @import "tailwindcss". Carregue as fontes "Archivo" e "Inter" no projeto. */
@theme inline {
--color-page: var(--page);
--color-surface: var(--surface);
--color-track: var(--track);
--color-grid: var(--grid);
--color-ink: var(--ink);
--color-ink-2: var(--ink-2);
--color-muted: var(--muted);
--color-rule: var(--rule);
--color-rule-strong: var(--rule-strong);
--color-axis: var(--axis);
--color-accent: var(--accent);
--color-success: var(--success);
--color-warning: var(--warning);
--color-critical: var(--critical);
--font-display: var(--fonte-titulo);
--shadow-flutuante: var(--sombra-flutuante);
}
:root {
color-scheme: light dark;
--page: light-dark(#ffffff, #0f0f0e);
--surface: light-dark(#f5f5f5, #1a1a19);
--track: light-dark(#f0f0f0, #262624);
--grid: light-dark(#ededed, #232321);
--ink: light-dark(#111111, #f0efec);
--ink-2: light-dark(#4a4a4a, #c3c2b7);
--muted: light-dark(#666666, #a3a29a);
--rule: light-dark(#e3e3e3, #2c2c2a);
--rule-strong: light-dark(#111111, #f0efec);
--axis: light-dark(#9a9a9a, #6b6a64);
--accent: light-dark(#1a56db, #7aa2f7);
--success: light-dark(#13865c, #3fc48f);
--warning: #fab219;
--critical: #d03b3b;
--series-1: light-dark(#2a78d6, #3987e5);
--series-2: light-dark(#eb6834, #d95926);
--series-3: light-dark(#1baf7a, #199e70);
--series-4: light-dark(#eda100, #c98500);
--series-5: light-dark(#e87ba4, #d55181);
--series-6: #008300;
--series-7: light-dark(#6250d6, #9085e9);
--series-8: light-dark(#e34948, #e66767);
--fonte-titulo: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
--fonte-texto: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
--spacing: 0.25rem;
--text-xs: 0.8rem;
--text-sm: 0.8944rem;
--text-base: 1rem;
--text-lg: 1.118rem;
--text-xl: 1.25rem;
--text-2xl: 1.5625rem;
--text-3xl: 1.9531rem;
--text-4xl: 2.4414rem;
--text-5xl: 3.0518rem;
--text-6xl: 3.8147rem;
--text-7xl: 4.7684rem;
--radius-xs: 2px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-3xl: 24px;
--radius-4xl: 32px;
--sombra-flutuante: 0 8px 24px -6px rgb(0 0 0 / 0.14), 0 2px 6px -2px rgb(0 0 0 / 0.08);
color: var(--ink);
font-family: var(--fonte-texto);
}
Na vitrine, a ponte com o Tailwind fica em src/themes/tokens.css:
src/themes/tokens.css
/* Ponte entre os tokens do tema e o Tailwind.
Os componentes da biblioteca usam classes comuns do Tailwind (p-5, text-sm,
rounded-xl...) e as cores semânticas abaixo (bg-page, text-ink...). Dentro de
[data-theme="vault"], o tema redefine as variáveis que essas classes leem:
--spacing, --text-*, --radius-* e as cores. Os valores vêm do tema escolhido
na vitrine (src/themes/presets.ts) e são gerados por themeCss(). */
@theme inline {
--color-page: var(--page);
--color-surface: var(--surface);
--color-ink: var(--ink);
--color-ink-2: var(--ink-2);
--color-muted: var(--muted);
--color-rule: var(--rule);
--color-rule-strong: var(--rule-strong);
--color-track: var(--track);
--color-grid: var(--grid);
--color-axis: var(--axis);
--color-accent: var(--accent);
--color-success: var(--success);
--color-warning: var(--warning);
--color-critical: var(--critical);
--font-display: var(--fonte-titulo);
--shadow-flutuante: var(--sombra-flutuante);
}