All files / src/common/components/layout Layout.tsx

82.45% Statements 47/57
25% Branches 1/4
100% Functions 1/1
82.45% Lines 47/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 791x 1x 1x 1x 1x 1x 1x           1x 6x 6x 6x   6x   6x     6x   6x 6x   6x 6x   6x 6x                     6x     6x 6x 6x 6x 6x       6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x   6x 6x 6x   6x 6x 6x   6x   1x  
import { useAuth } from '@common/contexts/AuthContext';
import ScrollToTop from "@web/components/ScrollToTop";
import { AnimatePresence, motion } from "framer-motion";
import { ReactNode, useCallback, useState } from "react";
import { Outlet, useLocation } from "react-router-dom";
import Header from "./Header";
import Sidebar from "./Sidebar";
 
interface LayoutProps {
  children?: ReactNode;
}
 
const Layout: React.FC<LayoutProps> = ({ children }) => {
  const { user, loading } = useAuth();
  const location = useLocation();
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
 
  const toggleSidebar = useCallback(() => {
    setIsSidebarOpen((prev) => !prev);
  }, []);
 
  // Don't show sidebar on login page or if user is not authenticated
  const showSidebar = user && !loading && !location.pathname.startsWith('/login');
 
  return (
    <div className="flex h-screen overflow-hidden bg-gradient-to-br from-slate-50 to-blue-50/30 dark:from-neutral-950 dark:to-neutral-900/40 dark:bg-neutral-950">
      {/* Sidebar - Always render, but only toggle on mobile */}
      {showSidebar && (
        <>
          {/* Mobile backdrop - darker and more blurred, disables background interaction */}
          <AnimatePresence>
            {isSidebarOpen && (
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.2 }}
                className="fixed inset-0 bg-black/40 backdrop-blur-sm z-40 lg:hidden pointer-events-auto"
                onClick={toggleSidebar}
                aria-hidden="true"
              />
            )}
          </AnimatePresence>
 
          {/* Sidebar component */}
          <Sidebar
            isOpen={isSidebarOpen}
            onToggle={toggleSidebar}
          />
        </>
      )}
 
      {/* Main content area */}
      <div className={`flex flex-col flex-1 overflow-hidden ${isSidebarOpen ? 'pointer-events-none lg:pointer-events-auto' : ''}`}>
        {user && <Header onMenuToggle={toggleSidebar} />}
        <main className="flex-1 overflow-y-auto px-4 py-4 md:px-6 md:py-6">
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -10 }}
            transition={{
              duration: 0.4,
              ease: [0.4, 0, 0.2, 1],
              delay: 0.1,
            }}
            className="max-w-7xl mx-auto space-y-4 md:space-y-6"
          >
            {children || <Outlet />}
          </motion.div>
        </main>
        {/* Global Scroll To Top button */}
        <ScrollToTop showThreshold={150} targetSelector="main.overflow-y-auto" />
      </div>
    </div>
  );
};
 
export default Layout;