* feat(design): redesign das páginas internas no novo design system Padroniza todas as páginas e componentes internos nos tokens e primitivos de app/globals.css, com contraste AAA (7:1) nos dois temas e alvos de toque de 44px. - Páginas reescritas: glossário, séries, série, seletor, comparador, diagnóstico, catálogo, component registry, frameworks, framework, sobre e a nova 404. - Componentes: PageHero e SectionHead novos; BoardCard, CodeExample, PinRestrictions, SeriesTabMenu e ConnectionsDiagram refeitos, com abas e diálogo acessíveis. - Remove emojis, cores avulsas por série, caixa alta com tracking, texto menor que 12px e os pares slate-*/dark:*. - Comparador: "mostrar só as diferenças" e leitura de ?chips=. - Corrige overflow em 320/390px e ajusta accent/copper do tema claro para fechar 7:1. - scripts/auditar-temas.mjs passa a travar paleta crua, dark:, texto pequeno, emoji e contraste dos tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc * ci: roda lint, auditorias de design e build em cada PR Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
118 lines
3.7 KiB
React
118 lines
3.7 KiB
React
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Check, Copy, ExternalLink } from "lucide-react";
|
|
|
|
export default function CodeExample({ framework }) {
|
|
const examplesUrl = framework?.exemplos;
|
|
|
|
const [sample, setSample] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setSample("");
|
|
setError(null);
|
|
setLoading(false);
|
|
|
|
if (!framework) return;
|
|
|
|
const exemploPath = framework.exemplo;
|
|
if (!exemploPath) {
|
|
setSample(`// Exemplos completos: ${examplesUrl || "ver repositório"}\n`);
|
|
return;
|
|
}
|
|
|
|
const url = exemploPath.startsWith("/") ? exemploPath : `/${exemploPath}`;
|
|
|
|
let cancelled = false;
|
|
setLoading(true);
|
|
|
|
fetch(url)
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.text();
|
|
})
|
|
.then((text) => {
|
|
if (cancelled) return;
|
|
if (text && text.trim().length > 0) {
|
|
setSample(text);
|
|
} else {
|
|
setSample("// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n");
|
|
setError("arquivo vazio");
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (cancelled) return;
|
|
setSample("// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n");
|
|
setError(err.message || "falha ao carregar");
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setLoading(false);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [framework?.exemplo, framework?.exemplos]);
|
|
|
|
const lines = (sample || "").replace(/\n$/, "").split("\n");
|
|
|
|
async function handleCopy() {
|
|
try {
|
|
await navigator.clipboard.writeText(sample);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {}
|
|
}
|
|
|
|
return (
|
|
<div className="card p-5 sm:p-7">
|
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
<div>
|
|
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">Exemplo de código</h2>
|
|
<p className="mt-1 text-sm text-muted">Trecho de partida em {framework?.linguagem}. Ajuste pinos e configuração à sua placa.</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<button type="button" onClick={handleCopy} className="btn btn-ghost btn-sm">
|
|
{copied ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
|
|
<span role="status">{copied ? "Copiado" : "Copiar"}</span>
|
|
</button>
|
|
{examplesUrl && (
|
|
<a href={examplesUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm">
|
|
Mais exemplos
|
|
<ExternalLink className="h-4 w-4" aria-hidden="true" />
|
|
<span className="sr-only"> (abre em nova aba)</span>
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="code-panel mt-5" aria-busy={loading}>
|
|
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2.5 text-xs text-[#a6afbd]">
|
|
<span className="font-mono">{framework?.nome}</span>
|
|
<span>{loading ? "Carregando exemplo…" : framework?.linguagem}</span>
|
|
</div>
|
|
<pre tabIndex={0} aria-label={`Código de exemplo em ${framework?.linguagem}`}>
|
|
<code>
|
|
{lines.map((line, idx) => (
|
|
<span key={idx} className="flex">
|
|
<span className="mr-4 w-7 shrink-0 select-none text-right text-[#7d8794]">{idx + 1}</span>
|
|
<span className="min-w-0 whitespace-pre text-[#e9eef4]">{line || " "}</span>
|
|
</span>
|
|
))}
|
|
</code>
|
|
</pre>
|
|
</div>
|
|
|
|
{error && (
|
|
<p role="alert" className="callout callout-warn mt-4">
|
|
Não foi possível carregar o exemplo local ({error}). Use “Mais exemplos” para ver o código oficial.
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|