refactor: enhance sidebar functionality with cookie state management and animations
This commit is contained in:
parent
d2548e8854
commit
2cb7067beb
7 changed files with 99 additions and 39 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import { getCookie } from "cookies-next";
|
||||
import {
|
||||
ExternalLink,
|
||||
Mail,
|
||||
|
|
@ -14,7 +15,10 @@ import { usePathname } from "next/navigation";
|
|||
import { useRouter } from "next/router";
|
||||
import { type ReactNode, useContext } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Sidebar } from "~/components/area-riservata/sidebar";
|
||||
import {
|
||||
SIDEBAR_COOKIE_NAME,
|
||||
Sidebar,
|
||||
} from "~/components/area-riservata/sidebar";
|
||||
import { BannerFactory } from "~/components/banners";
|
||||
import { Footer, MiniFooter } from "~/components/footer";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
|
|
@ -114,7 +118,10 @@ export const AreaRiservataLayout = ({
|
|||
<main className="flex h-full flex-1 overflow-auto">
|
||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||
{noSidebar ? null : (
|
||||
<Sidebar isAdmin={session.user?.isAdmin || false} />
|
||||
<Sidebar
|
||||
defaultOpen={getCookie(SIDEBAR_COOKIE_NAME) === "true"}
|
||||
isAdmin={session.user?.isAdmin || false}
|
||||
/>
|
||||
)}
|
||||
<EnforcedSessionContext.Provider value={session as ValidSession}>
|
||||
{children}
|
||||
|
|
@ -150,7 +157,10 @@ export const AreaRiservataLayoutUserView = ({
|
|||
/>
|
||||
</Head>
|
||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||
<Sidebar isAdmin />
|
||||
<Sidebar
|
||||
defaultOpen={getCookie(SIDEBAR_COOKIE_NAME) === "true"}
|
||||
isAdmin
|
||||
/>
|
||||
|
||||
<div className="mx-4 my-3 flex-1 overflow-auto">
|
||||
<UserViewHeader />
|
||||
|
|
|
|||
|
|
@ -1,44 +1,47 @@
|
|||
import { getCookie, setCookie } from "cookies-next/client";
|
||||
import { setCookie } from "cookies-next/client";
|
||||
import { add } from "date-fns";
|
||||
import { motion } from "framer-motion";
|
||||
import { Expand, Minimize2 } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ARMinimizableLinks } from "~/components/navbar/mobile-nav";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export const SIDEBAR_COOKIE_NAME = "sidebar_minimized";
|
||||
export const Sidebar = ({
|
||||
defaultOpen,
|
||||
isAdmin,
|
||||
className,
|
||||
}: {
|
||||
defaultOpen: boolean;
|
||||
isAdmin: boolean;
|
||||
className?: string;
|
||||
}) => {
|
||||
const pathname = usePathname();
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [minimized, setMinimized] = useState(defaultOpen);
|
||||
|
||||
const [minimized, setMinimized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultOpen = getCookie("sidebar_minimized") === "true";
|
||||
setMinimized(defaultOpen);
|
||||
}, []);
|
||||
const toggleSidebar = (v: boolean) => {
|
||||
setIsAnimating(true);
|
||||
setMinimized(v);
|
||||
setCookie("sidebar_minimized", v.toString(), {
|
||||
setCookie(SIDEBAR_COOKIE_NAME, v.toString(), {
|
||||
expires: add(new Date(), { days: 30 }),
|
||||
});
|
||||
setTimeout(() => {
|
||||
setIsAnimating(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.nav
|
||||
animate={{ width: minimized ? 65 : 192 }}
|
||||
animate={{ width: minimized ? 55 : 192 }}
|
||||
aria-label="Sidebar Menu"
|
||||
className={cn(
|
||||
"z-30 hidden h-auto w-48 shrink-0 space-y-4 border-r bg-white pt-3 text-accent-foreground md:block",
|
||||
className,
|
||||
minimized && "cursor-pointer",
|
||||
//defaultOpen && "w-[65px]",
|
||||
)}
|
||||
onClick={
|
||||
minimized
|
||||
|
|
@ -52,7 +55,7 @@ export const Sidebar = ({
|
|||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between px-7",
|
||||
"flex h-8 items-center justify-between px-4",
|
||||
minimized && "justify-center px-0",
|
||||
)}
|
||||
>
|
||||
|
|
@ -66,16 +69,17 @@ export const Sidebar = ({
|
|||
variant="ghost"
|
||||
>
|
||||
{minimized ? (
|
||||
<Expand className="size-6" />
|
||||
<Expand className="size-5" />
|
||||
) : (
|
||||
<Minimize2 className="size-6" />
|
||||
<Minimize2 className="size-5" />
|
||||
)}
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={cn("px-4 text-lg", minimized && "px-0")}>
|
||||
<div className={cn("px-2 text-lg")}>
|
||||
<ARMinimizableLinks
|
||||
isAdmin={isAdmin}
|
||||
isAnimating={isAnimating}
|
||||
minimized={minimized}
|
||||
pathname={pathname}
|
||||
setOpen={toggleSidebar}
|
||||
|
|
|
|||
|
|
@ -262,11 +262,13 @@ const AreaRiservataLinks = ({
|
|||
};
|
||||
|
||||
export const ARMinimizableLinks = ({
|
||||
isAnimating,
|
||||
isAdmin,
|
||||
setOpen,
|
||||
pathname,
|
||||
minimized,
|
||||
}: {
|
||||
isAnimating: boolean;
|
||||
isAdmin: boolean;
|
||||
setOpen?: (status: boolean) => void;
|
||||
pathname: string;
|
||||
|
|
@ -276,7 +278,7 @@ export const ARMinimizableLinks = ({
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col space-y-4 text-base text-primary",
|
||||
"flex w-full flex-col items-start gap-4 text-base text-primary",
|
||||
minimized && "justify-center",
|
||||
)}
|
||||
>
|
||||
|
|
@ -287,17 +289,23 @@ export const ARMinimizableLinks = ({
|
|||
<div key={item.title}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
item.href === pathname && "bg-muted",
|
||||
minimized && "justify-center",
|
||||
)}
|
||||
>
|
||||
<IconMatrix className="size-5" type={item.icon} />
|
||||
|
||||
<IconMatrix className="size-5 shrink-0" type={item.icon} />
|
||||
<span
|
||||
className={cn(
|
||||
"truncate duration-500",
|
||||
minimized && "hidden",
|
||||
isAnimating && "animate-expand",
|
||||
)}
|
||||
>
|
||||
{!minimized && item.title}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="truncate">
|
||||
<ul className="text-ellipsis">
|
||||
{item.items.map((item) => {
|
||||
return (
|
||||
<MobileLink
|
||||
|
|
@ -324,34 +332,48 @@ export const ARMinimizableLinks = ({
|
|||
return (
|
||||
<MobileLink
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
item.href === pathname && "bg-muted",
|
||||
minimized && "justify-center",
|
||||
)}
|
||||
disabled={minimized}
|
||||
href={item.href}
|
||||
key={item.title}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<IconMatrix className="size-5" type={item.icon} />
|
||||
<IconMatrix className="size-5 shrink-0" type={item.icon} />
|
||||
<span
|
||||
className={cn(
|
||||
"truncate duration-500",
|
||||
minimized && "hidden",
|
||||
isAnimating && "animate-expand",
|
||||
)}
|
||||
>
|
||||
{!minimized && item.title}
|
||||
</span>
|
||||
</MobileLink>
|
||||
);
|
||||
})
|
||||
: t.nav.areaRiservataUserNav.map((item) => (
|
||||
<MobileLink
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
|
||||
item.href === pathname && "bg-muted",
|
||||
minimized && "justify-center",
|
||||
)}
|
||||
disabled={minimized}
|
||||
href={item.href}
|
||||
key={item.title}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<IconMatrix className="size-5" type={item.icon} />
|
||||
<IconMatrix className="size-5 shrink-0" type={item.icon} />
|
||||
<span
|
||||
className={cn(
|
||||
"truncate duration-500",
|
||||
minimized && "hidden",
|
||||
isAnimating && "animate-expand",
|
||||
)}
|
||||
>
|
||||
{!minimized && item.title}
|
||||
</span>
|
||||
</MobileLink>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getCookie } from "cookies-next";
|
||||
import type { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import { Sidebar } from "~/components/area-riservata/sidebar";
|
||||
|
|
@ -41,7 +42,10 @@ const AdminChats: NextPageWithLayout = () => {
|
|||
<meta content={t.heads.area_riservata_description} name="description" />
|
||||
</Head>
|
||||
<div className="flex h-full w-full flex-col md:flex-row">
|
||||
<Sidebar isAdmin />
|
||||
<Sidebar
|
||||
defaultOpen={getCookie("sidebar_minimized") === "true"}
|
||||
isAdmin
|
||||
/>
|
||||
|
||||
<div className="h-full flex-1 grow">
|
||||
<div className="flex h-full w-full flex-row">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getCookie } from "cookies-next";
|
||||
import type { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import { Suspense } from "react";
|
||||
|
|
@ -32,7 +33,11 @@ const ChatPage: NextPageWithLayout<ChatPageProps> = ({
|
|||
<div className="h-full flex-1 grow">
|
||||
<div className="flex h-full w-full flex-row">
|
||||
<div className="h-full">
|
||||
<Sidebar className="h-full" isAdmin />
|
||||
<Sidebar
|
||||
className="h-full"
|
||||
defaultOpen={getCookie("sidebar_minimized") === "true"}
|
||||
isAdmin
|
||||
/>
|
||||
</div>
|
||||
<div className="h-full flex-1 grow">
|
||||
<div className="flex h-full w-full flex-row">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getCookie } from "cookies-next";
|
||||
import type { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import { Suspense } from "react";
|
||||
|
|
@ -31,7 +32,11 @@ const UserChat: NextPageWithLayout<UserChatProps> = ({
|
|||
<div className="h-full flex-1 grow">
|
||||
<div className="flex h-full w-full flex-row">
|
||||
<div className="h-full">
|
||||
<Sidebar className="h-full" isAdmin={false} />
|
||||
<Sidebar
|
||||
className="h-full"
|
||||
defaultOpen={getCookie("sidebar_minimized") === "true"}
|
||||
isAdmin={false}
|
||||
/>
|
||||
</div>
|
||||
<Suspense fallback={<LoadingPage />}>
|
||||
<Chat chatId={chatId} userData={session} />
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@
|
|||
--animate-collapsible-down: collapsible-down 0.3s ease-out;
|
||||
--animate-collapsible-up: collapsible-up 0.3s ease-in;
|
||||
|
||||
|
||||
@keyframes caret-blink {
|
||||
|
||||
0%,
|
||||
|
|
@ -154,6 +153,19 @@
|
|||
border-color: hsl(var(--border)) / 10;
|
||||
}
|
||||
}
|
||||
|
||||
--animate-expand: expand 0.3s ease-out forwards;
|
||||
@keyframes expand {
|
||||
0% {
|
||||
width: 0%;
|
||||
/*opacity: 0;*/
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 100%;
|
||||
/*opacity: 1;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
|
|
@ -287,8 +299,6 @@
|
|||
@apply !py-2 hover:border-muted-foreground focus:border-foreground focus:ring-foreground focus-visible:ring-1
|
||||
}
|
||||
|
||||
|
||||
|
||||
@utility no-spinner {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue