Skip to content

Commit 97e699a

Browse files
authored
Merge pull request #172 from ezhil56x/sidebar
feat(ui): enhance sidebar visuals and animations
2 parents 617cdb2 + 2122bac commit 97e699a

4 files changed

Lines changed: 91 additions & 72 deletions

File tree

apps/web/src/app/(main)/dashboard/layout.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import Sidebar from "@/components/dashboard/Sidebar";
3+
import { AnimatePresence } from "framer-motion";
34
import FiltersContainer from "@/components/ui/FiltersContainer";
45
import { useFilterStore } from "@/store/useFilterStore";
56
import { useShowSidebar } from "@/store/useShowSidebar";
@@ -17,9 +18,16 @@ export default function DashboardLayout({
1718
return (
1819
<div className="flex w-screen h-screen bg-ox-content overflow-hidden">
1920
{showFilters && <FiltersContainer />}
20-
<aside className={`h-full ${!showSidebar && "hidden xl:block"}`}>
21+
<aside className="hidden xl:block h-full">
2122
<Sidebar />
2223
</aside>
24+
<AnimatePresence>
25+
{showSidebar && (
26+
<div className="xl:hidden h-full">
27+
<Sidebar overlay />
28+
</div>
29+
)}
30+
</AnimatePresence>
2331
<div className="flex-1 flex flex-col h-full bg-ox-content">
2432
<div className="xl:hidden flex items-center h-16 px-4 border-b border-ox-header bg-ox-content">
2533
<IconWrapper onClick={() => setShowSidebar(true)}>

apps/web/src/components/dashboard/Sidebar.tsx

Lines changed: 72 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import React, { useState } from "react";
44
import Link from "next/link";
55
import SidebarItem from "../sidebar/SidebarItem";
6-
import { usePathname, useRouter } from "next/navigation";
6+
import { useRouter } from "next/navigation";
77
import { IconWrapper } from "../ui/IconWrapper";
8+
import { motion, AnimatePresence } from "framer-motion";
89
import {
910
XMarkIcon,
1011
HomeIcon,
1112
FolderIcon,
1213
ArrowRightOnRectangleIcon,
13-
ChevronDoubleLeftIcon,
14-
ChevronDoubleRightIcon,
1514
SparklesIcon,
1615
StarIcon,
1716
DocumentTextIcon,
@@ -22,6 +21,7 @@ import { signOut, useSession } from "next-auth/react";
2221
import { ProfilePic } from "./ProfilePic";
2322
import { useSubscription } from "@/hooks/useSubscription";
2423
import { OpensoxProBadge } from "../sheet/OpensoxProBadge";
24+
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
2525

2626
const SIDEBAR_ROUTES = [
2727
{
@@ -41,15 +41,8 @@ const SIDEBAR_ROUTES = [
4141
},
4242
];
4343

44-
const getSidebarLinkClassName = (currentPath: string, routePath: string) => {
45-
const isActive = currentPath === routePath;
46-
return `${isActive ? "text-ox-purple" : "text-ox-white"}`;
47-
};
48-
49-
export default function Sidebar() {
50-
const { showSidebar, setShowSidebar, isCollapsed, toggleCollapsed } =
51-
useShowSidebar();
52-
const pathname = usePathname();
44+
export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
45+
const { setShowSidebar, isCollapsed, toggleCollapsed } = useShowSidebar();
5346
const router = useRouter();
5447
const { isPaidUser } = useSubscription();
5548

@@ -64,17 +57,22 @@ export default function Sidebar() {
6457
router.push("/pricing");
6558
}
6659
};
60+
const desktopWidth = isCollapsed ? 80 : 288;
61+
const mobileWidth = desktopWidth;
6762

6863
return (
69-
<div
70-
className={`h-screen ${
71-
isCollapsed ? "w-20" : "w-72"
72-
} flex flex-col bg-ox-sidebar border-r border-ox-header z-50 transition-all duration-300 ease-out ${
73-
showSidebar ? "fixed xl:relative left-0 top-0 bottom-0" : ""
64+
<motion.div
65+
className={`h-screen flex flex-col bg-ox-sidebar border-r border-ox-header z-50 ${
66+
overlay ? "fixed left-0 top-0 bottom-0 xl:hidden" : ""
7467
}`}
68+
initial={overlay ? { x: -400, width: mobileWidth } : { width: desktopWidth }}
69+
animate={overlay ? { x: 0, width: mobileWidth } : { width: desktopWidth }}
70+
exit={overlay ? { x: -400, width: mobileWidth } : undefined}
71+
transition={{ type: "spring", stiffness: 260, damping: 30 }}
72+
style={{ width: overlay ? mobileWidth : desktopWidth }}
7573
>
7674
{/* Mobile header */}
77-
<div className="flex justify-between px-4 py-4 border-b border-ox-header xl:hidden bg-ox-sidebar">
75+
<div className="flex justify-between items-center h-16 px-4 border-b border-ox-header xl:hidden bg-ox-sideba">
7876
<div className="flex items-center">
7977
<Link
8078
href="/"
@@ -103,18 +101,17 @@ export default function Sidebar() {
103101
className={isCollapsed ? "w-full flex justify-center" : ""}
104102
>
105103
{isCollapsed ? (
106-
<ChevronDoubleRightIcon className="size-5 text-ox-purple" />
104+
<ChevronRightIcon className="size-5 text-ox-purple" />
107105
) : (
108-
<ChevronDoubleLeftIcon className="size-5 text-ox-purple" />
106+
<ChevronLeftIcon className="size-5 text-ox-purple" />
109107
)}
110108
</IconWrapper>
111109
</div>
112110

113-
<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4 space-y-1">
111+
<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4">
114112
{SIDEBAR_ROUTES.map((route) => {
115-
const activeClass = getSidebarLinkClassName(pathname, route.path);
116113
return (
117-
<Link href={route.path} className={activeClass} key={route.path}>
114+
<Link href={route.path} key={route.path}>
118115
<SidebarItem
119116
itemName={route.label}
120117
icon={route.icon}
@@ -131,14 +128,14 @@ export default function Sidebar() {
131128
/>
132129
{!isCollapsed && !isPaidUser ? (
133130
<div
134-
className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 pl-3 hover:bg-[#121214]"
131+
className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 pl-3 hover:bg-[#292929] group"
135132
onClick={proClickHandler}
136133
>
137-
<span className="shrink-0 text-[#eaeaea]">
134+
<span className="shrink-0 text-[#eaeaea] group-hover:text-white transition-colors">
138135
<StarIcon className="size-5" />
139136
</span>
140137
<div className="flex items-center gap-1">
141-
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-ox-purple">
138+
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-white transition-colors">
142139
Opensox Pro
143140
</h1>
144141
<OpensoxProBadge className="px-1.5 py-0.5 scale-75" />
@@ -156,7 +153,7 @@ export default function Sidebar() {
156153

157154
{/* Bottom profile */}
158155
<ProfileMenu isCollapsed={isCollapsed} />
159-
</div>
156+
</motion.div>
160157
);
161158
}
162159

@@ -189,7 +186,7 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
189186
return (
190187
<div className="px-3 py-4 border-t border-ox-header bg-ox-sidebar relative profile-menu-container">
191188
<div
192-
className={`group flex items-center rounded-md bg-ox-content border border-ox-header p-2 transition-all duration-300 ease-out cursor-pointer ${
189+
className={`group flex items-center rounded-md bg-ox-profile-card border border-ox-header p-2 transition-all duration-300 ease-out cursor-pointer ${
193190
isCollapsed ? "justify-center" : "gap-3"
194191
}`}
195192
onClick={() => setOpen((s) => !s)}
@@ -203,53 +200,62 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
203200
</span>
204201
<span className="text-[10px] text-zinc-400">{userEmail}</span>
205202
</div>
206-
<ChevronDoubleLeftIcon
203+
<ChevronLeftIcon
207204
className={`size-4 text-zinc-400 transition-transform ${open ? "rotate-90" : "-rotate-90"}`}
208205
/>
209206
</div>
210207
)}
211208
</div>
212209
{/* Profile Card Dropdown */}
213-
{!isCollapsed && open && (
214-
<div className="absolute bottom-full left-3 right-3 mb-2 bg-ox-content border border-ox-header rounded-lg shadow-xl overflow-hidden z-50">
215-
{/* User Info Section */}
216-
<div className="p-3 border-b border-ox-header">
217-
<div className="flex items-center gap-3">
218-
<ProfilePic imageUrl={userImage} />
219-
<div className="flex flex-col">
220-
<span className="text-sm text-white font-semibold">
221-
{fullName}
222-
</span>
223-
<span className="text-xs text-zinc-400">{userEmail}</span>
210+
<AnimatePresence>
211+
{!isCollapsed && open && (
212+
<motion.div
213+
key="profile-dropdown"
214+
initial={{ opacity: 0, y: 8 }}
215+
animate={{ opacity: 1, y: 0 }}
216+
exit={{ opacity: 0, y: 8 }}
217+
transition={{ duration: 0.18 }}
218+
className="absolute bottom-full left-3 right-3 mb-2 bg-ox-profile-card border border-ox-header rounded-lg shadow-xl overflow-hidden z-50"
219+
>
220+
{/* User Info Section */}
221+
<div className="p-3 border-b border-ox-header">
222+
<div className="flex items-center gap-3">
223+
<ProfilePic imageUrl={userImage} />
224+
<div className="flex flex-col">
225+
<span className="text-sm text-white font-semibold">
226+
{fullName}
227+
</span>
228+
<span className="text-xs text-zinc-400">{userEmail}</span>
229+
</div>
224230
</div>
225231
</div>
226-
</div>
227232

228-
{/* Menu Items */}
229-
<div className="py-1">
230-
<button
231-
onClick={() => {
232-
router.push("/dashboard/account");
233-
setOpen(false);
234-
}}
235-
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
236-
>
237-
<Cog6ToothIcon className="size-4" />
238-
<span>Account Settings</span>
239-
</button>
240-
<button
241-
onClick={() => {
242-
signOut({ callbackUrl: "/" });
243-
setOpen(false);
244-
}}
245-
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
246-
>
247-
<ArrowRightOnRectangleIcon className="size-4" />
248-
<span>Logout</span>
249-
</button>
250-
</div>
251-
</div>
252-
)}
233+
{/* Menu Items */}
234+
<div className="py-1">
235+
<button
236+
onClick={() => {
237+
router.push("/dashboard/account");
238+
setOpen(false);
239+
}}
240+
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
241+
>
242+
<Cog6ToothIcon className="size-4" />
243+
<span>Account Settings</span>
244+
</button>
245+
<button
246+
onClick={() => {
247+
signOut({ callbackUrl: "/" });
248+
setOpen(false);
249+
}}
250+
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
251+
>
252+
<ArrowRightOnRectangleIcon className="size-4" />
253+
<span>Logout</span>
254+
</button>
255+
</div>
256+
</motion.div>
257+
)}
258+
</AnimatePresence>
253259
</div>
254260
);
255261
}

apps/web/src/components/sidebar/SidebarItem.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ export default function SidebarItem({ itemName, onclick, icon, collapsed = false
1515
<div
1616
className={`w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 ${
1717
collapsed ? "justify-center" : "gap-3 pl-3"
18-
} hover:bg-[#121214]`}
18+
} hover:bg-[#292929] group`}
1919
onClick={onclick}
2020
>
21-
{icon && <span className="shrink-0 text-[#eaeaea]">{icon}</span>}
21+
{icon && (
22+
<span className="shrink-0 text-[#eaeaea] group-hover:text-white transition-colors">
23+
{icon}
24+
</span>
25+
)}
2226
{!collapsed && (
2327
<div className="flex items-center gap-1.5">
24-
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-ox-purple">
28+
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-white transition-colors">
2529
{itemName}
2630
</h1>
2731
{badge}

apps/web/tailwind.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ const config: Config = {
5757
"ox-white": "text-slate-400",
5858
"ox-black-1": "#0E0E10",
5959
"ox-black-2": "#15161A",
60-
"ox-sidebar": "#262626",
61-
"ox-content": "#1E1E1E",
60+
"ox-sidebar": "#141414",
61+
"ox-profile-card": "#1E1E1E",
62+
"ox-content": "#1A1A1A",
6263
"ox-header": "#363636",
6364
},
6465
borderRadius: {

0 commit comments

Comments
 (0)