feat: responsive landing page
This commit is contained in:
@@ -6,6 +6,14 @@ import Button from "~/components/Button";
|
||||
|
||||
const Header = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const menuItems = [
|
||||
{ label: "Roadmap", href: "/kan/roadmap", openInNewTab: true },
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "Pricing", href: "#pricing" },
|
||||
{ label: "Docs", href: "https://docs.kanbn.com", openInNewTab: true },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -15,42 +23,72 @@ const Header = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const toggleMenu = () => {
|
||||
setIsMenuOpen(!isMenuOpen);
|
||||
document.body.style.overflow = !isMenuOpen ? "hidden" : "";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-50 m-auto flex w-full max-w-[1100px] px-4 pt-4",
|
||||
isScrolled && "fixed",
|
||||
"fixed z-50 m-auto flex w-full transition-all duration-500 lg:max-w-[1100px] lg:px-4 lg:pt-4",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"m-auto flex h-[4rem] min-h-[4rem] w-full rounded-3xl border border-transparent px-5 py-2 align-middle transition-colors duration-200",
|
||||
"m-auto flex h-[4rem] min-h-[4rem] w-full px-5 py-2 align-middle transition-all duration-500 lg:rounded-3xl lg:border lg:border-transparent",
|
||||
isScrolled &&
|
||||
"rounded-2xl border border-light-300 bg-dark-100 bg-light-50/80 shadow-sm backdrop-blur-[10px] dark:border-dark-300 dark:bg-dark-50/90",
|
||||
"border-b border-light-300 bg-light-50/80 opacity-100 shadow-sm backdrop-blur-[10px] dark:border-dark-300 dark:bg-dark-50/90 lg:rounded-2xl lg:border",
|
||||
!isScrolled && "bg-transparent opacity-90",
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between px-2">
|
||||
<div className="my-auto flex items-center justify-between pl-2">
|
||||
<div className="flex w-full items-center justify-between lg:px-4">
|
||||
<div className="my-auto flex items-center justify-between">
|
||||
<h1 className="w-[200px] text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000">
|
||||
kan.bn
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex justify-center gap-10 dark:text-dark-1000">
|
||||
<Link href="/kan/roadmap" className="text-sm font-bold">
|
||||
Roadmap
|
||||
</Link>
|
||||
<Link href="#features" className="text-sm font-bold">
|
||||
Features
|
||||
</Link>
|
||||
<Link href="#pricing" className="text-sm font-bold">
|
||||
Pricing
|
||||
</Link>
|
||||
<Link href="/docs" className="text-sm font-bold">
|
||||
Docs
|
||||
</Link>
|
||||
{/* Desktop Menu */}
|
||||
<div className="hidden justify-center gap-10 dark:text-dark-1000 lg:flex">
|
||||
{menuItems.map((item) => (
|
||||
<Link
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
target={item.openInNewTab ? "_blank" : undefined}
|
||||
rel={item.openInNewTab ? "noopener noreferrer" : undefined}
|
||||
className="text-sm font-bold"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex w-[200px] justify-end gap-2">
|
||||
{/* Hamburger Menu Button */}
|
||||
<button
|
||||
onClick={toggleMenu}
|
||||
className="z-50 p-2 lg:hidden"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"my-[5px] h-[1.5px] w-4 bg-current bg-light-1000 transition-all dark:bg-dark-1000",
|
||||
isMenuOpen ? "translate-y-[6.5px] rotate-45" : "",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={twMerge(
|
||||
"my-[5px] h-[1.5px] w-3 bg-current bg-light-1000 transition-all dark:bg-dark-1000",
|
||||
isMenuOpen ? "opacity-0" : "",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={twMerge(
|
||||
"my-[5px] h-[1.5px] w-4 bg-current bg-light-1000 transition-all dark:bg-dark-1000",
|
||||
isMenuOpen ? "-translate-y-[6.5px] -rotate-45" : "",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<div className="hidden w-[200px] justify-end gap-2 lg:flex">
|
||||
{isLoggedIn ? (
|
||||
<Button href="/boards">Go to app</Button>
|
||||
) : (
|
||||
@@ -65,7 +103,46 @@ const Header = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isScrolled && <div className="h-[5rem] min-h-[5rem]"></div>}
|
||||
{/* Mobile Menu Overlay */}
|
||||
<div
|
||||
className={`fixed inset-0 z-40 transform transition-all duration-500 lg:hidden ${
|
||||
isMenuOpen
|
||||
? "translate-x-0 opacity-100"
|
||||
: "pointer-events-none translate-x-0 opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-white dark:bg-dark-100">
|
||||
<div className="flex h-full flex-col items-center justify-center space-y-8">
|
||||
{menuItems.map((item) => (
|
||||
<Link
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
target={item.openInNewTab ? "_blank" : undefined}
|
||||
rel={item.openInNewTab ? "noopener noreferrer" : undefined}
|
||||
className="transform text-xl font-bold text-light-1000 transition-all duration-300 hover:scale-105 dark:text-dark-1000"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
{isLoggedIn ? (
|
||||
<Button href="/boards" onClick={toggleMenu}>
|
||||
Go to app
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button href="/login" variant="ghost" onClick={toggleMenu}>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button href="/signup" onClick={toggleMenu}>
|
||||
Get started
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user