228 lines
6.1 KiB
TypeScript
228 lines
6.1 KiB
TypeScript
import { observer } from "mobx-react-lite";
|
|
import { useAccountStore } from "@/contexts/AccountContext";
|
|
import Plus from "@/icons/plus.svg?react";
|
|
import Books from "@/icons/books.svg?react";
|
|
import Discord from "@/icons/discord.svg?react";
|
|
import Profile from "@/icons/profile.svg?react";
|
|
import Settings from "@/icons/settings.svg?react";
|
|
import SignOut from "@/icons/sign-out.svg?react";
|
|
|
|
import { LINKS } from "@/lib/constants";
|
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
import { Button, type ButtonVariantType } from "../ui/button";
|
|
import { Link } from "@tanstack/react-router";
|
|
|
|
import Logo from "@/icons/logo.svg?react";
|
|
import LogoWithText from "@/icons/logo-with-text.svg?react";
|
|
import Sidebar from "@/icons/sidebar.svg?react";
|
|
import { ChatCell } from "./chat-cell";
|
|
import { Dropdown } from "../ui/dropdown";
|
|
|
|
interface MenuItemType extends ButtonVariantType {
|
|
name: string;
|
|
icon: ReactNode;
|
|
path: string;
|
|
}
|
|
|
|
const MainMenu = observer(() => {
|
|
const { isMainMenuOpen } = useAccountStore();
|
|
|
|
return (
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
className="w-[64px] data-[open=true]:w-[227px] transition-all border-r border-r border-fill-150"
|
|
>
|
|
<MainMenuContent />
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const MOCK_CHATS = Array.from({ length: 20 }, (_, i) => ({
|
|
id: i,
|
|
title: `Chat ${i + 1}`,
|
|
}));
|
|
|
|
const MainMenuContent = observer(() => {
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
|
|
const { isMainMenuOpen, setIsMainMenuOpen } = useAccountStore();
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [hasScrolled, setHasScrolled] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current;
|
|
if (!container) return;
|
|
|
|
const handleScroll = () => {
|
|
setHasScrolled(container.scrollTop > 0);
|
|
};
|
|
|
|
container.addEventListener("scroll", handleScroll);
|
|
handleScroll();
|
|
|
|
return () => {
|
|
container.removeEventListener("scroll", handleScroll);
|
|
};
|
|
}, []);
|
|
|
|
const DROPDOWN_OPTIONS = [
|
|
{
|
|
name: "user-mail",
|
|
icon: <Profile />,
|
|
onClick: () => console.log("1"),
|
|
className: "text-text-light-500",
|
|
},
|
|
{
|
|
name: "Settings",
|
|
icon: <Settings />,
|
|
onClick: () => console.log("2"),
|
|
},
|
|
{
|
|
name: "Sigh out",
|
|
icon: <SignOut />,
|
|
onClick: () => console.log("3"),
|
|
},
|
|
];
|
|
|
|
const showBorder = hasScrolled && isMainMenuOpen;
|
|
|
|
return (
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
ref={containerRef}
|
|
className="relative w-full h-full overflow-x-hidden data-[open=true]:overflow-y-auto overflow-y-hidden flex flex-col"
|
|
>
|
|
<header
|
|
data-scrolled={showBorder}
|
|
className="data-[scrolled=true]:border-b data-[scrolled=true]:border-fill-150 sticky top-0 z-10 bg-white pt-[30px] space-y-12 flex flex-col w-full px-4 pb-4 transition-shadow"
|
|
>
|
|
<div className="relative w-full h-[32px]">
|
|
{isMainMenuOpen ? (
|
|
<div className="flex items-center justify-between w-full">
|
|
<LogoWithText />
|
|
<Button
|
|
className="w-8 h-8 hover:cursor-w-resize"
|
|
variant="ghost"
|
|
onClick={() => setIsMainMenuOpen(false)}
|
|
>
|
|
<Sidebar />
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<div className="relative group px-1">
|
|
<Logo className="transition-opacity duration-150 group-hover:opacity-0" />
|
|
<Button
|
|
className="w-8 h-8 absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-150 hover:cursor-e-resize"
|
|
variant="ghost"
|
|
onClick={() => setIsMainMenuOpen(true)}
|
|
>
|
|
<Sidebar />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
className="space-y-4 w-full data-[open=true]:flex data-[open=true]:flex-col data-[open=true]:items-center"
|
|
>
|
|
{MENU_ITEMS.map((item) => (
|
|
<MenuItem item={item} key={item.name} />
|
|
))}
|
|
</div>
|
|
</header>
|
|
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
className="data-[open=false]:opacity-0 transition-opacity px-4 flex-1 mt-5"
|
|
>
|
|
<p className="text-text-light-200 font-[500] text-[14px] mb-2">
|
|
Recent
|
|
</p>
|
|
|
|
<ul className=" pb-10">
|
|
{MOCK_CHATS.map((chat) => (
|
|
<ChatCell
|
|
chat={{ id: chat.id.toString(), title: chat.title }}
|
|
key={chat.id}
|
|
/>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
<footer className="sticky bottom-0 z-10 bg-white w-full p-4 border-t border-fill-150 w-full">
|
|
<Dropdown
|
|
options={DROPDOWN_OPTIONS}
|
|
isOpen={isDropdownOpen}
|
|
onToggle={() => setIsDropdownOpen(!isDropdownOpen)}
|
|
classNameContent="min-w-[200px]"
|
|
classNameTrigger="w-[195px]"
|
|
>
|
|
<Button
|
|
data-open={isMainMenuOpen}
|
|
className="w-8 h-8 p-2 transition-all data-[open=true]:w-full data-[open=true]:h-10 items-center justify-start data-[open=true]:py-2 data-[open=true]:px-4"
|
|
variant="secondary"
|
|
>
|
|
<Profile />
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
className="data-[open=true]:opacity-100 opacity-0 transition-opacity duration-200 font-[500] text-[14px]"
|
|
>
|
|
username
|
|
</div>
|
|
</Button>
|
|
</Dropdown>
|
|
</footer>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const MenuItem = ({ item }: { item: MenuItemType }) => {
|
|
const { isMainMenuOpen } = useAccountStore();
|
|
|
|
return (
|
|
<Link
|
|
to={item.path}
|
|
className="group flex items-center gap-3 w-full whitespace-nowrap"
|
|
viewTransition={{ types: ["warp"] }}
|
|
>
|
|
<Button
|
|
data-variant={item.variant}
|
|
variant={item.variant}
|
|
className="w-8 h-8 data-[variant=secondary]:group-hover:bg-fill-150 data-[variant=primary]:group-hover:bg-fill-700 transition-colors"
|
|
>
|
|
{item.icon}
|
|
</Button>
|
|
<div
|
|
data-open={isMainMenuOpen}
|
|
className="data-[open=true]:opacity-100 opacity-0 transition-opacity duration-200 font-[500] text-[14px]"
|
|
>
|
|
{item.name}
|
|
</div>
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
const MENU_ITEMS: MenuItemType[] = [
|
|
{
|
|
name: "New Chat",
|
|
icon: <Plus className="scale-80" />,
|
|
path: "/",
|
|
variant: "primary",
|
|
},
|
|
{
|
|
name: "All chats",
|
|
icon: <Books className="scale-90" />,
|
|
path: "/all-chats",
|
|
variant: "secondary",
|
|
},
|
|
{
|
|
name: "Support",
|
|
icon: <Discord width={12} height={12} className="text-[#5865F2]" />,
|
|
path: LINKS.discord,
|
|
variant: "secondary",
|
|
},
|
|
];
|
|
|
|
export { MainMenu, MenuItem, MENU_ITEMS, MainMenuContent };
|