create main menu panel LO

This commit is contained in:
yzned
2025-07-20 16:41:40 +03:00
committed by yzned
parent 39b3a837df
commit 42b27b7ddc
13 changed files with 237 additions and 32 deletions

View File

@@ -0,0 +1,199 @@
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 { 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";
interface MenuItemType extends ButtonVariantType {
name: string;
icon: ReactNode;
path: string;
}
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",
},
];
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 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}`,
subtitle: `Last message preview...`,
}));
const MainMenuContent = observer(() => {
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);
};
}, []);
return (
<div
ref={containerRef}
className="relative w-full h-full overflow-auto flex flex-col"
>
<header
data-scrolled={hasScrolled}
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="space-y-2 pb-10">
{MOCK_CHATS.map((chat) => (
<li
key={chat.id}
className="rounded-md px-3 py-2 hover:bg-fill-100 cursor-pointer transition-colors"
>
<p className="text-sm font-medium text-text-light-900 truncate">
{chat.title}
</p>
<p className="text-xs text-text-light-400 truncate">
{chat.subtitle}
</p>
</li>
))}
</ul>
</div>
<footer className="sticky bottom-0 z-10 bg-white w-full p-4 border-t border-fill-150">
<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>
</footer>
</div>
);
});
export { MainMenu, MenuItem, MENU_ITEMS, MainMenuContent };