From 9e70772838865daa993e761beb0aa7380bf90c42 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:11:00 +0330 Subject: [PATCH] Import source: src/components/shell/sidebar Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- src/components/shell/sidebar.tsx | 254 +++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 src/components/shell/sidebar.tsx diff --git a/src/components/shell/sidebar.tsx b/src/components/shell/sidebar.tsx new file mode 100644 index 0000000..ebc7d78 --- /dev/null +++ b/src/components/shell/sidebar.tsx @@ -0,0 +1,254 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { ChevronDown, Menu, Search, Star, X } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Logo } from "@/components/common/logo"; +import { cn } from "@/lib/utils"; +import { groupRoutes, routeItems, sidebarRoutes } from "@/lib/routes"; +import { useUiStore } from "@/stores/ui-store"; + +export function Sidebar() { + const collapsed = useUiStore((state) => state.sidebarCollapsed); + const toggleSidebar = useUiStore((state) => state.toggleSidebar); + + return ( + + ); +} + +export function MobileSidebar() { + const open = useUiStore((state) => state.mobileSidebarOpen); + const setOpen = useUiStore((state) => state.setMobileSidebarOpen); + const pathname = usePathname(); + + useEffect(() => { + setOpen(false); + }, [pathname, setOpen]); + + useEffect(() => { + if (!open) return; + document.body.style.overflow = "hidden"; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") setOpen(false); + }; + window.addEventListener("keydown", onKeyDown); + return () => { + document.body.style.overflow = ""; + window.removeEventListener("keydown", onKeyDown); + }; + }, [open, setOpen]); + + return ( +
+
setOpen(false)} + /> + +
+ ); +} + +function SidebarBody({ + collapsed, + headerAction, + onNavigate, +}: { + collapsed: boolean; + headerAction: React.ReactNode; + onNavigate?: () => void; +}) { + const pathname = usePathname(); + const favorites = useUiStore((state) => state.favorites); + const recent = useUiStore((state) => state.recent); + const [filter, setFilter] = useState(""); + + const favoritesRoutes = routeItems.filter((item) => favorites.includes(item.path)); + const recentRoutes = routeItems.filter((item) => recent.includes(item.path)); + const visible = useMemo(() => { + if (!filter) return sidebarRoutes; + const term = filter.toLowerCase(); + return routeItems.filter((item) => + [item.label, item.category, item.description, ...item.keywords].some((value) => + value.toLowerCase().includes(term), + ), + ); + }, [filter]); + const groups = groupRoutes(visible); + + return ( + <> +
+ + + + {headerAction} +
+ {!collapsed ? ( +
+
+ + setFilter(event.target.value)} + placeholder="Find tools" + className="h-8 pl-8" + /> +
+
+ ) : null} +
+ {!collapsed && !filter ? ( + + ) : null} + {!collapsed && !filter ? ( + + ) : null} + {Object.entries(groups).map(([title, routes]) => ( + + ))} +
+ + ); +} + +function SidebarSection({ + title, + routes, + pathname, + collapsed, + icon, + onNavigate, +}: { + title: string; + routes: typeof routeItems; + pathname: string; + collapsed: boolean; + icon?: "star"; + onNavigate?: () => void; +}) { + if (!routes.length) return null; + return ( +
+ {!collapsed ? ( +
+ {icon === "star" ? ( + + ) : ( + + )} + {title} +
+ ) : null} +
+ {routes.map((route) => { + const Icon = route.icon; + const active = + pathname === route.path || + (route.path !== "/dashboard" && pathname.startsWith(`${route.path}/`)); + return ( + + {active ? ( + + ) : null} + + {!collapsed ? {route.label} : null} + + ); + })} +
+
+ ); +}