"use client"; import { Settings, Trash2, Users } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useRef, useState } from "react"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; interface SearchSpaceAvatarProps { name: string; isActive?: boolean; isShared?: boolean; isOwner?: boolean; onClick?: () => void; onDelete?: () => void; onSettings?: () => void; size?: "sm" | "md"; disableTooltip?: boolean; } /** * Generates a consistent color based on search space name */ function stringToColor(str: string): string { let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash); } const colors = [ "#6366f1", // indigo "#22c55e", // green "#f59e0b", // amber "#ef4444", // red "#8b5cf6", // violet "#06b6d4", // cyan "#ec4899", // pink "#14b8a6", // teal ]; return colors[Math.abs(hash) % colors.length]; } /** * Gets initials from search space name (max 2 chars) */ function getInitials(name: string): string { const words = name.trim().split(/\s+/); if (words.length >= 2) { return (words[0][0] + words[1][0]).toUpperCase(); } return name.slice(0, 2).toUpperCase(); } export function SearchSpaceAvatar({ name, isActive, isShared, isOwner = true, onClick, onDelete, onSettings, size = "md", disableTooltip = false, }: SearchSpaceAvatarProps) { const t = useTranslations("searchSpace"); const tCommon = useTranslations("common"); const bgColor = stringToColor(name); const initials = getInitials(name); const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm"; // Long-press state for mobile const [longPressMenuOpen, setLongPressMenuOpen] = useState(false); const longPressTimer = useRef | null>(null); const touchMoved = useRef(false); const handleTouchStart = useCallback(() => { touchMoved.current = false; longPressTimer.current = setTimeout(() => { if (!touchMoved.current) { setLongPressMenuOpen(true); } }, 500); }, []); const handleTouchMove = useCallback(() => { touchMoved.current = true; if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null; } }, []); const handleTouchEnd = useCallback(() => { if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null; } }, []); const tooltipContent = (
{name} {isShared && ( {isOwner ? tCommon("owner") : tCommon("shared")} )}
); const avatarButton = ( ); const menuItems = ( <> {onSettings && ( {tCommon("settings")} )} {onSettings && onDelete && } {onDelete && isOwner && ( {tCommon("delete")} )} {onDelete && !isOwner && ( {t("leave")} )} ); // If delete or settings handlers are provided, wrap with context menu if (onDelete || onSettings) { // Mobile: use long-press triggered DropdownMenu if (disableTooltip) { return (
{avatarButton}
{menuItems}
); } // Desktop: use right-click ContextMenu + Tooltip return (
{avatarButton}
{tooltipContent}
{onSettings && ( {tCommon("settings")} )} {onSettings && onDelete && } {onDelete && isOwner && ( {tCommon("delete")} )} {onDelete && !isOwner && ( {t("leave")} )}
); } // No context menu needed if (disableTooltip) { return avatarButton; } return ( {avatarButton} {tooltipContent} ); }