Redesign das páginas internas no novo design system (AAA) (#2)
* 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>
This commit is contained in:
1 parent
4f27f5858b
commit
bc9ae5c036
34 files changed
+4911
-6874
No files matched your search
+266
-586
@@ -1,22 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useEffect, useMemo, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import {
|
||||
Search,
|
||||
X,
|
||||
Grid,
|
||||
List,
|
||||
Filter,
|
||||
AlertTriangle,
|
||||
Info,
|
||||
Tag,
|
||||
Zap,
|
||||
Sliders,
|
||||
CheckCircle,
|
||||
ExternalLink
|
||||
} from "lucide-react";
|
||||
import { AlertTriangle, CheckCircle, List, Grid, Search, Sliders, X, Zap } from "lucide-react";
|
||||
|
||||
/*
|
||||
* As cores abaixo são dado de visualização: identificam o tipo de função do
|
||||
* pino no diagrama. O texto nunca fica sobre elas; elas aparecem só como borda
|
||||
* ou ponto, e o rótulo usa as cores do tema (contraste AAA).
|
||||
*/
|
||||
const FILTER_CATEGORIES = [
|
||||
{ id: "All", name: "Todos", color: "#8b5cf6" }, // purple
|
||||
{ id: "GND", name: "Terra (GND)", color: "#64748b" }, // slate
|
||||
@@ -52,11 +44,11 @@ const type_colors = {
|
||||
export default function ConnectionsDiagram({ connections, serie }) {
|
||||
const [activeFilter, setActiveFilter] = useState("All");
|
||||
const [selectedConnection, setSelectedConnection] = useState(null);
|
||||
const [hoveredConnection, setHoveredConnection] = useState(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [viewMode, setViewMode] = useState("grid");
|
||||
const [showTooltip, setShowTooltip] = useState(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const dialogRef = useRef(null);
|
||||
const gatilhoRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -64,7 +56,8 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
const isConnectionVisible = (connection) => {
|
||||
const matchesFilter = activeFilter === "All" || connection.categorias.includes(activeFilter);
|
||||
const matchesSearch = searchQuery === "" ||
|
||||
const matchesSearch =
|
||||
searchQuery === "" ||
|
||||
connection.nome.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
connection.numero.toString().toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesFilter && matchesSearch;
|
||||
@@ -75,641 +68,328 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
return filter?.color || "#64748b";
|
||||
};
|
||||
|
||||
const visibleConnections = useMemo(() => {
|
||||
return connections.filter(isConnectionVisible);
|
||||
}, [connections, activeFilter, searchQuery]);
|
||||
const nomeCategoria = (cat) => (cat === "Strapping" ? "Inicialização" : cat);
|
||||
|
||||
const activeCategories = useMemo(() => {
|
||||
if (activeFilter === "All") return [];
|
||||
return FILTER_CATEGORIES.filter(cat =>
|
||||
visibleConnections.some(conn => conn.categorias.includes(cat.id))
|
||||
);
|
||||
}, [visibleConnections, activeFilter]);
|
||||
const visibleConnections = useMemo(() => connections.filter(isConnectionVisible), [connections, activeFilter, searchQuery]);
|
||||
|
||||
const abrir = (connection, evento) => {
|
||||
gatilhoRef.current = evento.currentTarget;
|
||||
setSelectedConnection(connection);
|
||||
};
|
||||
|
||||
const fechar = () => {
|
||||
setSelectedConnection(null);
|
||||
gatilhoRef.current?.focus?.();
|
||||
};
|
||||
|
||||
// Diálogo: Esc fecha, o foco entra nele, fica preso enquanto aberto e volta ao pino de origem.
|
||||
useEffect(() => {
|
||||
const handleEscape = (e) => {
|
||||
if (!selectedConnection) return;
|
||||
const dialogo = dialogRef.current;
|
||||
const focaveis = () => dialogo?.querySelectorAll('button, [href], input, [tabindex]:not([tabindex="-1"])') ?? [];
|
||||
focaveis()[0]?.focus();
|
||||
|
||||
const aoTeclar = (e) => {
|
||||
if (e.key === "Escape") {
|
||||
setSelectedConnection(null);
|
||||
setShowTooltip(null);
|
||||
fechar();
|
||||
return;
|
||||
}
|
||||
if (e.key !== "Tab") return;
|
||||
const itens = [...focaveis()];
|
||||
if (itens.length === 0) return;
|
||||
const primeiro = itens[0];
|
||||
const ultimo = itens[itens.length - 1];
|
||||
if (e.shiftKey && document.activeElement === primeiro) {
|
||||
e.preventDefault();
|
||||
ultimo.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === ultimo) {
|
||||
e.preventDefault();
|
||||
primeiro.focus();
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedConnection) {
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("keydown", aoTeclar);
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "unset";
|
||||
document.removeEventListener("keydown", aoTeclar);
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [selectedConnection]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
if (selectedConnection) return;
|
||||
|
||||
if (e.key === "Tab") {
|
||||
const buttons = document.querySelectorAll('[data-connection-button]');
|
||||
const currentIndex = Array.from(buttons).findIndex(btn => btn === document.activeElement);
|
||||
|
||||
if (e.shiftKey && currentIndex > 0) {
|
||||
e.preventDefault();
|
||||
buttons[currentIndex - 1].focus();
|
||||
} else if (!e.shiftKey && currentIndex < buttons.length - 1) {
|
||||
e.preventDefault();
|
||||
buttons[currentIndex + 1].focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedConnection]);
|
||||
|
||||
if (!connections || connections.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16 px-4">
|
||||
<div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-300 dark:border-slate-800 flex items-center justify-center shadow-lg">
|
||||
<Sliders className="w-10 h-10 text-slate-500 dark:text-slate-400" />
|
||||
</div>
|
||||
|
||||
<p className="text-lg font-bold text-slate-800 dark:text-slate-200">
|
||||
Diagrama de conexões indisponível
|
||||
</p>
|
||||
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-2">
|
||||
Este modelo ainda não possui um mapa detalhado de conexões e barramentos.
|
||||
</p>
|
||||
<div className="px-4 py-14 text-center">
|
||||
<Sliders className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
|
||||
<p className="mt-4 font-display text-xl font-semibold text-ink">Diagrama de conexões indisponível</p>
|
||||
<p className="mt-1 text-sm text-muted">Este modelo ainda não tem um mapa detalhado de conexões e barramentos.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const numeroCurto = (c) => (typeof c.numero === "number" ? c.numero : String(c.numero).substring(0, 4));
|
||||
|
||||
return (
|
||||
<div className="space-y-8 pb-8">
|
||||
|
||||
{/* Header info */}
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
|
||||
<Sliders className="w-5 h-5" style={{ color: serie.color }} />
|
||||
<span>Diagrama de Conexões Interativo</span>
|
||||
</h2>
|
||||
<p className="text-sm text-slate-550 dark:text-slate-400 leading-relaxed">
|
||||
Explore todas as conexões e multiplexação do SoC do {serie.nome_completo}. Utilize o painel de busca rápida ou filtre por categoria para acender conexões específicas e ver mapas de barramentos.
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">Diagrama de conexões interativo</h2>
|
||||
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted">
|
||||
Todas as conexões e a multiplexação do {serie.nome_completo}. Busque por nome ou número, filtre por função e abra um pino para ver os detalhes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search Panel */}
|
||||
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 space-y-4 shadow-xl">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-500 dark:text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold"
|
||||
aria-label="Search connections"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery("")}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex gap-1.5 bg-slate-100 dark:bg-slate-950/80 rounded-xl p-1 shrink-0 border border-slate-250/30 dark:border-slate-850/30">
|
||||
<button
|
||||
onClick={() => setViewMode("grid")}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-xs font-bold uppercase tracking-wider cursor-pointer active:scale-95 ${
|
||||
viewMode === "grid"
|
||||
? "bg-white dark:bg-slate-900 shadow-md text-slate-900 dark:text-slate-100"
|
||||
: "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200"
|
||||
}`}
|
||||
aria-label="Grid view"
|
||||
aria-pressed={viewMode === "grid"}
|
||||
>
|
||||
<Grid className="w-3.5 h-3.5" />
|
||||
<span>Grade</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-xs font-bold uppercase tracking-wider cursor-pointer active:scale-95 ${
|
||||
viewMode === "list"
|
||||
? "bg-white dark:bg-slate-900 shadow-md text-slate-900 dark:text-slate-100"
|
||||
: "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200"
|
||||
}`}
|
||||
aria-label="List view"
|
||||
aria-pressed={viewMode === "list"}
|
||||
>
|
||||
<List className="w-3.5 h-3.5" />
|
||||
<span>Lista</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<div className="relative flex-1">
|
||||
<label htmlFor="busca-pino" className="sr-only">Buscar conexão por nome ou número</label>
|
||||
<Search className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" />
|
||||
<input
|
||||
id="busca-pino"
|
||||
type="search"
|
||||
placeholder="Buscar pino (ex.: GPIO1, TXD, XTAL)"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
autoComplete="off"
|
||||
className="field pl-11 pr-4 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div role="group" aria-label="Modo de exibição" className="flex gap-2">
|
||||
<button type="button" onClick={() => setViewMode("grid")} aria-pressed={viewMode === "grid"} className="chip-btn">
|
||||
<Grid className="h-4 w-4" aria-hidden="true" />
|
||||
Grade
|
||||
</button>
|
||||
<button type="button" onClick={() => setViewMode("list")} aria-pressed={viewMode === "list"} className="chip-btn">
|
||||
<List className="h-4 w-4" aria-hidden="true" />
|
||||
Lista
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters Panel */}
|
||||
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 shadow-xl">
|
||||
<div className="flex items-center gap-3 mb-5 border-b border-slate-150/40 dark:border-slate-850/45 pb-3">
|
||||
<Filter className="w-4 h-4 text-purple-500" />
|
||||
<h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none">
|
||||
Filtre por Função do Pino
|
||||
</h4>
|
||||
|
||||
<span className="ml-auto text-[10px] font-bold text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-300 dark:border-slate-850/30 shadow-xs select-none">
|
||||
{visibleConnections.length} de {connections.length} ativos
|
||||
</span>
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="text-sm font-semibold text-ink">Função do pino</h3>
|
||||
<p role="status" aria-live="polite" className="text-sm text-muted">
|
||||
{visibleConnections.length} de {connections.length} pinos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div role="group" aria-label="Filtrar por função" className="mt-3 flex flex-wrap gap-2">
|
||||
{FILTER_CATEGORIES.map((filter) => {
|
||||
const isActive = activeFilter === filter.id;
|
||||
const count = connections.filter(c =>
|
||||
filter.id === "All" ? true : c.categorias.includes(filter.id)
|
||||
).length;
|
||||
|
||||
if (count === 0 && !isActive) return null; // Hide filters that don't apply to this chip
|
||||
|
||||
const count = connections.filter((c) => (filter.id === "All" ? true : c.categorias.includes(filter.id))).length;
|
||||
if (count === 0 && !isActive) return null; // filtros que não se aplicam a este chip
|
||||
return (
|
||||
<button
|
||||
key={filter.id}
|
||||
onClick={() => setActiveFilter(filter.id)}
|
||||
aria-pressed={isActive}
|
||||
aria-label={`Filter by ${filter.name}`}
|
||||
className={`group relative px-4 py-2 rounded-xl text-xs font-extrabold transition-all duration-300 focus:outline-none cursor-pointer active:scale-95 ${
|
||||
isActive
|
||||
? "shadow-lg text-white"
|
||||
: "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-300 dark:border-slate-850/40"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: isActive ? filter.color : undefined,
|
||||
boxShadow: isActive ? `0 8px 15px -8px ${filter.color}` : undefined
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{filter.name}</span>
|
||||
<span className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 font-bold ${
|
||||
isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400"
|
||||
}`}>
|
||||
{count}
|
||||
</span>
|
||||
</span>
|
||||
<button key={filter.id} type="button" onClick={() => setActiveFilter(filter.id)} aria-pressed={isActive} className="chip-btn">
|
||||
{filter.id !== "All" && <span className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ backgroundColor: filter.color }} aria-hidden="true" />}
|
||||
{filter.name}
|
||||
<span className="tabular-nums opacity-80">{count}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Active Categories badges */}
|
||||
{activeFilter !== "All" && activeCategories.length > 0 && (
|
||||
<div className="mt-5 pt-5 border-t border-slate-200 dark:border-slate-850/40 select-none">
|
||||
<p className="text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeCategories.map(cat => (
|
||||
<span
|
||||
key={cat.id}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-[10px] font-bold text-white shadow-xs"
|
||||
style={{ backgroundColor: cat.color }}
|
||||
>
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-white shrink-0"></span>
|
||||
<span>{cat.name}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Diagram Workspace */}
|
||||
{visibleConnections.length === 0 ? (
|
||||
<div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-300 dark:border-slate-850/80 text-center shadow-inner">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 dark:bg-slate-950 flex items-center justify-center shadow-md">
|
||||
<Search className="w-6 h-6 text-slate-400" />
|
||||
</div>
|
||||
<p className="text-base font-bold text-slate-800 dark:text-slate-200 mb-1 select-none">Nenhuma conexão encontrada</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar seus termos de pesquisa ou limpe os filtros rápidos.</p>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveFilter("All");
|
||||
setSearchQuery("");
|
||||
}}
|
||||
className="mt-5 px-6 py-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg"
|
||||
>
|
||||
Limpar Filtros e Busca
|
||||
<div className="rounded-2xl border border-dashed border-line-strong px-6 py-12 text-center">
|
||||
<Search className="mx-auto h-8 w-8 text-muted" aria-hidden="true" />
|
||||
<p className="mt-3 font-semibold text-ink">Nenhuma conexão encontrada</p>
|
||||
<p className="mt-1 text-sm text-muted">Ajuste a busca ou volte para todas as funções.</p>
|
||||
<button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); }} className="btn btn-primary btn-sm mt-5">
|
||||
Limpar busca e filtros
|
||||
</button>
|
||||
</div>
|
||||
) : viewMode === "grid" ? (
|
||||
/* Grid View Mode: Futuristic Electronic Chip Sockets */
|
||||
<div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-850/60 shadow-inner">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
|
||||
{visibleConnections.map((connection, index) => {
|
||||
const isHovered = hoveredConnection === index;
|
||||
const connectionId = `connection-${index}`;
|
||||
const baseColor = type_colors[connection.tipo] || "#94a3b8";
|
||||
|
||||
return (
|
||||
<div key={index} className="relative">
|
||||
<button
|
||||
id={connectionId}
|
||||
data-connection-button
|
||||
onClick={() => setSelectedConnection(connection)}
|
||||
onMouseEnter={() => {
|
||||
setHoveredConnection(index);
|
||||
setShowTooltip(index);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoveredConnection(null);
|
||||
setShowTooltip(null);
|
||||
}}
|
||||
onFocus={() => setHoveredConnection(index)}
|
||||
onBlur={() => setHoveredConnection(null)}
|
||||
aria-label={`Pino ${connection.nome}, número ${connection.numero}`}
|
||||
aria-describedby={showTooltip === index ? `tooltip-${index}` : undefined}
|
||||
className="w-full flex flex-col items-center justify-center p-4 rounded-2xl border-2 transition-all duration-300 cursor-pointer active:scale-95 focus:outline-none"
|
||||
style={{
|
||||
backgroundColor: isHovered ? `${baseColor}24` : `${baseColor}0c`,
|
||||
borderColor: isHovered ? baseColor : `${baseColor}40`,
|
||||
boxShadow: isHovered ? `0 0 20px -5px ${baseColor}60` : 'none',
|
||||
transform: isHovered ? "translateY(-4px)" : undefined,
|
||||
}}
|
||||
<ul className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
|
||||
{visibleConnections.map((connection) => {
|
||||
const baseColor = type_colors[connection.tipo] || "#94a3b8";
|
||||
return (
|
||||
<li key={`${connection.numero}-${connection.nome}`} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => abrir(connection, e)}
|
||||
aria-haspopup="dialog"
|
||||
aria-label={`Pino ${connection.nome}, número ${connection.numero}${connection.avisos ? ", com restrições de uso" : ""}`}
|
||||
className="flex w-full flex-col items-center rounded-xl border border-line bg-surface p-3 transition-colors hover:bg-surface-2"
|
||||
>
|
||||
<span
|
||||
className="grid h-11 w-11 place-items-center rounded-full border-2 bg-surface text-sm font-semibold tabular-nums text-ink"
|
||||
style={{ borderColor: baseColor }}
|
||||
>
|
||||
{/* Pad Contact Core Circle */}
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center text-white font-extrabold text-xs mb-3 shadow-lg select-none"
|
||||
style={{ backgroundColor: baseColor, boxShadow: `0 4px 10px -3px ${baseColor}` }}
|
||||
>
|
||||
{typeof connection.numero === "number"
|
||||
? connection.numero
|
||||
: connection.numero.substring(0, 4)}
|
||||
</div>
|
||||
|
||||
{/* Pad Name label */}
|
||||
<div className="text-xs font-bold text-center text-slate-850 dark:text-slate-200 leading-tight mb-2 select-none group-hover:text-purple-600 transition-colors">
|
||||
{connection.nome}
|
||||
</div>
|
||||
|
||||
{/* Small Category dots indicator */}
|
||||
<div className="flex flex-wrap gap-1 justify-center select-none">
|
||||
{connection.categorias.slice(0, 5).map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="w-1.5 h-1.5 rounded-full shrink-0"
|
||||
style={{ backgroundColor: getCategoryColor(cat) }}
|
||||
title={cat === "Strapping" ? "Inicialização" : cat}
|
||||
/>
|
||||
))}
|
||||
{connection.categorias.length > 5 && (
|
||||
<span className="text-[9px] text-slate-500 dark:text-slate-400 font-bold ml-0.5 leading-none">
|
||||
+{connection.categorias.length - 5}
|
||||
{numeroCurto(connection)}
|
||||
</span>
|
||||
<span className="mt-2 break-all text-center text-sm font-medium leading-tight text-ink">{connection.nome}</span>
|
||||
<span className="mt-2 flex flex-wrap justify-center gap-1" aria-hidden="true">
|
||||
{connection.categorias.slice(0, 5).map((cat) => (
|
||||
<span key={cat} className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: getCategoryColor(cat) }} />
|
||||
))}
|
||||
</span>
|
||||
</button>
|
||||
{connection.avisos && (
|
||||
<span className="pointer-events-none absolute -right-1.5 -top-1.5 grid h-6 w-6 place-items-center rounded-full border-2 border-surface bg-danger-soft text-danger" aria-hidden="true">
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="overflow-x-auto rounded-xl border border-line">
|
||||
<table className="w-full min-w-[40rem] border-collapse text-left text-sm">
|
||||
<caption className="sr-only">Conexões do {serie.nome_completo}</caption>
|
||||
<thead>
|
||||
<tr className="border-b border-line bg-surface-2 text-muted">
|
||||
<th scope="col" className="px-4 py-3 font-medium">Pino</th>
|
||||
<th scope="col" className="px-4 py-3 font-medium">Nome</th>
|
||||
<th scope="col" className="px-4 py-3 font-medium">Funções</th>
|
||||
<th scope="col" className="px-4 py-3 font-medium">Uso</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-line">
|
||||
{visibleConnections.map((connection) => {
|
||||
const baseColor = type_colors[connection.tipo] || "#94a3b8";
|
||||
return (
|
||||
<tr key={`${connection.numero}-${connection.nome}`} className="hover:bg-surface-2/60">
|
||||
<td className="px-4 py-2.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => abrir(connection, e)}
|
||||
aria-haspopup="dialog"
|
||||
aria-label={`Abrir detalhes do pino ${connection.nome}, número ${connection.numero}`}
|
||||
className="grid h-11 w-11 place-items-center rounded-full border-2 bg-surface text-sm font-semibold tabular-nums text-ink hover:bg-surface-2"
|
||||
style={{ borderColor: baseColor }}
|
||||
>
|
||||
{numeroCurto(connection)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-4 py-2.5 font-medium text-ink">{connection.nome}</td>
|
||||
<td className="max-w-md px-4 py-2.5 text-muted">
|
||||
<span className="line-clamp-2">{connection.funcoes.join(", ")}</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-4 py-2.5">
|
||||
{connection.avisos ? (
|
||||
<span className="inline-flex items-center gap-1.5 font-medium text-danger">
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden="true" />
|
||||
Restrito
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 text-ok">
|
||||
<CheckCircle className="h-4 w-4" aria-hidden="true" />
|
||||
Livre
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* High-Voltage warnings tags */}
|
||||
{connection.avisos && (
|
||||
<div
|
||||
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 shadow-lg ring-2 ring-white dark:ring-slate-950 animate-pulse"
|
||||
aria-label="Possui restrições de uso"
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Sleek Tooltip Popover */}
|
||||
{showTooltip === index && (
|
||||
<div
|
||||
id={`tooltip-${index}`}
|
||||
role="tooltip"
|
||||
className="absolute z-40 bottom-full left-1/2 -translate-x-1/2 mb-3 p-3 bg-slate-950/90 dark:bg-slate-900/90 backdrop-blur-md text-white text-[10px] rounded-xl shadow-2xl max-w-xs pointer-events-none animate-fadeIn border border-slate-850/50"
|
||||
style={{ minWidth: "180px" }}
|
||||
>
|
||||
<div className="font-extrabold text-sm mb-1">{connection.nome}</div>
|
||||
<div className="text-slate-400 mb-2 font-semibold">Pino Físico {connection.numero}</div>
|
||||
<div className="flex flex-wrap gap-1 mb-2">
|
||||
{connection.categorias.map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="px-1.5 py-0.5 rounded text-[8px] font-bold"
|
||||
style={{
|
||||
backgroundColor: `${getCategoryColor(cat)}24`,
|
||||
color: getCategoryColor(cat)
|
||||
}}
|
||||
>
|
||||
{cat === "Strapping" ? "Inicialização" : cat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-purple-400 font-bold text-[9px] uppercase tracking-wider">Clique para detalhes</div>
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-slate-950/95 dark:border-t-slate-900/95"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
/* List View Mode: Interactive Zebra Table */
|
||||
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 overflow-hidden shadow-xl">
|
||||
<div className="overflow-x-auto scrollbar-thin">
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 select-none">
|
||||
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Pino</th>
|
||||
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Nome do Pad</th>
|
||||
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Categorias</th>
|
||||
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Multiplexação de Funções</th>
|
||||
<th className="px-6 py-4 text-center text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Restrições</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100 dark:divide-slate-850/30">
|
||||
{visibleConnections.map((connection, index) => {
|
||||
const baseColor = type_colors[connection.tipo] || "#94a3b8";
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
onClick={() => setSelectedConnection(connection)}
|
||||
className="hover:bg-purple-500/5 dark:hover:bg-purple-400/5 cursor-pointer transition-colors group"
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center text-white font-extrabold text-xs shadow-md select-none"
|
||||
style={{ backgroundColor: baseColor }}
|
||||
>
|
||||
{typeof connection.numero === "number"
|
||||
? connection.numero
|
||||
: connection.numero.substring(0, 4)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-bold text-slate-800 dark:text-slate-200 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
|
||||
{connection.nome}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex flex-wrap gap-1.5 select-none">
|
||||
{connection.categorias.map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md text-[9px] font-bold text-white shadow-xs"
|
||||
style={{ backgroundColor: getCategoryColor(cat) }}
|
||||
>
|
||||
{cat === "Strapping" ? "Inicialização" : cat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 font-semibold max-w-md line-clamp-2 leading-relaxed">
|
||||
{connection.funcoes.join(", ")}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-4 text-center whitespace-nowrap">
|
||||
{connection.avisos ? (
|
||||
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-red-500/10 text-red-600 dark:text-red-400 text-[10px] font-bold uppercase border border-red-500/20 select-none">
|
||||
<AlertTriangle className="w-3.5 h-3.5 shrink-0" />
|
||||
<span>Restrito</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase border border-emerald-500/20 select-none">
|
||||
<CheckCircle className="w-3.5 h-3.5 shrink-0" />
|
||||
<span>Livre</span>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{connections.some((c) => c.avisos) && (
|
||||
<div className="callout callout-warn flex items-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
|
||||
<div>
|
||||
<p className="font-semibold text-ink">Notas sobre a pinagem</p>
|
||||
<ul className="mt-1 list-disc space-y-1 pl-5">
|
||||
<li>Pinos com o ícone de alerta têm papel especial no boot ou limitações analógicas. Abra o pino para ver os detalhes.</li>
|
||||
<li>Isole os circuitos de toque capacitivo de pinos com ruído de RF acoplado.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Global Warning notice */}
|
||||
{connections.some(c => c.avisos) && (
|
||||
<div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 rounded-2xl p-6 shadow-sm select-none">
|
||||
<div className="flex gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="w-12 h-12 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<p className="text-base font-bold text-amber-850 dark:text-amber-300 mb-1">
|
||||
Notas Importantes sobre a Pinagem
|
||||
</p>
|
||||
<ul className="space-y-1.5 text-xs text-amber-800 dark:text-amber-400 leading-relaxed">
|
||||
<li>• Pinos contendo tags de <strong>Avisos/Restrições</strong> (cor vermelha) possuem papéis especiais de Strapping na inicialização, ou limitações analógicas. Clique neles para ver detalhes.</li>
|
||||
<li>• Sempre garanta isolamento de circuitos capacitivos de toque em pinos com ruído de RF acoplado.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Interactive Overlay Dialog Details Sheet */}
|
||||
{selectedConnection && mounted && createPortal(
|
||||
<div
|
||||
className="fixed inset-0 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4 z-50 animate-fadeIn"
|
||||
onClick={() => setSelectedConnection(null)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<div className="dialog-scrim fixed inset-0 z-50 flex items-center justify-center bg-code/80 p-4 backdrop-blur-sm" onClick={fechar}>
|
||||
<div
|
||||
className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp"
|
||||
ref={dialogRef}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
className="dialog-panel flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl border border-line bg-surface shadow-[var(--shadow)]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Dialog Header */}
|
||||
<div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 p-6 md:p-8 z-10">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<div
|
||||
className="w-14 h-14 rounded-2xl flex items-center justify-center text-white font-extrabold text-lg shadow-xl shrink-0"
|
||||
style={{ backgroundColor: type_colors[selectedConnection.tipo] || "#64748b" }}
|
||||
>
|
||||
{typeof selectedConnection.numero === "number"
|
||||
? selectedConnection.numero
|
||||
: selectedConnection.numero.substring(0, 4)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 id="modal-title" className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none tracking-tight">
|
||||
{selectedConnection.nome}
|
||||
</h3>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">
|
||||
Pino Físico {selectedConnection.numero}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setSelectedConnection(null)}
|
||||
aria-label="Fechar"
|
||||
className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors p-2 bg-slate-100 dark:bg-slate-900 rounded-xl shrink-0 cursor-pointer active:scale-95"
|
||||
<div className="flex items-start justify-between gap-4 border-b border-line p-5 sm:p-7">
|
||||
<div className="flex min-w-0 items-center gap-4">
|
||||
<span
|
||||
className="grid h-14 w-14 shrink-0 place-items-center rounded-full border-2 bg-surface text-lg font-semibold tabular-nums text-ink"
|
||||
style={{ borderColor: type_colors[selectedConnection.tipo] || "#64748b" }}
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
{numeroCurto(selectedConnection)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<h3 id="modal-title" className="break-words font-display text-2xl font-semibold tracking-[-0.03em] text-ink">
|
||||
{selectedConnection.nome}
|
||||
</h3>
|
||||
<p className="text-sm text-muted">Pino físico {selectedConnection.numero}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onClick={fechar} aria-label="Fechar detalhes do pino" className="grid h-11 w-11 shrink-0 place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink">
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Dialog Body Contents */}
|
||||
<div className="flex-1 overflow-y-auto p-6 md:p-8 space-y-8 scrollbar-thin">
|
||||
|
||||
{/* Categories */}
|
||||
<div className="flex-1 space-y-6 overflow-y-auto p-5 sm:p-7">
|
||||
{selectedConnection.categorias.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
|
||||
<Tag className="w-4 h-4 text-purple-500" />
|
||||
<span>Categorias de Multiplexação</span>
|
||||
</h4>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<section>
|
||||
<h4 className="text-sm font-semibold text-ink">Categorias</h4>
|
||||
<ul className="mt-3 flex flex-wrap gap-2">
|
||||
{selectedConnection.categorias.map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="px-3 py-1.5 rounded-xl text-xs font-bold text-white shadow-md select-none"
|
||||
style={{ backgroundColor: getCategoryColor(cat) }}
|
||||
>
|
||||
{cat === "Strapping" ? "Inicialização" : cat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Power Source */}
|
||||
{selectedConnection.alimentacao && (
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
|
||||
<Zap className="w-4 h-4 text-red-500" />
|
||||
<span>Domínio de Alimentação Interna</span>
|
||||
</h4>
|
||||
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
|
||||
<ul className="space-y-2.5">
|
||||
{selectedConnection.alimentacao.map((source, index) => (
|
||||
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
|
||||
<span
|
||||
className="shrink-0 w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: type_colors["power"] }}
|
||||
/>
|
||||
<span>{source}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Function Multiplexation */}
|
||||
<div>
|
||||
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
|
||||
<Sliders className="w-4 h-4 text-emerald-500" />
|
||||
<span>Barramentos de Funções Disponíveis</span>
|
||||
</h4>
|
||||
|
||||
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{selectedConnection.funcoes.map((func, index) => (
|
||||
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
|
||||
<span
|
||||
className="shrink-0 w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: type_colors[selectedConnection.tipo] || "#64748b" }}
|
||||
/>
|
||||
<span>{func}</span>
|
||||
<li key={cat} className="chip">
|
||||
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: getCategoryColor(cat) }} aria-hidden="true" />
|
||||
{nomeCategoria(cat)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{selectedConnection.alimentacao && (
|
||||
<section>
|
||||
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
|
||||
<Zap className="h-4 w-4 text-danger" aria-hidden="true" />
|
||||
Domínio de alimentação
|
||||
</h4>
|
||||
<ul className="tile mt-3 space-y-2">
|
||||
{selectedConnection.alimentacao.map((source) => (
|
||||
<li key={source} className="text-sm text-ink-soft">{source}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<h4 className="text-sm font-semibold text-ink">Funções disponíveis</h4>
|
||||
<ul className="tile mt-3 grid gap-2.5 sm:grid-cols-2">
|
||||
{selectedConnection.funcoes.map((func) => (
|
||||
<li key={func} className="flex items-center gap-2.5 text-sm text-ink-soft">
|
||||
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: type_colors[selectedConnection.tipo] || "#64748b" }} aria-hidden="true" />
|
||||
{func}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Alerts/Warnings */}
|
||||
{selectedConnection.avisos && (
|
||||
<div className="bg-red-500/10 border border-red-500/20 rounded-2xl p-5 shadow-xs">
|
||||
<div className="flex gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="w-10 h-10 rounded-xl bg-red-500/15 border border-red-500/25 flex items-center justify-center text-red-500">
|
||||
<AlertTriangle className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-bold text-red-900 dark:text-red-300 mb-2">
|
||||
Avisos Importantes de Engenharia
|
||||
</p>
|
||||
{selectedConnection.avisos.map((aviso, index) => (
|
||||
<div key={index} className="flex items-start gap-2.5 mb-1.5 text-xs text-red-800 dark:text-red-400 font-semibold leading-relaxed">
|
||||
<span>•</span>
|
||||
<span>{aviso}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="callout callout-danger">
|
||||
<h4 className="flex items-center gap-2 font-semibold text-ink">
|
||||
<AlertTriangle className="h-4 w-4 text-danger" aria-hidden="true" />
|
||||
Avisos de engenharia
|
||||
</h4>
|
||||
<ul className="mt-2 list-disc space-y-1.5 pl-5">
|
||||
{selectedConnection.avisos.map((aviso) => (
|
||||
<li key={aviso}>{aviso}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Dialog Footer */}
|
||||
<div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-300 dark:border-slate-850/60 p-5">
|
||||
<button
|
||||
onClick={() => setSelectedConnection(null)}
|
||||
className="w-full py-3.5 rounded-xl font-bold text-white text-xs uppercase tracking-wider shadow-lg hover:opacity-90 active:scale-98 transition-all cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: serie.color,
|
||||
boxShadow: `0 8px 25px -8px ${serie.color}`
|
||||
}}
|
||||
aria-label="Close modal"
|
||||
>
|
||||
Confirmar e Fechar
|
||||
</button>
|
||||
<div className="border-t border-line p-4 sm:px-7">
|
||||
<button type="button" onClick={fechar} className="btn btn-primary w-full">Fechar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
<style jsx>{`
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.animate-slideUp {
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user