| import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; | |
| import { motion } from 'framer-motion'; | |
| import { useStore } from '@nanostores/react'; | |
| import { classNames } from '~/utils/classNames'; | |
| import { profileStore } from '~/lib/stores/profile'; | |
| import type { TabType, Profile } from './types'; | |
| const BetaLabel = () => ( | |
| <span className="px-1.5 py-0.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20 text-[10px] font-medium text-purple-600 dark:text-purple-400 ml-2"> | |
| BETA | |
| </span> | |
| ); | |
| interface AvatarDropdownProps { | |
| onSelectTab: (tab: TabType) => void; | |
| } | |
| export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => { | |
| const profile = useStore(profileStore) as Profile; | |
| return ( | |
| <DropdownMenu.Root> | |
| <DropdownMenu.Trigger asChild> | |
| <motion.button | |
| className="w-10 h-10 rounded-full bg-transparent flex items-center justify-center focus:outline-none" | |
| whileHover={{ scale: 1.02 }} | |
| whileTap={{ scale: 0.98 }} | |
| > | |
| {profile?.avatar ? ( | |
| <img | |
| src={profile.avatar} | |
| alt={profile?.username || 'Profile'} | |
| className="w-full h-full rounded-full object-cover" | |
| loading="eager" | |
| decoding="sync" | |
| /> | |
| ) : ( | |
| <div className="w-full h-full rounded-full flex items-center justify-center bg-white dark:bg-gray-800 text-gray-400 dark:text-gray-500"> | |
| <div className="i-ph:question w-6 h-6" /> | |
| </div> | |
| )} | |
| </motion.button> | |
| </DropdownMenu.Trigger> | |
| <DropdownMenu.Portal> | |
| <DropdownMenu.Content | |
| className={classNames( | |
| 'min-w-[240px] z-[250]', | |
| 'bg-white dark:bg-[#141414]', | |
| 'rounded-lg shadow-lg', | |
| 'border border-gray-200/50 dark:border-gray-800/50', | |
| 'animate-in fade-in-0 zoom-in-95', | |
| 'py-1', | |
| )} | |
| sideOffset={5} | |
| align="end" | |
| > | |
| <div | |
| className={classNames( | |
| 'px-4 py-3 flex items-center gap-3', | |
| 'border-b border-gray-200/50 dark:border-gray-800/50', | |
| )} | |
| > | |
| <div className="w-10 h-10 rounded-full overflow-hidden flex-shrink-0 bg-white dark:bg-gray-800 shadow-sm"> | |
| {profile?.avatar ? ( | |
| <img | |
| src={profile.avatar} | |
| alt={profile?.username || 'Profile'} | |
| className={classNames('w-full h-full', 'object-cover', 'transform-gpu', 'image-rendering-crisp')} | |
| loading="eager" | |
| decoding="sync" | |
| /> | |
| ) : ( | |
| <div className="w-full h-full flex items-center justify-center text-gray-400 dark:text-gray-500 font-medium text-lg"> | |
| <span className="relative -top-0.5">?</span> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="font-medium text-sm text-gray-900 dark:text-white truncate"> | |
| {profile?.username || 'Guest User'} | |
| </div> | |
| {profile?.bio && <div className="text-xs text-gray-500 dark:text-gray-400 truncate">{profile.bio}</div>} | |
| </div> | |
| </div> | |
| <DropdownMenu.Item | |
| className={classNames( | |
| 'flex items-center gap-2 px-4 py-2.5', | |
| 'text-sm text-gray-700 dark:text-gray-200', | |
| 'hover:bg-purple-50 dark:hover:bg-purple-500/10', | |
| 'hover:text-purple-500 dark:hover:text-purple-400', | |
| 'cursor-pointer transition-all duration-200', | |
| 'outline-none', | |
| 'group', | |
| )} | |
| onClick={() => onSelectTab('profile')} | |
| > | |
| <div className="i-ph:user-circle w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> | |
| Edit Profile | |
| </DropdownMenu.Item> | |
| <DropdownMenu.Item | |
| className={classNames( | |
| 'flex items-center gap-2 px-4 py-2.5', | |
| 'text-sm text-gray-700 dark:text-gray-200', | |
| 'hover:bg-purple-50 dark:hover:bg-purple-500/10', | |
| 'hover:text-purple-500 dark:hover:text-purple-400', | |
| 'cursor-pointer transition-all duration-200', | |
| 'outline-none', | |
| 'group', | |
| )} | |
| onClick={() => onSelectTab('settings')} | |
| > | |
| <div className="i-ph:gear-six w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> | |
| Settings | |
| </DropdownMenu.Item> | |
| <div className="my-1 border-t border-gray-200/50 dark:border-gray-800/50" /> | |
| <DropdownMenu.Item | |
| className={classNames( | |
| 'flex items-center gap-2 px-4 py-2.5', | |
| 'text-sm text-gray-700 dark:text-gray-200', | |
| 'hover:bg-purple-50 dark:hover:bg-purple-500/10', | |
| 'hover:text-purple-500 dark:hover:text-purple-400', | |
| 'cursor-pointer transition-all duration-200', | |
| 'outline-none', | |
| 'group', | |
| )} | |
| onClick={() => onSelectTab('task-manager')} | |
| > | |
| <div className="i-ph:activity w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> | |
| Task Manager | |
| <BetaLabel /> | |
| </DropdownMenu.Item> | |
| <DropdownMenu.Item | |
| className={classNames( | |
| 'flex items-center gap-2 px-4 py-2.5', | |
| 'text-sm text-gray-700 dark:text-gray-200', | |
| 'hover:bg-purple-50 dark:hover:bg-purple-500/10', | |
| 'hover:text-purple-500 dark:hover:text-purple-400', | |
| 'cursor-pointer transition-all duration-200', | |
| 'outline-none', | |
| 'group', | |
| )} | |
| onClick={() => onSelectTab('service-status')} | |
| > | |
| <div className="i-ph:heartbeat w-4 h-4 text-gray-400 group-hover:text-purple-500 dark:group-hover:text-purple-400 transition-colors" /> | |
| Service Status | |
| <BetaLabel /> | |
| </DropdownMenu.Item> | |
| </DropdownMenu.Content> | |
| </DropdownMenu.Portal> | |
| </DropdownMenu.Root> | |
| ); | |
| }; | |