Add public/series.json containing detailed ESP32 family specifications
Add a new JSON file public/series.json that defines detailed metadata for multiple ESP32 series variants. The file includes entries for: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-P4 and ESP32-H2. Each entry provides human-friendly and technical fields such as: - nome_completo, icone, descricao - arquitetura, nucleos, frequencia, coprocessador_ulp, aceleradores_ia (when applicable) - wireless capabilities (Wi‑Fi, Bluetooth, Zigbee/Thread, Matter), USB, Ethernet - memory details (SRAM, RTC SRAM, ROM, external flash/PSRAM) - peripherals and I/O counts (GPIO, ADC, DAC, touch, UART, SPI, I2C, I2S, PWM, SDIO, etc.) - sensor info, temperature ranges, datasheet links and UI color values This file will be used to power device/series lists and UI displays with consistent, localized technical descriptions and links to datasheets.
This commit is contained in:
1 parent
3d06c74447
commit
f49d9a8921
41 files changed
+14794
-132
No files matched your search
@@ -0,0 +1,430 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const FILTER_CATEGORIES = [
|
||||
{ id: "All", name: "Todos", color: "#64748b" },
|
||||
{ id: "Ground", name: "Terra (GND)", color: "#64748b" },
|
||||
{ id: "Power", name: "Alimentação", color: "#ef4444" },
|
||||
{ id: "RTC", name: "RTC", color: "#f59e0b" },
|
||||
{ id: "GPIO", name: "GPIO", color: "#22c55e" },
|
||||
{ id: "ADC", name: "ADC", color: "#ec4899" },
|
||||
{ id: "DAC", name: "DAC", color: "#a855f7" },
|
||||
{ id: "Touch", name: "Touch", color: "#06b6d4" },
|
||||
{ id: "UART", name: "UART", color: "#3b82f6" },
|
||||
{ id: "SPI", name: "SPI", color: "#ef4444" },
|
||||
{ id: "I2C", name: "I2C", color: "#10b981" },
|
||||
{ id: "PWM", name: "PWM", color: "#f97316" },
|
||||
{ id: "USB", name: "USB", color: "#8b5cf6" },
|
||||
{ id: "Clock", name: "Clock", color: "#eab308" },
|
||||
{ id: "JTAG", name: "JTAG", color: "#8b5cf6" },
|
||||
{ id: "Strapping", name: "Inicialização", color: "#dc2626" },
|
||||
];
|
||||
|
||||
export default function ConnectionsDiagram({ connections, color }) {
|
||||
const [activeFilter, setActiveFilter] = useState("All");
|
||||
const [selectedConnection, setSelectedConnection] = useState(null);
|
||||
const [hoveredConnection, setHoveredConnection] = useState(null);
|
||||
|
||||
const isConnectionVisible = (connection) => {
|
||||
if (activeFilter === "All") return true;
|
||||
return connection.categorias.includes(activeFilter);
|
||||
};
|
||||
|
||||
const getCategoryColor = (category) => {
|
||||
const filter = FILTER_CATEGORIES.find((f) => f.id === category);
|
||||
return filter?.color || "#64748b";
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e) => {
|
||||
if (e.key === "Escape") setSelectedConnection(null);
|
||||
};
|
||||
|
||||
if (selectedConnection) {
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "unset";
|
||||
};
|
||||
}, [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-full bg-gray-100 flex items-center justify-center">
|
||||
<svg
|
||||
className="w-10 h-10 text-gray-400"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p className="text-lg font-medium text-gray-600">
|
||||
Diagrama de conexões indisponível
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
Este modelo ainda não possui dados de conexão
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8 pb-8">
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-6 pb-3 border-b-2 flex items-center gap-3" style={{ borderColor: color }}>
|
||||
<span>📍</span>
|
||||
Diagrama de Conexões Interativo
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 leading-relaxed max-w-3xl">
|
||||
Clique em qualquer conexão para ver informações detalhadas ou use filtros para destacar funções específicas.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<svg
|
||||
className="w-5 h-5 text-gray-700"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
|
||||
<h4 className="text-lg font-semibold text-gray-900">
|
||||
Filtre por Função
|
||||
</h4>
|
||||
|
||||
{activeFilter !== "All" && (
|
||||
<span className="ml-auto text-sm text-gray-600">
|
||||
{connections.filter(isConnectionVisible).length} de {connections.length} conexões
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{FILTER_CATEGORIES.map((filter) => {
|
||||
const isActive = activeFilter === filter.id;
|
||||
return (
|
||||
<button
|
||||
key={filter.id}
|
||||
onClick={() => setActiveFilter(filter.id)}
|
||||
aria-pressed={isActive}
|
||||
aria-label={`Filter by ${filter.name}`}
|
||||
className={`px-5 py-2.5 rounded-xl text-sm font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
|
||||
isActive
|
||||
? "shadow-lg transform scale-105 text-white"
|
||||
: "bg-gray-50 text-gray-700 hover:bg-gray-100 hover:shadow-md"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: isActive ? filter.color : undefined,
|
||||
focusRingColor: filter.color,
|
||||
}}
|
||||
>
|
||||
{filter.name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-linear-to-br from-gray-50 to-gray-100 rounded-2xl p-8 border-2 border-gray-200 shadow-inner">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4">
|
||||
{connections.map((connection, index) => {
|
||||
const visible = isConnectionVisible(connection);
|
||||
const isHovered = hoveredConnection === index;
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => visible && setSelectedConnection(connection)}
|
||||
onMouseEnter={() => visible && setHoveredConnection(index)}
|
||||
onMouseLeave={() => setHoveredConnection(null)}
|
||||
disabled={!visible}
|
||||
aria-label={`Connection ${connection.nome}, number ${connection.numero}`}
|
||||
className={`relative flex flex-col items-center justify-center p-2 rounded-2xl border-2 transition-all duration-300 ${
|
||||
visible
|
||||
? "opacity-100 hover:scale-110 hover:shadow-2xl hover:z-10 cursor-pointer active:scale-95"
|
||||
: "opacity-15 cursor-not-allowed grayscale"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: visible ? connection.cor + "15" : "#f3f4f615",
|
||||
borderColor: visible ? (isHovered ? connection.cor : connection.cor + "60") : "#d1d5db",
|
||||
transform: isHovered ? "translateY(-2px)" : undefined,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center text-white font-bold text-sm mb-3 shadow-lg ring-2 ring-white"
|
||||
style={{ backgroundColor: connection.cor }}
|
||||
>
|
||||
{typeof connection.numero === "number"
|
||||
? connection.numero
|
||||
: connection.numero.substring(0, 4)}
|
||||
</div>
|
||||
|
||||
<div className="text-xs font-bold text-center text-gray-900 leading-tight">
|
||||
{connection.nome}
|
||||
</div>
|
||||
|
||||
{connection.restricoes && (
|
||||
<div
|
||||
className="absolute -top-2 -right-2 bg-red-500 rounded-full p-1.5 shadow-lg"
|
||||
aria-label="Has restrictions"
|
||||
>
|
||||
<svg
|
||||
className="w-3.5 h-3.5 text-white"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{connections.some(c => c.restricoes) && (
|
||||
<div className="bg-linear-to-r from-amber-50 to-yellow-50 border-2 border-amber-200 rounded-2xl p-6 shadow-sm">
|
||||
<div className="flex gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="w-12 h-12 rounded-full bg-amber-100 flex items-center justify-center">
|
||||
<svg
|
||||
className="w-6 h-6 text-amber-600"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<p className="text-lg font-bold text-amber-900 mb-3">
|
||||
Informações Importantes
|
||||
</p>
|
||||
<ul className="space-y-2.5 text-sm text-amber-800">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-amber-600 mt-0.5 font-bold">•</span>
|
||||
<span className="leading-relaxed">
|
||||
Conexões com ícones de aviso possuem restrições de uso. Clique para mais detalhes.
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-amber-600 mt-0.5 font-bold">•</span>
|
||||
<span className="leading-relaxed">
|
||||
Sempre consulte o datasheet oficial antes de usar conexões com funções especiais.
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedConnection && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-60 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="bg-white rounded-3xl shadow-2xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-slideUp"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="sticky top-0 bg-linear-to-r from-white to-gray-50 border-b-2 border-gray-200 p-8">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div className="flex items-center gap-5 flex-1">
|
||||
<div
|
||||
className="w-16 h-16 rounded-2xl flex items-center justify-center text-white font-bold text-xl shadow-xl ring-4 ring-white shrink-0"
|
||||
style={{ backgroundColor: selectedConnection.cor }}
|
||||
>
|
||||
{typeof selectedConnection.numero === "number"
|
||||
? selectedConnection.numero
|
||||
: selectedConnection.numero.substring(0, 4)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 id="modal-title" className="text-3xl font-bold text-gray-900 mb-1">
|
||||
{selectedConnection.nome}
|
||||
</h3>
|
||||
<p className="text-base text-gray-600">
|
||||
Conexão {selectedConnection.numero}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setSelectedConnection(null)}
|
||||
aria-label="Close modal"
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors p-2 hover:bg-gray-100 rounded-xl shrink-0"
|
||||
>
|
||||
<svg
|
||||
className="w-7 h-7"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-8 space-y-8">
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" />
|
||||
</svg>
|
||||
Categorias
|
||||
</h4>
|
||||
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{selectedConnection.categorias.map((cat) => (
|
||||
<span
|
||||
key={cat}
|
||||
className="px-4 py-2 rounded-xl text-sm font-bold text-white shadow-md ring-2 ring-white"
|
||||
style={{ backgroundColor: getCategoryColor(cat) }}
|
||||
>{cat}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Funções da Conexão
|
||||
</h4>
|
||||
|
||||
<div className="bg-linear-to-br from-gray-50 to-gray-100 rounded-2xl p-6 border border-gray-200">
|
||||
<ul className="space-y-3">
|
||||
{selectedConnection.funcoes.map((func, index) => (
|
||||
<li key={index} className="flex items-start gap-4 text-sm group">
|
||||
<span
|
||||
className="mt-1 shrink-0 w-2 h-2 rounded-full group-hover:scale-125 transition-transform"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
<span className="text-gray-800 font-medium leading-relaxed">{func}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedConnection.restricoes && (
|
||||
<div className="bg-linear-to-br from-red-50 to-rose-50 border-2 border-red-200 rounded-2xl p-6 shadow-sm">
|
||||
<div className="flex gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center">
|
||||
<svg
|
||||
className="w-6 h-6 text-red-600"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-lg font-bold text-red-900 mb-3">
|
||||
Alertas Importantes
|
||||
</p>
|
||||
<p className="text-sm text-red-800 leading-relaxed">
|
||||
{selectedConnection.restricoes}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="sticky bottom-0 bg-linear-to-t from-gray-50 to-white border-t-2 border-gray-200 p-6">
|
||||
<button
|
||||
onClick={() => setSelectedConnection(null)}
|
||||
className="w-full py-4 rounded-2xl font-bold text-white shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-offset-2"
|
||||
style={{ backgroundColor: color }}
|
||||
aria-label="Close modal"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100">
|
||||
<h3 className="text-lg font-bold text-gray-800 mb-3">Sobre a Matriz de Conexões Flexível</h3>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
O ESP32 possui uma matriz de conexões flexível que permite mapear diversas funções para múltiplos pinos.
|
||||
Isso significa que muitas conexões podem ser configuradas para desempenhar diferentes papéis, dependendo das necessidades do seu projeto.
|
||||
Consulte a documentação oficial para entender como aproveitar ao máximo essa flexibilidade.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-white py-3 text-black border-t border-gray-300 space-y-2">
|
||||
<div className="text-center leading-5 text-sm md:flex md:justify-between max-w-5xl mx-auto">
|
||||
<div className="m-0 p-0">
|
||||
© {new Date().getFullYear()}
|
||||
<a
|
||||
href="https://cienciaembarcada.com.br"
|
||||
className="text-blue-700 font-semibold hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Ciência Embarcada
|
||||
</a>
|
||||
. Todos os direitos reservados.
|
||||
</div>
|
||||
|
||||
<div className="m-0 p-0">
|
||||
Desenvolvido por
|
||||
<a
|
||||
href="https://linkedin.com/in/lucasrguerra"
|
||||
className="text-blue-700 font-semibold hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Lucas Rayan Guerra
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import Drawer from "@mui/material/Drawer";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import HomeIcon from "@mui/icons-material/Home";
|
||||
import AboutIcon from "@mui/icons-material/Info";
|
||||
import ModelsIcon from "@mui/icons-material/Memory";
|
||||
import FrameworksIcon from "@mui/icons-material/Apps";
|
||||
import CompareIcon from "@mui/icons-material/CompareArrows";
|
||||
|
||||
export default function Header() {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
const pathname = usePathname();
|
||||
const menuItems = [
|
||||
{ text: "Início", href: "/", icon: <HomeIcon /> },
|
||||
{ text: "Sobre o Projeto", href: "/sobre", icon: <AboutIcon /> },
|
||||
{ text: "Séries", href: "/series", icon: <ModelsIcon /> },
|
||||
{ text: "Frameworks", href: "/frameworks", icon: <FrameworksIcon /> },
|
||||
{ text: "Comparar Séries", href: "/comparacao", icon: <CompareIcon /> },
|
||||
];
|
||||
|
||||
function isActive(href) {
|
||||
return pathname === href;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="bg-white/80 backdrop-blur-md py-4 px-4 md:px-8 text-black shadow-lg border-b border-gray-100 sticky top-0 z-50">
|
||||
<div className="flex justify-between max-w-5xl mx-auto items-center">
|
||||
<Link href="/" className="group">
|
||||
<h1 className="text-2xl md:text-3xl font-bold bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-105">
|
||||
ESPDocs
|
||||
</h1>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setDrawerOpen(true)}
|
||||
className="border-2 border-gray-300 text-gray-700 hover:border-purple-500 hover:text-purple-600 transition-all duration-300 hover:shadow-lg rounded-xl! px-4"
|
||||
>
|
||||
<MenuIcon className="" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={drawerOpen}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
PaperProps={{
|
||||
className: "bg-linear-to-br from-white via-blue-50 to-purple-50"
|
||||
}}
|
||||
>
|
||||
<div className="p-6 w-72">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
Menu
|
||||
</h2>
|
||||
<div className="h-1 w-16 bg-linear-to-r from-blue-600 to-purple-600 rounded-full mt-2"></div>
|
||||
</div>
|
||||
|
||||
<List>
|
||||
{menuItems.map((item, index) => (
|
||||
<ListItem
|
||||
key={item.text}
|
||||
disablePadding
|
||||
className="mb-3"
|
||||
style={{
|
||||
animation: `slideIn 0.3s ease-out ${index * 0.1}s both`
|
||||
}}
|
||||
>
|
||||
<div className="w-full">
|
||||
<Link
|
||||
className={`flex items-center w-full p-4 text-left rounded-xl transition-all duration-300 ${
|
||||
isActive(item.href)
|
||||
? "bg-linear-to-r from-blue-600 to-purple-600 text-white shadow-lg transform scale-105"
|
||||
: "hover:bg-white hover:shadow-md hover:transform hover:scale-102 text-gray-700"
|
||||
}`}
|
||||
onClick={() => { setDrawerOpen(false); }}
|
||||
href={item.href}
|
||||
>
|
||||
<span className={`transition-transform duration-300 ${isActive(item.href) ? "scale-110" : "group-hover:scale-110"}`}>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className="ml-3 font-medium">{item.text}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<div className="mt-8 p-4 bg-white/50 backdrop-blur-sm rounded-xl border border-gray-200">
|
||||
<p className="text-xs text-gray-600 text-center">
|
||||
Documentação não oficial
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<style jsx global>{`
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function SeriesTabMenu({ tabs, color }) {
|
||||
const availableTabs = tabs.filter(tab => tab.available);
|
||||
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs');
|
||||
|
||||
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<div className="hidden md:flex bg-white rounded-2xl shadow-lg py-2 px-4 mb-6 border-2 border-gray-100 gap-4">
|
||||
{availableTabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-4 px-6 rounded-xl font-semibold transition-all duration-300 ${
|
||||
activeTab === tab.id
|
||||
? 'text-white shadow-lg transform scale-105'
|
||||
: 'text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
style={
|
||||
activeTab === tab.id
|
||||
? { backgroundColor: color }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<span className="hidden lg:inline">{tab.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="md:hidden mb-6">
|
||||
<div className="bg-white rounded-2xl shadow-lg border-2 border-gray-100 overflow-hidden">
|
||||
<button
|
||||
onClick={() => {
|
||||
const dropdown = document.getElementById('mobile-tab-dropdown');
|
||||
dropdown.classList.toggle('hidden');
|
||||
}}
|
||||
className="w-full flex items-center justify-between py-4 px-6 font-semibold text-white"
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xl">
|
||||
{availableTabs.find(tab => tab.id === activeTab)?.icon}
|
||||
</span>
|
||||
<span>
|
||||
{availableTabs.find(tab => tab.id === activeTab)?.label}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xl">▼</span>
|
||||
</button>
|
||||
|
||||
<div id="mobile-tab-dropdown" className="hidden">
|
||||
{availableTabs.map((tab) => (
|
||||
tab.id !== activeTab && (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => {
|
||||
setActiveTab(tab.id);
|
||||
document.getElementById('mobile-tab-dropdown').classList.add('hidden');
|
||||
}}
|
||||
className="w-full flex items-center gap-3 py-4 px-6 text-gray-700 hover:bg-gray-50 transition-colors border-t border-gray-100"
|
||||
>
|
||||
<span className="text-xl">{tab.icon}</span>
|
||||
<span className="font-medium">{tab.label}</span>
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="animate-fadeIn">
|
||||
{activeTabContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user