diff --git a/package.json b/package.json index c7809ef..4022abc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test": "vitest run" }, "dependencies": { + "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcd6d8b..f294231 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.15 + version: 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-toast': specifier: ^1.2.14 version: 1.2.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) diff --git a/src/components/main-menu/chat-cell.tsx b/src/components/main-menu/chat-cell.tsx new file mode 100644 index 0000000..975b47d --- /dev/null +++ b/src/components/main-menu/chat-cell.tsx @@ -0,0 +1,71 @@ +import { Dropdown } from "../ui/dropdown"; +import { cn } from "@/lib/utils"; +import { Link, useMatchRoute } from "@tanstack/react-router"; +import { useState } from "react"; +import ThreeDots from "@/icons/three-dots.svg?react"; +import Edit from "@/icons/edit.svg?react"; +import Share from "@/icons/share.svg?react"; +import Trash from "@/icons/trash.svg?react"; + +export const ChatCell = ({ chat }: { chat: { id: string; title: string } }) => { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + const matchRoute = useMatchRoute(); + const match = matchRoute({ to: "/interaction-panel/$id", fuzzy: true }); + + const activeChatId = match ? match.id : undefined; + + const DROPDOWN_OPTIONS = [ + { + name: "Share", + icon: , + onClick: () => console.log("1"), + }, + { + name: "Rename", + icon: , + onClick: () => console.log("2"), + }, + { + name: "Delete", + icon: , + onClick: () => console.log("3"), + className: "text-feedback-negative-900", + }, + ]; + + return ( +
+ +
+ {chat.title} +
+ + + setIsDropdownOpen(!isDropdownOpen)} + className="cursor-pointer flex items-center pr-3 justify-end w-5 h-full" + > + + +
+ ); +}; diff --git a/src/components/main-menu/main-menu.tsx b/src/components/main-menu/main-menu.tsx index b945932..29042e1 100644 --- a/src/components/main-menu/main-menu.tsx +++ b/src/components/main-menu/main-menu.tsx @@ -4,16 +4,19 @@ 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 ThreeDots from "@/icons/three-dots.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, useMatchRoute, useParams } from "@tanstack/react-router"; +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; @@ -21,27 +24,6 @@ interface MenuItemType extends ButtonVariantType { path: string; } -const MENU_ITEMS: MenuItemType[] = [ - { - name: "New Chat", - icon: , - path: "/", - variant: "primary", - }, - { - name: "All chats", - icon: , - path: "/all-chats", - variant: "secondary", - }, - { - name: "Support", - icon: , - path: LINKS.discord, - variant: "secondary", - }, -]; - const MainMenu = observer(() => { const { isMainMenuOpen } = useAccountStore(); @@ -61,6 +43,8 @@ const MOCK_CHATS = Array.from({ length: 20 }, (_, i) => ({ })); const MainMenuContent = observer(() => { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const { isMainMenuOpen, setIsMainMenuOpen } = useAccountStore(); const containerRef = useRef(null); const [hasScrolled, setHasScrolled] = useState(false); @@ -81,6 +65,25 @@ const MainMenuContent = observer(() => { }; }, []); + const DROPDOWN_OPTIONS = [ + { + name: "user-mail", + icon: , + onClick: () => console.log("1"), + className: "text-text-light-500", + }, + { + name: "Settings", + icon: , + onClick: () => console.log("2"), + }, + { + name: "Sigh out", + icon: , + onClick: () => console.log("3"), + }, + ]; + return (
{
-
- + +
+ username +
+ +
); @@ -190,37 +201,25 @@ const MenuItem = ({ item }: { item: MenuItemType }) => { ); }; -const ChatCell = ({ chat }: { chat: { id: string; title: string } }) => { - const matchRoute = useMatchRoute(); - const match = matchRoute({ to: "/interaction-panel/$id", fuzzy: true }); - - const activeChatId = match ? match.id : undefined; - - return ( -
- -
- {chat.title} -
- - - -
- ); -}; +const MENU_ITEMS: MenuItemType[] = [ + { + name: "New Chat", + icon: , + path: "/", + variant: "primary", + }, + { + name: "All chats", + icon: , + path: "/all-chats", + variant: "secondary", + }, + { + name: "Support", + icon: , + path: LINKS.discord, + variant: "secondary", + }, +]; export { MainMenu, MenuItem, MENU_ITEMS, MainMenuContent }; diff --git a/src/components/ui/dropdown.tsx b/src/components/ui/dropdown.tsx new file mode 100644 index 0000000..cae1798 --- /dev/null +++ b/src/components/ui/dropdown.tsx @@ -0,0 +1,85 @@ +"use client"; + +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { FC, type ReactNode } from "react"; +import clsx from "clsx"; +import { cn } from "@/lib/utils"; + +interface DropdownOption { + name: string; + icon?: ReactNode; + onClick: () => void; + className?: string; +} + +interface DropdownProps { + children: ReactNode; + options: DropdownOption[]; + isOpen: boolean; + onToggle: () => void; + classNameContent?: string; + classNameTrigger?: string; +} + +const Dropdown: FC = ({ + children, + options = [], + isOpen, + onToggle, + classNameContent = "", + classNameTrigger = "", +}) => { + return ( + + + + + + + + {options.map((option, index) => ( + { + e.preventDefault(); + option.onClick(); + }} + className={cn( + "focus:outline-none flex items-center gap-2 h-8 px-2 text-sm text-text-light-900 hover:bg-fill-100 cursor-pointer rounded-[8px] transition-colors", + option.className, + )} + > + {option.icon && ( + + {option.icon} + + )} + {option.name} + + ))} + + + + ); +}; + +export { Dropdown }; diff --git a/src/icons/edit.svg b/src/icons/edit.svg new file mode 100644 index 0000000..3a1259b --- /dev/null +++ b/src/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/settings.svg b/src/icons/settings.svg new file mode 100644 index 0000000..c444733 --- /dev/null +++ b/src/icons/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/share.svg b/src/icons/share.svg new file mode 100644 index 0000000..96ba202 --- /dev/null +++ b/src/icons/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/sign-out.svg b/src/icons/sign-out.svg new file mode 100644 index 0000000..ff09ee6 --- /dev/null +++ b/src/icons/sign-out.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/trash.svg b/src/icons/trash.svg new file mode 100644 index 0000000..7969d48 --- /dev/null +++ b/src/icons/trash.svg @@ -0,0 +1,3 @@ + + +