Skip to content

Commit 308050d

Browse files
committed
feat(ui): add framer-motion animations to components
1 parent aa177ed commit 308050d

2 files changed

Lines changed: 69 additions & 47 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: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Link from "next/link";
55
import SidebarItem from "../sidebar/SidebarItem";
66
import { useRouter } from "next/navigation";
77
import { IconWrapper } from "../ui/IconWrapper";
8+
import { motion, AnimatePresence } from "framer-motion";
89
import {
910
XMarkIcon,
1011
HomeIcon,
@@ -41,9 +42,8 @@ const SIDEBAR_ROUTES = [
4142
},
4243
];
4344

44-
export default function Sidebar() {
45-
const { showSidebar, setShowSidebar, isCollapsed, toggleCollapsed } =
46-
useShowSidebar();
45+
export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
46+
const { setShowSidebar, isCollapsed, toggleCollapsed } = useShowSidebar();
4747
const router = useRouter();
4848
const { isPaidUser } = useSubscription();
4949

@@ -58,14 +58,19 @@ export default function Sidebar() {
5858
router.push("/pricing");
5959
}
6060
};
61+
const desktopWidth = isCollapsed ? 80 : 288;
62+
const mobileWidth = desktopWidth;
6163

6264
return (
63-
<div
64-
className={`h-screen ${
65-
isCollapsed ? "w-20" : "w-72"
66-
} flex flex-col bg-ox-sidebar border-r border-ox-header z-50 transition-all duration-300 ease-out ${
67-
showSidebar ? "fixed xl:relative left-0 top-0 bottom-0" : ""
65+
<motion.div
66+
className={`h-screen flex flex-col bg-ox-sidebar border-r border-ox-header z-50 ${
67+
overlay ? "fixed left-0 top-0 bottom-0 xl:hidden" : ""
6868
}`}
69+
initial={overlay ? { x: -400, width: mobileWidth } : { width: desktopWidth }}
70+
animate={overlay ? { x: 0, width: mobileWidth } : { width: desktopWidth }}
71+
exit={overlay ? { x: -400, width: mobileWidth } : undefined}
72+
transition={{ type: "spring", stiffness: 260, damping: 30 }}
73+
style={{ width: overlay ? mobileWidth : desktopWidth }}
6974
>
7075
{/* Mobile header */}
7176
<div className="flex justify-between items-center h-16 px-4 border-b border-ox-header xl:hidden bg-ox-sideba">
@@ -149,7 +154,7 @@ export default function Sidebar() {
149154

150155
{/* Bottom profile */}
151156
<ProfileMenu isCollapsed={isCollapsed} />
152-
</div>
157+
</motion.div>
153158
);
154159
}
155160

@@ -203,46 +208,55 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
203208
)}
204209
</div>
205210
{/* Profile Card Dropdown */}
206-
{!isCollapsed && open && (
207-
<div 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">
208-
{/* User Info Section */}
209-
<div className="p-3 border-b border-ox-header">
210-
<div className="flex items-center gap-3">
211-
<ProfilePic imageUrl={userImage} />
212-
<div className="flex flex-col">
213-
<span className="text-sm text-white font-semibold">
214-
{fullName}
215-
</span>
216-
<span className="text-xs text-zinc-400">{userEmail}</span>
211+
<AnimatePresence>
212+
{!isCollapsed && open && (
213+
<motion.div
214+
key="profile-dropdown"
215+
initial={{ opacity: 0, y: 8 }}
216+
animate={{ opacity: 1, y: 0 }}
217+
exit={{ opacity: 0, y: 8 }}
218+
transition={{ duration: 0.18 }}
219+
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"
220+
>
221+
{/* User Info Section */}
222+
<div className="p-3 border-b border-ox-header">
223+
<div className="flex items-center gap-3">
224+
<ProfilePic imageUrl={userImage} />
225+
<div className="flex flex-col">
226+
<span className="text-sm text-white font-semibold">
227+
{fullName}
228+
</span>
229+
<span className="text-xs text-zinc-400">{userEmail}</span>
230+
</div>
217231
</div>
218232
</div>
219-
</div>
220233

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

0 commit comments

Comments
 (0)