From ddc8b2697b281e002d59249d454bdec95b30dde6 Mon Sep 17 00:00:00 2001 From: Harshit Date: Sun, 7 Dec 2025 22:43:29 +0530 Subject: [PATCH 1/2] Hoverable and expandend Sidebar UI --- apps/web/src/components/dashboard/Sidebar.tsx | 592 +++++++++++++++++- 1 file changed, 569 insertions(+), 23 deletions(-) diff --git a/apps/web/src/components/dashboard/Sidebar.tsx b/apps/web/src/components/dashboard/Sidebar.tsx index d27c636..c5cf9b6 100644 --- a/apps/web/src/components/dashboard/Sidebar.tsx +++ b/apps/web/src/components/dashboard/Sidebar.tsx @@ -1,3 +1,531 @@ +// "use client"; + +// import React, { useState, useEffect } from "react"; +// import Link from "next/link"; +// import SidebarItem from "../sidebar/SidebarItem"; +// import { useRouter, usePathname } from "next/navigation"; +// import { IconWrapper } from "../ui/IconWrapper"; +// import { motion, AnimatePresence } from "framer-motion"; +// import { +// XMarkIcon, +// HomeIcon, +// FolderIcon, +// ArrowRightOnRectangleIcon, +// SparklesIcon, +// StarIcon, +// DocumentTextIcon, +// Cog6ToothIcon, +// NewspaperIcon, +// Squares2X2Icon, +// ChevronDownIcon, +// LockClosedIcon, +// AcademicCapIcon +// } from "@heroicons/react/24/outline"; +// import { useShowSidebar } from "@/store/useShowSidebar"; +// import { signOut, useSession } from "next-auth/react"; +// import { ProfilePic } from "./ProfilePic"; +// import { useSubscription } from "@/hooks/useSubscription"; +// import { OpensoxProBadge } from "../sheet/OpensoxProBadge"; +// import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +// type RouteConfig = { +// path: string; +// label: string; +// icon: React.ReactNode; +// badge?: string; // optional badge text (e.g., "New", "Beta") +// }; + +// // free features only +// const FREE_ROUTES: RouteConfig[] = [ +// { +// path: "/dashboard/home", +// label: "Home", +// icon: , +// }, +// { +// path: "/dashboard/projects", +// label: "OSS Projects", +// icon: , +// }, +// { +// path: "/dashboard/sheet", +// label: "OSS Sheet", +// icon: , +// }, +// { +// path: "/dashboard/oss-programs", +// label: "OSS Programs", +// icon: , +// }, +// ]; + +// // premium features under Opensox Pro +// const PREMIUM_ROUTES: RouteConfig[] = [ +// { +// path: "/dashboard/pro/dashboard", +// label: "Dashboard", +// icon: , +// badge: "New", +// }, +// { +// path: "/dashboard/newsletters", +// label: "Newsletter", +// icon: , +// badge: "New", +// }, +// ]; + +// export default function Sidebar({ overlay = false }: { overlay?: boolean }) { +// const { setShowSidebar, isCollapsed, toggleCollapsed } = useShowSidebar(); +// const router = useRouter(); +// const pathname = usePathname(); +// const { isPaidUser } = useSubscription(); +// const [proSectionExpanded, setProSectionExpanded] = useState(true); + +// // auto-expand pro section if user is on a premium route +// useEffect(() => { +// if (isPaidUser) { +// const isOnPremiumRoute = PREMIUM_ROUTES.some((route) => { +// return pathname === route.path || pathname.startsWith(`${route.path}/`); +// }); +// if (isOnPremiumRoute) { +// setProSectionExpanded(true); +// } +// } +// }, [pathname, isPaidUser]); + +// const reqFeatureHandler = () => { +// window.open("https://github.com/apsinghdev/opensox/issues", "_blank"); +// }; + +// const handleProSectionClick = () => { +// if (isPaidUser) { +// setProSectionExpanded(!proSectionExpanded); +// } else { +// router.push("/pricing"); +// } +// }; + +// const desktopWidth = isCollapsed ? 80 : 288; +// const mobileWidth = desktopWidth; + +// return ( +// +// {/* Mobile header */} +//
+//
+// +// Opensox AI +// +//
+// setShowSidebar(false)}> +// +// +//
+ +// {/* Desktop header with collapse */} +//
+// {!isCollapsed && ( +// +// Opensox AI +// +// )} +// +// {isCollapsed ? ( +// +// ) : ( +// +// )} +// +//
+ +//
+// {/* free features section */} +// {FREE_ROUTES.map((route) => { +// const isActive = +// pathname === route.path || pathname.startsWith(`${route.path}/`); +// return ( +// +//
+// +// {route.icon} +// +// {!isCollapsed && ( +//
+//

+// {route.label} +//

+// {route.badge && ( +// +// {route.badge} +// +// )} +//
+// )} +//
+// +// ); +// })} + +// {/* divider */} +// {!isCollapsed && ( +//
+//
+//
+// )} + +// {/* premium section */} +// {!isCollapsed ? ( +//
+// {(() => { +// const isPremiumRouteActive = PREMIUM_ROUTES.some( +// (route) => +// pathname === route.path || +// pathname.startsWith(`${route.path}/`) +// ); +// const newFeaturesCount = PREMIUM_ROUTES.filter( +// (route) => route.badge +// ).length; +// return ( +//
{ +// if (e.key === "Enter" || e.key === " ") { +// e.preventDefault(); +// handleProSectionClick(); +// } +// }} +// > +//
+// +// +// +//
+//

+// Opensox Pro +//

+// +// {newFeaturesCount > 0 && ( +// +// {newFeaturesCount} +// +// )} +//
+//
+// {isPaidUser && ( +// +// )} +//
+// ); +// })()} + +// {/* premium sub-items (only show if paid user and expanded) */} +// {isPaidUser && proSectionExpanded && ( +// +// {PREMIUM_ROUTES.map((route) => { +// const isActive = +// pathname === route.path || +// pathname.startsWith(`${route.path}/`); +// return ( +// +//
+// +// {route.icon} +// +//
+//

+// {route.label} +//

+// {route.badge && ( +// +// {route.badge} +// +// )} +//
+//
+// +// ); +// })} +//
+// )} + +// {/* free user: show locked preview */} +// {!isPaidUser && ( +// +// {PREMIUM_ROUTES.map((route) => ( +//
router.push("/pricing")} +// className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 opacity-50 hover:opacity-75 group" +// role="button" +// tabIndex={0} +// aria-label={`${route.label} - Upgrade to Pro`} +// onKeyDown={(e) => { +// if (e.key === "Enter" || e.key === " ") { +// e.preventDefault(); +// router.push("/pricing"); +// } +// }} +// > +// +// {route.icon} +// +//
+//

+// {route.label} +//

+// {route.badge && ( +// +// {route.badge} +// +// )} +//
+//
+// +//
+//
+// ))} +//
+// )} +//
+// ) : ( +// // collapsed sidebar: show icon only +//
{ +// if (e.key === "Enter" || e.key === " ") { +// e.preventDefault(); +// handleProSectionClick(); +// } +// }} +// > +// +//
+// )} + +// {/* divider */} +// {!isCollapsed && ( +//
+//
+//
+// )} + +// {/* utility features */} +// } +// collapsed={isCollapsed} +// /> +//
+ +// {/* Bottom profile */} +// +// +// ); +// } + +// function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) { +// const [open, setOpen] = useState(false); +// const { data: session } = useSession(); +// const router = useRouter(); + +// const fullName = session?.user?.name || "User"; +// const firstName = fullName.split(" ")[0]; +// const userEmail = session?.user?.email || ""; +// const userImage = session?.user?.image || null; + +// // Close dropdown when clicking outside +// React.useEffect(() => { +// const handleClickOutside = (event: MouseEvent) => { +// const target = event.target as HTMLElement; +// if (open && !target.closest(".profile-menu-container")) { +// setOpen(false); +// } +// }; + +// if (open) { +// document.addEventListener("mousedown", handleClickOutside); +// return () => +// document.removeEventListener("mousedown", handleClickOutside); +// } +// }, [open]); + +// return ( +//
+//
setOpen((s) => !s)} +// > +// +// {!isCollapsed && ( +//
+//
+// +// {firstName} +// +// {userEmail} +//
+// +//
+// )} +//
+// {/* Profile Card Dropdown */} +// +// {!isCollapsed && open && ( +// +// {/* User Info Section */} +//
+//
+// +//
+// +// {fullName} +// +// {userEmail} +//
+//
+//
+ +// {/* Menu Items */} +//
+// +// +//
+//
+// )} +//
+//
+// ); +// } + + "use client"; import React, { useState, useEffect } from "react"; @@ -19,7 +547,7 @@ import { Squares2X2Icon, ChevronDownIcon, LockClosedIcon, - AcademicCapIcon + AcademicCapIcon, } from "@heroicons/react/24/outline"; import { useShowSidebar } from "@/store/useShowSidebar"; import { signOut, useSession } from "next-auth/react"; @@ -32,10 +560,9 @@ type RouteConfig = { path: string; label: string; icon: React.ReactNode; - badge?: string; // optional badge text (e.g., "New", "Beta") + badge?: string; }; -// free features only const FREE_ROUTES: RouteConfig[] = [ { path: "/dashboard/home", @@ -59,7 +586,6 @@ const FREE_ROUTES: RouteConfig[] = [ }, ]; -// premium features under Opensox Pro const PREMIUM_ROUTES: RouteConfig[] = [ { path: "/dashboard/pro/dashboard", @@ -82,6 +608,9 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { const { isPaidUser } = useSubscription(); const [proSectionExpanded, setProSectionExpanded] = useState(true); + // 🟣 New state for hover expand + const [isHovered, setIsHovered] = useState(false); + // auto-expand pro section if user is on a premium route useEffect(() => { if (isPaidUser) { @@ -106,7 +635,18 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { } }; - const desktopWidth = isCollapsed ? 80 : 288; + // 🟣 Hover handlers + const handleMouseEnter = () => { + if (isCollapsed) setIsHovered(true); + }; + + const handleMouseLeave = () => { + if (isHovered) setTimeout(() => setIsHovered(false), 150); + }; + + // compute dynamic width + const isSidebarExpanded = !isCollapsed || isHovered; + const desktopWidth = isSidebarExpanded ? 288 : 80; const mobileWidth = desktopWidth; return ( @@ -114,11 +654,14 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { className={`h-screen flex flex-col bg-dash-surface border-r border-dash-border z-50 ${ overlay ? "fixed left-0 top-0 bottom-0 xl:hidden" : "" }`} - initial={ - overlay ? { x: -400, width: mobileWidth } : { width: desktopWidth } - } - animate={overlay ? { x: 0, width: mobileWidth } : { width: desktopWidth }} - exit={overlay ? { x: -400, width: mobileWidth } : undefined} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + initial={{ + width: overlay ? mobileWidth : desktopWidth, + }} + animate={{ + width: overlay ? mobileWidth : desktopWidth, + }} transition={{ type: "spring", stiffness: 260, damping: 30 }} style={{ width: overlay ? mobileWidth : desktopWidth }} > @@ -139,7 +682,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { {/* Desktop header with collapse */}
- {!isCollapsed && ( + {!isSidebarExpanded ? null : ( - {isCollapsed ? ( - - ) : ( + {isSidebarExpanded ? ( + ) : ( + )}
-
+ {/* Sidebar content */} +
{/* free features section */} {FREE_ROUTES.map((route) => { const isActive = @@ -182,7 +726,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { > {route.icon} - {!isCollapsed && ( + {isSidebarExpanded && (

)} {/* premium section */} - {!isCollapsed ? ( + {isSidebarExpanded ? (
{(() => { const isPremiumRouteActive = PREMIUM_ROUTES.some( @@ -401,7 +945,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { )} {/* divider */} - {!isCollapsed && ( + {isSidebarExpanded && (
@@ -412,12 +956,12 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) { itemName="Request a feature" onclick={reqFeatureHandler} icon={} - collapsed={isCollapsed} + collapsed={!isSidebarExpanded} />
{/* Bottom profile */} - + ); } @@ -466,7 +1010,9 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) { {userEmail}

)} From 84c982c2512a9534698b35d44517ae169890eafd Mon Sep 17 00:00:00 2001 From: Harshit Date: Tue, 9 Dec 2025 17:05:27 +0530 Subject: [PATCH 2/2] update sidebar --- apps/web/src/components/dashboard/Sidebar.tsx | 527 ------------------ 1 file changed, 527 deletions(-) diff --git a/apps/web/src/components/dashboard/Sidebar.tsx b/apps/web/src/components/dashboard/Sidebar.tsx index c5cf9b6..1209319 100644 --- a/apps/web/src/components/dashboard/Sidebar.tsx +++ b/apps/web/src/components/dashboard/Sidebar.tsx @@ -1,530 +1,3 @@ -// "use client"; - -// import React, { useState, useEffect } from "react"; -// import Link from "next/link"; -// import SidebarItem from "../sidebar/SidebarItem"; -// import { useRouter, usePathname } from "next/navigation"; -// import { IconWrapper } from "../ui/IconWrapper"; -// import { motion, AnimatePresence } from "framer-motion"; -// import { -// XMarkIcon, -// HomeIcon, -// FolderIcon, -// ArrowRightOnRectangleIcon, -// SparklesIcon, -// StarIcon, -// DocumentTextIcon, -// Cog6ToothIcon, -// NewspaperIcon, -// Squares2X2Icon, -// ChevronDownIcon, -// LockClosedIcon, -// AcademicCapIcon -// } from "@heroicons/react/24/outline"; -// import { useShowSidebar } from "@/store/useShowSidebar"; -// import { signOut, useSession } from "next-auth/react"; -// import { ProfilePic } from "./ProfilePic"; -// import { useSubscription } from "@/hooks/useSubscription"; -// import { OpensoxProBadge } from "../sheet/OpensoxProBadge"; -// import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; - -// type RouteConfig = { -// path: string; -// label: string; -// icon: React.ReactNode; -// badge?: string; // optional badge text (e.g., "New", "Beta") -// }; - -// // free features only -// const FREE_ROUTES: RouteConfig[] = [ -// { -// path: "/dashboard/home", -// label: "Home", -// icon: , -// }, -// { -// path: "/dashboard/projects", -// label: "OSS Projects", -// icon: , -// }, -// { -// path: "/dashboard/sheet", -// label: "OSS Sheet", -// icon: , -// }, -// { -// path: "/dashboard/oss-programs", -// label: "OSS Programs", -// icon: , -// }, -// ]; - -// // premium features under Opensox Pro -// const PREMIUM_ROUTES: RouteConfig[] = [ -// { -// path: "/dashboard/pro/dashboard", -// label: "Dashboard", -// icon: , -// badge: "New", -// }, -// { -// path: "/dashboard/newsletters", -// label: "Newsletter", -// icon: , -// badge: "New", -// }, -// ]; - -// export default function Sidebar({ overlay = false }: { overlay?: boolean }) { -// const { setShowSidebar, isCollapsed, toggleCollapsed } = useShowSidebar(); -// const router = useRouter(); -// const pathname = usePathname(); -// const { isPaidUser } = useSubscription(); -// const [proSectionExpanded, setProSectionExpanded] = useState(true); - -// // auto-expand pro section if user is on a premium route -// useEffect(() => { -// if (isPaidUser) { -// const isOnPremiumRoute = PREMIUM_ROUTES.some((route) => { -// return pathname === route.path || pathname.startsWith(`${route.path}/`); -// }); -// if (isOnPremiumRoute) { -// setProSectionExpanded(true); -// } -// } -// }, [pathname, isPaidUser]); - -// const reqFeatureHandler = () => { -// window.open("https://github.com/apsinghdev/opensox/issues", "_blank"); -// }; - -// const handleProSectionClick = () => { -// if (isPaidUser) { -// setProSectionExpanded(!proSectionExpanded); -// } else { -// router.push("/pricing"); -// } -// }; - -// const desktopWidth = isCollapsed ? 80 : 288; -// const mobileWidth = desktopWidth; - -// return ( -// -// {/* Mobile header */} -//
-//
-// -// Opensox AI -// -//
-// setShowSidebar(false)}> -// -// -//
- -// {/* Desktop header with collapse */} -//
-// {!isCollapsed && ( -// -// Opensox AI -// -// )} -// -// {isCollapsed ? ( -// -// ) : ( -// -// )} -// -//
- -//
-// {/* free features section */} -// {FREE_ROUTES.map((route) => { -// const isActive = -// pathname === route.path || pathname.startsWith(`${route.path}/`); -// return ( -// -//
-// -// {route.icon} -// -// {!isCollapsed && ( -//
-//

-// {route.label} -//

-// {route.badge && ( -// -// {route.badge} -// -// )} -//
-// )} -//
-// -// ); -// })} - -// {/* divider */} -// {!isCollapsed && ( -//
-//
-//
-// )} - -// {/* premium section */} -// {!isCollapsed ? ( -//
-// {(() => { -// const isPremiumRouteActive = PREMIUM_ROUTES.some( -// (route) => -// pathname === route.path || -// pathname.startsWith(`${route.path}/`) -// ); -// const newFeaturesCount = PREMIUM_ROUTES.filter( -// (route) => route.badge -// ).length; -// return ( -//
{ -// if (e.key === "Enter" || e.key === " ") { -// e.preventDefault(); -// handleProSectionClick(); -// } -// }} -// > -//
-// -// -// -//
-//

-// Opensox Pro -//

-// -// {newFeaturesCount > 0 && ( -// -// {newFeaturesCount} -// -// )} -//
-//
-// {isPaidUser && ( -// -// )} -//
-// ); -// })()} - -// {/* premium sub-items (only show if paid user and expanded) */} -// {isPaidUser && proSectionExpanded && ( -// -// {PREMIUM_ROUTES.map((route) => { -// const isActive = -// pathname === route.path || -// pathname.startsWith(`${route.path}/`); -// return ( -// -//
-// -// {route.icon} -// -//
-//

-// {route.label} -//

-// {route.badge && ( -// -// {route.badge} -// -// )} -//
-//
-// -// ); -// })} -//
-// )} - -// {/* free user: show locked preview */} -// {!isPaidUser && ( -// -// {PREMIUM_ROUTES.map((route) => ( -//
router.push("/pricing")} -// className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 opacity-50 hover:opacity-75 group" -// role="button" -// tabIndex={0} -// aria-label={`${route.label} - Upgrade to Pro`} -// onKeyDown={(e) => { -// if (e.key === "Enter" || e.key === " ") { -// e.preventDefault(); -// router.push("/pricing"); -// } -// }} -// > -// -// {route.icon} -// -//
-//

-// {route.label} -//

-// {route.badge && ( -// -// {route.badge} -// -// )} -//
-//
-// -//
-//
-// ))} -//
-// )} -//
-// ) : ( -// // collapsed sidebar: show icon only -//
{ -// if (e.key === "Enter" || e.key === " ") { -// e.preventDefault(); -// handleProSectionClick(); -// } -// }} -// > -// -//
-// )} - -// {/* divider */} -// {!isCollapsed && ( -//
-//
-//
-// )} - -// {/* utility features */} -// } -// collapsed={isCollapsed} -// /> -//
- -// {/* Bottom profile */} -// -// -// ); -// } - -// function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) { -// const [open, setOpen] = useState(false); -// const { data: session } = useSession(); -// const router = useRouter(); - -// const fullName = session?.user?.name || "User"; -// const firstName = fullName.split(" ")[0]; -// const userEmail = session?.user?.email || ""; -// const userImage = session?.user?.image || null; - -// // Close dropdown when clicking outside -// React.useEffect(() => { -// const handleClickOutside = (event: MouseEvent) => { -// const target = event.target as HTMLElement; -// if (open && !target.closest(".profile-menu-container")) { -// setOpen(false); -// } -// }; - -// if (open) { -// document.addEventListener("mousedown", handleClickOutside); -// return () => -// document.removeEventListener("mousedown", handleClickOutside); -// } -// }, [open]); - -// return ( -//
-//
setOpen((s) => !s)} -// > -// -// {!isCollapsed && ( -//
-//
-// -// {firstName} -// -// {userEmail} -//
-// -//
-// )} -//
-// {/* Profile Card Dropdown */} -// -// {!isCollapsed && open && ( -// -// {/* User Info Section */} -//
-//
-// -//
-// -// {fullName} -// -// {userEmail} -//
-//
-//
- -// {/* Menu Items */} -//
-// -// -//
-//
-// )} -//
-//
-// ); -// } - "use client";