From 4201e18ae99042d3c6d1dce9a3a091b86a0e13e9 Mon Sep 17 00:00:00 2001 From: elstua Date: Wed, 4 Mar 2026 18:54:00 +0000 Subject: [PATCH 1/2] add sidebar navigation and update layout Replace header with a persistent sidebar component, restructure the main layout to use sidebar + content in a centered container, and update mobile drawer positioning. Made-with: Cursor --- apps/web/src/components/sidebar.tsx | 215 ++++++++++++++++++++++++++++ apps/web/src/routes/_view/index.tsx | 2 +- apps/web/src/routes/_view/route.tsx | 26 ++-- 3 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 apps/web/src/components/sidebar.tsx diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx new file mode 100644 index 0000000000..ef62c88477 --- /dev/null +++ b/apps/web/src/components/sidebar.tsx @@ -0,0 +1,215 @@ +import { Link, useRouterState } from "@tanstack/react-router"; +import { Menu, X } from "lucide-react"; +import { useEffect, useState } from "react"; + +import { cn } from "@hypr/utils"; + +import { SearchTrigger } from "@/components/search"; +import { getPlatformCTA, usePlatform } from "@/hooks/use-platform"; + +function CharLogo({ className }: { className?: string }) { + return ( + + + + + + + + + ); +} + +const navLinks = [ + { to: "/why-char/", label: "Why Char" }, + { to: "/product/ai-notetaking", label: "Product" }, + { to: "/docs/", label: "Resources" }, + { to: "/pricing/", label: "Pricing" }, +]; + +export function Sidebar() { + const [isMobileOpen, setIsMobileOpen] = useState(false); + const router = useRouterState(); + const platform = usePlatform(); + const platformCTA = getPlatformCTA(platform); + const pathname = router.location.pathname; + + useEffect(() => { + setIsMobileOpen(false); + }, [pathname]); + + useEffect(() => { + if (isMobileOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + return () => { + document.body.style.overflow = ""; + }; + }, [isMobileOpen]); + + return ( + <> + + + {isMobileOpen && ( +
setIsMobileOpen(false)} + /> + )} + + + + {/* Mobile slide-out sidebar */} + + + ); +} + +function MobileTopBar({ + isMobileOpen, + setIsMobileOpen, +}: { + isMobileOpen: boolean; + setIsMobileOpen: (open: boolean) => void; +}) { + return ( +
+ + + + +
+ ); +} + +function SidebarCTA({ + platformCTA, +}: { + platformCTA: ReturnType; +}) { + const baseClass = + "flex h-9 items-center justify-center rounded-lg bg-neutral-800 text-sm text-neutral-300 transition-colors hover:bg-neutral-700 hover:text-neutral-100"; + + if (platformCTA.action === "download") { + return ( + + {platformCTA.label} + + ); + } + + return ( + + {platformCTA.label} + + ); +} diff --git a/apps/web/src/routes/_view/index.tsx b/apps/web/src/routes/_view/index.tsx index 28e825d3c0..60d71fab08 100644 --- a/apps/web/src/routes/_view/index.tsx +++ b/apps/web/src/routes/_view/index.tsx @@ -116,7 +116,7 @@ function Component() { return (
diff --git a/apps/web/src/routes/_view/route.tsx b/apps/web/src/routes/_view/route.tsx index 549a6ae1be..7bd00f72d1 100644 --- a/apps/web/src/routes/_view/route.tsx +++ b/apps/web/src/routes/_view/route.tsx @@ -8,9 +8,9 @@ import { allHandbooks } from "content-collections"; import { useCallback, useMemo, useRef, useState } from "react"; import { Footer } from "@/components/footer"; -import { Header } from "@/components/header"; import { NotFoundContent } from "@/components/not-found"; import { SearchPaletteProvider } from "@/components/search"; +import { Sidebar } from "@/components/sidebar"; import { SidebarNavigation } from "@/components/sidebar-navigation"; import { BlogTocContext } from "@/hooks/use-blog-toc"; import { DocsDrawerContext } from "@/hooks/use-docs-drawer"; @@ -76,11 +76,19 @@ function Component() { }} >
- {!isChoosePage &&
} -
- -
+ {/* Mobile top bar spacer */} +
+ + {/* Sidebar + content in a centered container */} +
+ {!isChoosePage && } +
+ +
+
+ {!isChoosePage &&