From dec8bd390956a93065b1cbf52d2f5476a0b24cdd Mon Sep 17 00:00:00 2001 From: Marco Santos Date: Thu, 24 Jul 2025 00:07:59 +0100 Subject: [PATCH] Novo layout --- README.md | 5 +- geradoresfe/.env | 2 +- geradoresfe/geradoresfe.esproj | 1 + geradoresfe/postcss.config.cjs | 2 +- geradoresfe/src/Api/GeradorApi.tsx | 10 +- geradoresfe/src/App.tsx | 12 +- .../src/components/Geradores/GeradorCard.tsx | 208 +++++++------ .../components/Geradores/Tipos/GenerateCC.tsx | 29 ++ .../Geradores/Tipos/GenerateNIF.tsx | 61 ++-- .../Geradores/Tipos/GenerateNISS.tsx | 29 ++ geradoresfe/src/components/ui/Button.tsx | 31 +- geradoresfe/src/components/ui/ButtonGroup.tsx | 2 +- .../src/components/ui/DarkModeToggle.tsx | 27 ++ .../src/components/ui/ResultDisplay.tsx | 30 +- geradoresfe/src/context/ThemeContext.tsx | 80 +++-- geradoresfe/src/index.css | 4 +- geradoresfe/src/layout/Footer.tsx | 26 +- geradoresfe/src/layout/Header.tsx | 284 ++++++++++-------- geradoresfe/src/layout/Layout.tsx | 8 +- geradoresfe/src/main.tsx | 9 +- geradoresfe/src/pages/GenerateCC.tsx | 35 --- geradoresfe/src/pages/GenerateNISS.tsx | 34 --- geradoresfe/src/pages/Home.tsx | 14 +- geradoresfe/vite.config.ts | 1 - 24 files changed, 519 insertions(+), 425 deletions(-) create mode 100644 geradoresfe/src/components/Geradores/Tipos/GenerateCC.tsx create mode 100644 geradoresfe/src/components/Geradores/Tipos/GenerateNISS.tsx create mode 100644 geradoresfe/src/components/ui/DarkModeToggle.tsx delete mode 100644 geradoresfe/src/pages/GenerateCC.tsx delete mode 100644 geradoresfe/src/pages/GenerateNISS.tsx diff --git a/README.md b/README.md index 6d3bb3d..22efff8 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# Geradores \ No newline at end of file +# Geradores + + docker build -t shini89/geradoresws .. + docker run --rm -p 44329:8080 shini89/geradoresws diff --git a/geradoresfe/.env b/geradoresfe/.env index e88a86e..386a66c 100644 --- a/geradoresfe/.env +++ b/geradoresfe/.env @@ -1 +1 @@ -VITE_API_URL=https://localhost:44329/ \ No newline at end of file +VITE_API_URL=http://localhost:44329/ \ No newline at end of file diff --git a/geradoresfe/geradoresfe.esproj b/geradoresfe/geradoresfe.esproj index c071f8c..273064d 100644 --- a/geradoresfe/geradoresfe.esproj +++ b/geradoresfe/geradoresfe.esproj @@ -19,5 +19,6 @@ + \ No newline at end of file diff --git a/geradoresfe/postcss.config.cjs b/geradoresfe/postcss.config.cjs index fcb25d2..8970ae1 100644 --- a/geradoresfe/postcss.config.cjs +++ b/geradoresfe/postcss.config.cjs @@ -3,4 +3,4 @@ module.exports = { require('@tailwindcss/postcss'), require('autoprefixer'), ], -} \ No newline at end of file +} diff --git a/geradoresfe/src/Api/GeradorApi.tsx b/geradoresfe/src/Api/GeradorApi.tsx index 5229300..4dd910c 100644 --- a/geradoresfe/src/Api/GeradorApi.tsx +++ b/geradoresfe/src/Api/GeradorApi.tsx @@ -1,6 +1,7 @@ import axios from 'axios'; import type { AxiosResponse } from 'axios'; import { NIFType } from '../service/api'; +import { toast } from 'react-hot-toast'; const API_URL = import.meta.env.VITE_API_URL; @@ -15,21 +16,24 @@ class GeradorService { }); return response.data; } catch (error) { - console.error('Error fetching NIF:', error); + + toast.error('Error fetching NIF:' + error.message); return []; } } - static async ValidateNIF(type: string | null): Promise { + static async ValidateNIF(nif: string | null): Promise { try { const response: AxiosResponse = await axios.get(API_URL + 'Generate/ValidateNIF', { params: { - type: type + nif: nif } }); return response.data; } catch (error) { + toast.error('Error fetching NIF:' + error); + console.error('Error fetching NIF:', error); return []; } diff --git a/geradoresfe/src/App.tsx b/geradoresfe/src/App.tsx index 1dbe89d..0310a4a 100644 --- a/geradoresfe/src/App.tsx +++ b/geradoresfe/src/App.tsx @@ -2,8 +2,11 @@ import { Routes, Route, BrowserRouter as Router } from 'react-router-dom'; import { Toaster } from 'react-hot-toast'; import Home from './pages/Home'; import { Layout } from './layout/Layout'; +import { useTheme } from './context/ThemeContext'; function App() { + const { theme } = useTheme(); + return ( @@ -13,7 +16,14 @@ function App() { - + ); diff --git a/geradoresfe/src/components/Geradores/GeradorCard.tsx b/geradoresfe/src/components/Geradores/GeradorCard.tsx index ccf6d58..d3bf3e9 100644 --- a/geradoresfe/src/components/Geradores/GeradorCard.tsx +++ b/geradoresfe/src/components/Geradores/GeradorCard.tsx @@ -1,111 +1,139 @@ -import React, { useState } from "react"; +import React, { useState } from "react"; import { RefreshCwIcon, CopyIcon } from "lucide-react"; import { Button } from "../ui/Button"; import { Input } from "../ui/Input"; -import { Select } from "../ui/Select"; // Import do Select moderno +import { Select } from "../ui/Select"; import { ResultDisplay } from "../ui/ResultDisplay"; -import { ButtonGroup } from "../ui/ButtonGroup"; +import { toast } from 'react-hot-toast'; -type Parametro = - | { nome: string; tipo: "text" | "number" | "password"; placeholder?: string } - | { nome: string; tipo: "select"; options: { label: string; value: string }[] }; +type Parametro = + | { nome: string; tipo: "text" | "number" | "password"; placeholder?: string } + | { nome: string; tipo: "select"; options: { label: string; value: string }[] }; type GeradorCardProps = { - titulo: string; - descricao?: string; - parametros?: Parametro[]; - onGerar: (parametros?: Record) => string | Promise; - onValidar?: (valor: string) => boolean | Promise; + readonly titulo: string; + readonly descricao?: string; + readonly parametros?: Parametro[]; + readonly onGerar: (parametros?: Record) => string | Promise; + readonly onValidar?: (valor: string) => boolean | Promise; }; export default function GeradorCard({ - titulo, - descricao, - parametros = [], - onGerar, - onValidar, + titulo, + descricao, + parametros = [], + onGerar, + onValidar, }: GeradorCardProps) { - const [resultado, setResultado] = useState(""); - const [paramInputs, setParamInputs] = useState>({}); - const [validado, setValidado] = useState(null); + const [resultado, setResultado] = useState(""); + const [paramInputs, setParamInputs] = useState>({}); + const [validado, setValidado] = useState(null); - const handleInputChange = (nome: string, valor: string) => { - setParamInputs((prev) => ({ ...prev, [nome]: valor })); - }; + const handleInputChange = (nome: string, valor: string) => { + setParamInputs((prev) => ({ ...prev, [nome]: valor })); + }; - const handleGerar = async () => { - const gerado = await onGerar(paramInputs); - setResultado(gerado); - setValidado(null); - }; + const handleGerar = async () => { + const gerado = await onGerar(paramInputs); + setResultado(gerado); + setValidado(null); + }; - const handleValidar = async () => { - if (onValidar) { - const isValid = await onValidar(resultado); - setValidado(isValid); - } - }; + const handleValidar = async () => { + if (onValidar) { + const isValid = await onValidar(resultado); + setValidado(isValid); + } + }; - const handleCopiar = () => { - navigator.clipboard.writeText(resultado); - }; + const handleCopiar = () => { + if (!resultado) return; + navigator.clipboard.writeText(resultado); + toast.success(`${titulo} copiado!`, + { + duration: 3000 + }); + }; - return ( -
-
-

{titulo}

- {descricao &&

{descricao}

} -
+ const handleResultadoChange = (valor: string) => { + setResultado(valor); + setValidado(null); // resetar estado de validação ao editar + }; - {parametros.length > 0 && ( -
- {parametros.map((param) => { - if (param.tipo === "select") { - return ( - handleInputChange(param.nome, val)} + placeholder={`Selecionar ${param.nome}`} + /> + ); + } + + return ( + handleInputChange(param.nome, e.target.value)} + /> + ); + })} +
+ )} + + {/* Resultado + botão de copiar como ícone */} +
+ - ); - } + {resultado && ( + + )} +
- // Input normal (text, number, password) - return ( - handleInputChange(param.nome, e.target.value)} - /> - ); - })} + {/* Botões distribuídos numa linha com preenchimento igual */} +
+ + + {onValidar && ( + + )} +
- )} - -
- -
- - - - - - - {onValidar && ( - - )} - - - ); + ); } diff --git a/geradoresfe/src/components/Geradores/Tipos/GenerateCC.tsx b/geradoresfe/src/components/Geradores/Tipos/GenerateCC.tsx new file mode 100644 index 0000000..2074cad --- /dev/null +++ b/geradoresfe/src/components/Geradores/Tipos/GenerateCC.tsx @@ -0,0 +1,29 @@ +import GeradorService from "../../../Api/GeradorApi.tsx"; +import GeradorCard from "../GeradorCard.tsx"; + +const GenerateCC = () => { + + const handleGenerateCC = async () => { + const cc = await GeradorService.GenerateCC() + return cc; + }; + + const handleValidateCC = async (valor: string) => { + // Lógica real de validação + //const bol = await GeradorService.ValidateCC(valor); + return true; + }; + + return ( +
+ +
+ ); +}; + +export default GenerateCC; + diff --git a/geradoresfe/src/components/Geradores/Tipos/GenerateNIF.tsx b/geradoresfe/src/components/Geradores/Tipos/GenerateNIF.tsx index 00b7bc4..d036215 100644 --- a/geradoresfe/src/components/Geradores/Tipos/GenerateNIF.tsx +++ b/geradoresfe/src/components/Geradores/Tipos/GenerateNIF.tsx @@ -2,43 +2,44 @@ import React from "react"; import GeradorCard from "../GeradorCard"; import { EnumToOptions } from "../../../library/utils"; import { NIFType } from "../../../service/api"; +import GeradorService from "../../../Api/GeradorApi"; const NifTypes = EnumToOptions(NIFType).map((opt) => ({ - label: opt.label, - value: opt.value, + label: opt.label, + value: opt.value, })); const GenerateNIF = () => { - const handleGenerateNIF = async (params: Record) => { - const tipoSelecionado = params["Tipo de NIF"]; - // Aqui chamarias a tua API real, exemplo: - // const nif = await GeradorService.GenerateNIF({ type: tipoSelecionado }); - // Para simular: - return `NIF-${tipoSelecionado}-${Math.floor(Math.random() * 1000000)}`; - }; - const handleValidateNIF = async (valor: string) => { - // Lógica real de validação - return valor.startsWith("NIF-"); - }; + const handleGenerateNIF = async (params: Record) => { + const tipoSelecionado = params["Tipo de NIF"]; + const nif = await GeradorService.GenerateNIF(tipoSelecionado); - return ( -
- -
- ); + return nif; + }; + + const handleValidateNIF = async (valor: string) => { + // Lógica real de validação + const bol = await GeradorService.ValidateNIF(valor); + return bol; + }; + + return ( +
+ +
+ ); }; export default GenerateNIF; diff --git a/geradoresfe/src/components/Geradores/Tipos/GenerateNISS.tsx b/geradoresfe/src/components/Geradores/Tipos/GenerateNISS.tsx new file mode 100644 index 0000000..28a434f --- /dev/null +++ b/geradoresfe/src/components/Geradores/Tipos/GenerateNISS.tsx @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import GeradorService from "../../../Api/GeradorApi"; +import GeradorCard from "../GeradorCard.tsx"; + +const GenerateNISS = () => { + + const handleGenerateNISS = async () => { + const niss = await GeradorService.GenerateNISS() + return niss; + }; + + const handleValidateNISS = async (valor: string) => { + // Lógica real de validação + const bol = await GeradorService.ValidateNISS(valor); + return bol; + }; + + return ( +
+ +
+ ); +}; + +export default GenerateNISS; diff --git a/geradoresfe/src/components/ui/Button.tsx b/geradoresfe/src/components/ui/Button.tsx index 92104a9..b742540 100644 --- a/geradoresfe/src/components/ui/Button.tsx +++ b/geradoresfe/src/components/ui/Button.tsx @@ -7,6 +7,9 @@ type ButtonProps = React.ButtonHTMLAttributes & { icon?: ReactNode; variant?: "primary" | "secondary" | "ghost" | "danger"; loading?: boolean; + loadingText?: string; + fullWidth?: boolean; + rounded?: boolean | "md" | "lg" | "xl" | "2xl" | "full"; }; export function Button({ @@ -16,19 +19,27 @@ export function Button({ className = "", type = "button", loading = false, + loadingText, disabled, + fullWidth = false, + rounded = "2xl", ...rest }: ButtonProps) { - const base = - "inline-flex items-center justify-center gap-2 rounded-2xl px-5 py-2.5 font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md"; + const base = twMerge( + "inline-flex items-center justify-center gap-2 px-5 py-2.5 font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md", + fullWidth && "w-full", + typeof rounded === "string" ? `rounded-${rounded}` : rounded === true ? "rounded-md" : "rounded-2xl" + ); const variants = { - primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500", + primary: + "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 dark:bg-blue-600 dark:hover:bg-blue-500", secondary: - "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600 focus:ring-zinc-400", + "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 focus:ring-zinc-400 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600", ghost: - "bg-transparent text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-800 focus:ring-zinc-300", - danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500", + "bg-transparent text-zinc-700 hover:bg-zinc-100 focus:ring-zinc-300 dark:text-zinc-200 dark:hover:bg-zinc-800", + danger: + "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 dark:bg-red-600 dark:hover:bg-red-500", }; return ( @@ -36,13 +47,17 @@ export function Button({ type={type} className={twMerge(base, variants[variant], className)} disabled={disabled || loading} + aria-busy={loading} {...rest} > {loading ? ( - + <> + + {loadingText && {loadingText}} + ) : ( <> - {icon && {icon}} + {icon && {icon}} {children} )} diff --git a/geradoresfe/src/components/ui/ButtonGroup.tsx b/geradoresfe/src/components/ui/ButtonGroup.tsx index 2a36191..bad5a18 100644 --- a/geradoresfe/src/components/ui/ButtonGroup.tsx +++ b/geradoresfe/src/components/ui/ButtonGroup.tsx @@ -1,5 +1,5 @@ import { ReactNode } from "react"; export function ButtonGroup({ children }: { children: ReactNode }) { - return
{children}
; + return
{children}
; } diff --git a/geradoresfe/src/components/ui/DarkModeToggle.tsx b/geradoresfe/src/components/ui/DarkModeToggle.tsx new file mode 100644 index 0000000..0a57f5d --- /dev/null +++ b/geradoresfe/src/components/ui/DarkModeToggle.tsx @@ -0,0 +1,27 @@ +import { SunIcon, MoonIcon } from "@heroicons/react/24/outline"; +import { useTheme } from "../../context/ThemeContext"; + +export function DarkModeToggle() { + const { theme, toggleTheme } = useTheme(); + + const handleToggle = () => { + console.log("Toggle clicado, tema antes:", theme); + toggleTheme(); + }; + + console.log("Render DarkModeToggle, theme =", theme); + + return ( + + ); +} diff --git a/geradoresfe/src/components/ui/ResultDisplay.tsx b/geradoresfe/src/components/ui/ResultDisplay.tsx index 25b84de..b2da59b 100644 --- a/geradoresfe/src/components/ui/ResultDisplay.tsx +++ b/geradoresfe/src/components/ui/ResultDisplay.tsx @@ -3,29 +3,27 @@ import { CheckIcon, XIcon } from "lucide-react"; type ResultDisplayProps = { value: string; validated: boolean | null; + onChange?: (value: string) => void; }; -export function ResultDisplay({ value, validated }: ResultDisplayProps) { - const baseClasses = "flex items-center gap-2 px-3 py-2 border rounded-md text-sm "; +export function ResultDisplay({ value, validated, onChange }: ResultDisplayProps) { + const baseClasses = "flex items-center gap-2 px-3 py-2 border rounded-md text-sm w-full"; const colors = validated === true - ? "border-green-500 text-green-700 bg-green-50" + ? "border-green-500 text-green-700 bg-green-50 dark:text-green-400 dark:bg-green-950 dark:border-green-700" : validated === false - ? "border-red-500 text-red-700 bg-red-50" - : "border-gray-300 text-gray-700 bg-gray-100"; + ? "border-red-500 text-red-700 bg-red-50 dark:text-red-400 dark:bg-red-950 dark:border-red-700" + : "border-gray-300 text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-zinc-800 dark:border-gray-600"; return ( -
- {validated !== null ? ( - - ) : ( - {value || "Resultado..."} - )} +
+ onChange?.(e.target.value)} + className="flex-1 bg-transparent outline-none font-mono truncate" + /> {validated === true && } {validated === false && }
diff --git a/geradoresfe/src/context/ThemeContext.tsx b/geradoresfe/src/context/ThemeContext.tsx index 1e6cc7d..fc515c5 100644 --- a/geradoresfe/src/context/ThemeContext.tsx +++ b/geradoresfe/src/context/ThemeContext.tsx @@ -1,58 +1,54 @@ -"use client"; - -import type React from "react"; -import { createContext, useState, useContext, useEffect } from "react"; +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; type Theme = "light" | "dark"; type ThemeContextType = { - theme: Theme; - toggleTheme: () => void; + theme: Theme; + toggleTheme: () => void; }; const ThemeContext = createContext(undefined); -export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ - children, -}) => { - const [theme, setTheme] = useState("light"); - const [isInitialized, setIsInitialized] = useState(false); +export const ThemeProvider = ({ children }: { children: ReactNode }) => { + const [theme, setTheme] = useState(null); // null = tema ainda não carregado - useEffect(() => { - // This code will only run on the client side - const savedTheme = localStorage.getItem("theme") as Theme | null; - const initialTheme = savedTheme || "light"; // Default to light theme + useEffect(() => { + const storedTheme = localStorage.getItem("theme") as Theme | null; + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const initialTheme = storedTheme ?? (prefersDark ? "dark" : "light"); + setTheme(initialTheme); + }, []); - setTheme(initialTheme); - setIsInitialized(true); - }, []); + useEffect(() => { + if (theme === null) return; // ainda a carregar - useEffect(() => { - if (isInitialized) { - localStorage.setItem("theme", theme); - if (theme === "dark") { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - } - }, [theme, isInitialized]); + const root = document.documentElement; + root.classList.remove("light", "dark"); + root.classList.add(theme); + localStorage.setItem("theme", theme); + }, [theme]); - const toggleTheme = () => { - setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light")); - }; + const toggleTheme = () => { + setTheme((prev) => { + const nextTheme = prev === "dark" ? "light" : "dark"; + console.log("ToggleTheme:", prev, "->", nextTheme); + return nextTheme; + }); + //setTheme((prev) => (prev === "dark" ? "light" : "dark")); + }; - return ( - - {children} - - ); + // Enquanto tema não carregou, não renderiza nada + if (theme === null) return null; + + return ( + + {children} + + ); }; -export const useTheme = () => { - const context = useContext(ThemeContext); - if (context === undefined) { - throw new Error("useTheme must be used within a ThemeProvider"); - } - return context; +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (!context) throw new Error("useTheme must be used within ThemeProvider"); + return context; }; diff --git a/geradoresfe/src/index.css b/geradoresfe/src/index.css index 599ab3d..0229963 100644 --- a/geradoresfe/src/index.css +++ b/geradoresfe/src/index.css @@ -1,5 +1,3 @@ @import "tailwindcss"; -@tailwind base; -@tailwind components; -@tailwind utilities; +@custom-variant dark (&:where(.dark, .dark *)); \ No newline at end of file diff --git a/geradoresfe/src/layout/Footer.tsx b/geradoresfe/src/layout/Footer.tsx index dcdf0b2..5acd0af 100644 --- a/geradoresfe/src/layout/Footer.tsx +++ b/geradoresfe/src/layout/Footer.tsx @@ -4,33 +4,33 @@ export default function Footer() { const year = new Date().getFullYear(); return ( -
+
{/* Marca e descrição */}
-

FactoryiD

-

+

FactoryiD

+

Geradores inteligentes para profissionais modernos. Soluções simples, rápidas e acessíveis.

{/* Navegação principal */}
-

Navegação

+

Navegação

  • - + Início
  • - + Geradores
  • - + Contacto
  • @@ -39,15 +39,15 @@ export default function Footer() { {/* Outros links */}
    -

    Outros

    +

    Outros

    • - + Termos de Utilização
    • - + Política de Privacidade
    • @@ -56,13 +56,13 @@ export default function Footer() {
    {/* Separador */} -
    +
    {/* Copyright */} -
    +
    © {year} FactoryiD. Todos os direitos reservados.
); -} \ No newline at end of file +} diff --git a/geradoresfe/src/layout/Header.tsx b/geradoresfe/src/layout/Header.tsx index 7917f8b..ca418ff 100644 --- a/geradoresfe/src/layout/Header.tsx +++ b/geradoresfe/src/layout/Header.tsx @@ -2,150 +2,170 @@ import { useEffect, useState } from "react"; import { Link, NavLink, useLocation } from "react-router"; import { Disclosure, Menu, Transition } from "@headlessui/react"; import { Bars3Icon, XMarkIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; - import Logo from "../assets/logotipo32x32.png"; +import { DarkModeToggle } from "../components/ui/DarkModeToggle"; const navItems = [ - { name: "Início", path: "/" }, - { - name: "Geradores", - subItems: [ - { name: "Gerador de Senhas", path: "/geradores/senhas" }, - { name: "Gerador de Nomes", path: "/geradores/nomes" }, - ], - }, - { name: "Contacto", path: "/contacto" }, + { name: "Início", path: "/" }, + { + name: "Geradores", + subItems: [ + { name: "Gerador de Senhas", path: "/geradores/senhas" }, + { name: "Gerador de Nomes", path: "/geradores/nomes" }, + ], + }, + { name: "Contacto", path: "/contacto" }, ]; + function Header() { - const location = useLocation(); - const [isScrolled, setIsScrolled] = useState(false); + const location = useLocation(); + const [isScrolled, setIsScrolled] = useState(false); - // Header shrink on scroll - useEffect(() => { - const onScroll = () => { - setIsScrolled(window.scrollY > 10); - }; - window.addEventListener("scroll", onScroll); - return () => window.removeEventListener("scroll", onScroll); - }, []); + useEffect(() => { + const onScroll = () => setIsScrolled(window.scrollY > 10); + window.addEventListener("scroll", onScroll); + return () => window.removeEventListener("scroll", onScroll); + }, []); - const navLinkClass = (isActive: boolean) => - `px-3 py-2 rounded-md text-sm font-medium transition ${isActive ? "text-blue-600 font-semibold" : "text-gray-700 hover:text-blue-500" - }`; + const navLinkClass = (isActive: boolean) => + `px-3 py-2 rounded-md text-sm font-medium transition ${isActive + ? "text-blue-600 font-semibold" + : "text-gray-700 hover:text-blue-500 dark:text-gray-300 dark:hover:text-blue-400" + }`; - return ( - - {({ open }) => ( -
-
- {/* LOGO */} - - Logo - Factory Id - + return ( + + {({ open }) => ( +
+
+ {/* LOGO */} + + Logo + + Factory Id + + - {/* Desktop Nav */} -
- {navItems.map((item) => - item.subItems ? ( - - - {item.name} - - - - -
- {item.subItems.map((sub) => ( - - {({ active }) => ( - - {sub.name} - - )} - - ))} -
-
-
-
- ) : ( - navLinkClass(isActive)} - > - {item.name} - - ) - )} + {/* Desktop Nav */} +
+ {navItems.map((item) => + item.subItems ? ( + + + {item.name} + + + + +
+ {item.subItems.map((sub) => ( + + {({ active }) => ( + + {sub.name} + + )} + + ))}
+
+
+
+ ) : ( + navLinkClass(isActive)} + > + {item.name} + + ) + )} + +
- {/* Mobile menu button */} -
- - {open ? : } - -
+ {/* Mobile menu button */} +
+ + {open ? ( + + ) : ( + + )} + +
+
+ + {/* Mobile Menu Panel */} + +
+ {navItems.map((item) => + item.subItems ? ( +
+ + {item.name} + +
+ {item.subItems.map((sub) => ( + + `block text-sm px-3 py-1 rounded ${ + isActive + ? "text-blue-600 font-semibold" + : "text-gray-700 dark:text-gray-300" + }` + } + > + {sub.name} + + ))}
- - {/* Mobile Menu Panel */} - -
- {navItems.map((item) => - item.subItems ? ( -
- - {item.name} - -
- {item.subItems.map((sub) => ( - - `block text-sm px-3 py-1 rounded ${isActive ? "text-blue-600 font-semibold" : "text-gray-700" - }` - } - > - {sub.name} - - ))} -
-
- ) : ( - - `block text-sm px-3 py-2 rounded-md ${isActive ? "text-blue-600 font-semibold" : "text-gray-700" - }` - } - > - {item.name} - - ) - )} -
-
-
- )} -
- ); +
+ ) : ( + + `block text-sm px-3 py-2 rounded-md ${ + isActive + ? "text-blue-600 font-semibold" + : "text-gray-700 dark:text-gray-300" + }` + } + > + {item.name} + + ) + )} +
+ +
+
+ + + )} + + ); } export default Header; diff --git a/geradoresfe/src/layout/Layout.tsx b/geradoresfe/src/layout/Layout.tsx index b1a9711..2fcc986 100644 --- a/geradoresfe/src/layout/Layout.tsx +++ b/geradoresfe/src/layout/Layout.tsx @@ -1,12 +1,14 @@ // components/layout/Layout.tsx import Footer from './Footer'; import Header from './Header'; +import { useTheme } from "../context/ThemeContext"; // caminho conforme a tua estrutura export const Layout = ({ children }: { children: React.ReactNode }) => { + return ( -
-
-
+
+
+
{children}