feat: enhance Sidebar component with responsive menu and swipe functionality; update UserList component for full-width display

This commit is contained in:
Rémi 2025-01-07 14:37:21 +01:00
parent bb751c1476
commit 2f304684f3
3 changed files with 112 additions and 20 deletions

View File

@ -24,13 +24,15 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<div className="flex">
<Sidebar items={sidebarItems} />
<main className="p-7 w-full">
<h1 className="text-2xl font-semibold">
{
sidebarItems
.flat()
.find((item) => item.href === pathName)?.title
}
</h1>
<div className="flex items-center gap-2">
<h1 className="text-2xl font-semibold">
{
sidebarItems
.flat()
.find((item) => item.href === pathName)?.title
}
</h1>
</div>
<Divider className="mt-2 mb-5 bg-foreground-300" />

View File

@ -2,6 +2,8 @@
import { Divider, Link } from "@nextui-org/react";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { BsChevronDoubleRight } from "react-icons/bs";
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
import { SidebarItem } from "./item";
@ -16,13 +18,102 @@ export const Sidebar = ({
}) => {
const router = useRouter();
const pathName = usePathname();
const sidebarRef = useRef<HTMLDivElement | null>(null);
// State to manage menu openness
const [open, setOpen] = useState(false);
const [startX, setStartX] = useState(0);
const [isSwiping, setIsSwiping] = useState(false);
// Effect to manage responsiveness
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 1024) {
setOpen(true); // Menu always open on larger screens
} else {
setOpen(false); // Menu closed by default on smaller screens
}
};
handleResize(); // Set initial state based on current window size
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// Effect to close the menu when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (sidebarRef.current && !sidebarRef.current.contains(event.target as Node)) {
setOpen(false); // Close the menu if clicked outside
}
};
document.addEventListener("click", handleClickOutside);
// Cleanup the event listener when component unmounts
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, []);
// Handle swipe start (touchstart event)
const handleTouchStart = (e: React.TouchEvent) => {
const touchStart = e.touches[0].clientX;
setStartX(touchStart);
setIsSwiping(true);
};
// Handle swipe move (touchmove event)
const handleTouchMove = (e: React.TouchEvent) => {
if (!isSwiping) return;
const touchMove = e.touches[0].clientX;
const distance = touchMove - startX;
if (distance > 50 && !open) {
setOpen(true); // Open the menu if swiping right
} else if (distance < -50 && open) {
setOpen(false); // Close the menu if swiping left
}
};
// Handle swipe end (touchend event)
const handleTouchEnd = () => {
setIsSwiping(false);
};
return (
<aside className="h-screen">
<div className="flex flex-col w-72 gap-5 h-full px-3 py-4 overflow-y-auto bg-foreground-100">
<aside
ref={sidebarRef}
className={`
${open ? "translate-x-0" : "-translate-x-full"}
fixed top-0 left-0 z-40 w-64 h-screen transition-transform
lg:translate-x-0 lg:static
`}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className="flex flex-col h-full px-3 py-4 overflow-y-auto bg-foreground-100 scrollbar-hide">
{/* Toggle Button - Hidden on large screens */}
<button
className={`
lg:hidden flex items-center justify-center w-12 h-12
fixed left-64 transform bg-foreground-200 rounded-full shadow-lg
transition-transform duration-500 z-50
-translate-x-1/2 top-1/2 -translate-y-1/2
${open ? "rotate-180" : "rotate-0"}
`}
onClick={() => setOpen(!open)}
>
<span className="text-xl font-bold">
<BsChevronDoubleRight className="text-gray-900 dark:text-white" />
</span>
</button>
<div className="flex flex-col gap-2 p-2">
<Link
className="flex text-2xl font-semibold text-gray-900 dark:text-white rounded-lg cursor-pointer"
className="text-2xl font-semibold text-gray-900 dark:text-white rounded-lg cursor-pointer"
onPress={() => router.push("/")}
>
Toogether
@ -31,7 +122,6 @@ export const Sidebar = ({
Manage classes, rooms, and users
</p>
</div>
{items.map((group, index) => (
<div key={index}>
<section className="flex flex-col">
@ -39,11 +129,13 @@ export const Sidebar = ({
{group.map((item, index) => (
<li
key={index}
className={`${
pathName === item.href
? "bg-foreground-300"
: "hover:bg-foreground-200"
} rounded-md cursor-pointer`}
className={`
${
pathName === item.href
? "bg-foreground-300"
: "hover:bg-foreground-200"
} rounded-md cursor-pointer
`}
>
<SidebarItem
href={item.href}
@ -54,13 +146,11 @@ export const Sidebar = ({
))}
</ul>
</section>
{index < items.length - 1 && (
<Divider className="bg-foreground-300 mt-4" />
)}
</div>
))}
<section className="mt-auto flex justify-between">
<ThemeSwitcher />
</section>

View File

@ -24,7 +24,7 @@ export const UserList = () => {
if (!users) {
return (
<Card className="w-[300px] space-y-5 p-4" radius="lg">
<Card className="w-full space-y-5 p-4" radius="lg">
<Skeleton className="rounded-lg">
<div className="h-10 rounded-lg bg-default-300" />
</Skeleton>
@ -44,7 +44,7 @@ export const UserList = () => {
}
return (
<Table aria-label="List of users" className="w-[300px]">
<Table aria-label="List of users" className="w-full">
<TableHeader>
<TableColumn>USERNAME</TableColumn>
</TableHeader>