Bankbot / frontend /src /components /layout /DashboardLayout.tsx
mohsin-devs's picture
feat: improved light mode + language sync to html lang attribute
7782325
Raw
History Blame Contribute Delete
5.15 kB
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { usePathname } from "next/navigation";
import { Sidebar } from "./Sidebar";
import { Navbar } from "./Navbar";
import { Menu } from "lucide-react";
import { useThemeStore } from "@/lib/stores/themeStore";
// ─── Page transition wrapper ──────────────────────────────────────────────────
function PageTransition({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
return (
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.25, ease: "easeOut" }}
className="h-full"
>
{children}
</motion.div>
</AnimatePresence>
);
}
export function DashboardLayout({ children }: { children: React.ReactNode }) {
const [mobileOpen, setMobileOpen] = useState(false);
const { theme } = useThemeStore();
const isLight = theme === "light";
return (
<div
className="flex h-screen overflow-hidden transition-colors duration-300"
style={{ background: "var(--bg)", color: "var(--fg)" }}
>
{/* Ambient background gradients */}
<div className="fixed inset-0 z-0 overflow-hidden pointer-events-none">
<div
className="absolute top-[-15%] left-[-5%] w-[45%] h-[45%] rounded-full blur-[140px] transition-opacity duration-300"
style={{ background: "var(--ambient-1)" }}
/>
<div
className="absolute bottom-[-15%] right-[-5%] w-[45%] h-[45%] rounded-full blur-[140px] transition-opacity duration-300"
style={{ background: "var(--ambient-2)" }}
/>
<div
className="absolute top-[40%] left-[40%] w-[30%] h-[30%] rounded-full blur-[120px] transition-opacity duration-300"
style={{ background: "var(--ambient-3)" }}
/>
{/* Noise texture β€” only visible in dark mode */}
{!isLight && (
<div
className="absolute inset-0 opacity-[0.015]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
backgroundRepeat: "repeat",
backgroundSize: "128px 128px",
}}
/>
)}
</div>
{/* ── Desktop Sidebar ─────────────────────────────────────────────────── */}
<div className="z-10 hidden lg:flex h-full flex-shrink-0">
<Sidebar />
</div>
{/* ── Mobile Sidebar Overlay ───────────────────────────────────────────── */}
<AnimatePresence>
{mobileOpen && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setMobileOpen(false)}
className={`fixed inset-0 z-30 backdrop-blur-sm lg:hidden ${isLight ? "bg-slate-900/30" : "bg-black/60"}`}
/>
<motion.div
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "spring", damping: 30, stiffness: 300 }}
className="fixed left-0 top-0 z-40 h-full lg:hidden"
>
<Sidebar />
</motion.div>
</>
)}
</AnimatePresence>
{/* ── Main content ─────────────────────────────────────────────────────── */}
<div className="z-10 flex flex-1 flex-col overflow-hidden min-w-0">
{/* Mobile header with hamburger */}
<div
className="flex items-center lg:hidden border-b backdrop-blur-xl px-4 py-3"
style={{ background: "var(--navbar-bg)", borderColor: "var(--border)" }}
>
<button
onClick={() => setMobileOpen(true)}
className={`rounded-xl p-2 mr-3 transition-colors ${
isLight
? "bg-slate-100 text-slate-500 hover:text-slate-800"
: "bg-white/5 text-zinc-400 hover:text-white"
}`}
>
<Menu className="h-5 w-5" />
</button>
<span className="text-base font-bold gradient-text">BankBot</span>
</div>
<Navbar />
<main className="flex-1 overflow-y-auto p-4 sm:p-6 lg:p-8">
<PageTransition>{children}</PageTransition>
</main>
</div>
</div>
);
}