"use client" import { useState, useRef, useEffect, type DragEvent } from "react" import { MoreVertical } from "lucide-react" import { cn } from "@/lib/utils" import { useEmailDropTarget } from "@/lib/drag-context" import { MAIL_SIDEBAR_COLOR_PICKER_CLASS, MAIL_SIDEBAR_COLOR_SWATCH_RING_CLASS, MAIL_SIDEBAR_MENU_PLAIN_ITEM_CLASS, MAIL_SIDEBAR_MENU_SEPARATOR_CLASS, MAIL_SIDEBAR_MENU_SUB_TRIGGER_CLASS, MAIL_SIDEBAR_MENU_SURFACE_CLASS, } from "@/lib/mail-chrome-classes" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from "@/components/ui/context-menu" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { isSystemNavLabelId } from "@/lib/sidebar-nav-data" import type { LabelInMessageListVisibility, LabelListSidebarVisibility, NavItemPrefs, } from "@/lib/sidebar-nav-context" import { readSidebarNavDragData, resolveNavDropPlacement, setSidebarNavDragData, } from "@/lib/sidebar-nav-dnd" import { LABEL_MENU_COLOR_SWATCHES } from "@/components/gmail/sidebar/sidebar-nav-constants" import { SidebarNavOptionsSheet, SidebarNavSheetAction, SidebarNavSheetCheckOption, SidebarNavSheetColorPicker, SidebarNavSheetDivider, SidebarNavSheetSectionLabel, } from "@/components/gmail/sidebar/sidebar-nav-options-sheet" import { useSidebarTouchOptionsMenu } from "@/components/gmail/sidebar/use-sidebar-touch-options" import { LabelMenuOptionWithCheck, ContextLabelMenuOptionWithCheck, navRowRoundedWhenActive, SidebarNavDragHandle, SidebarNavIconSlot, SidebarOverflowColumn, sidebarOverflowMenuButtonClass, navRowActivate, } from "@/components/gmail/sidebar/sidebar-nav-primitives" import type { SidebarNavDragBindings } from "@/components/gmail/sidebar/sidebar-nav-drag-bindings" export type SidebarLabelItemRowProps = SidebarNavDragBindings & { item: { id: string; label: string; color: string; count?: number } unreadCount: number isExpanded: boolean selectedFolder: string touchNav: boolean onSelectFolder: (id: string) => void getNavItemPrefs: (id: string) => Required> setNavItemSidebarVisibility: (id: string, v: LabelListSidebarVisibility) => void setNavItemMessageVisibility: (id: string, v: LabelInMessageListVisibility) => void updateFolderOrLabelColor: (id: string, color: string) => void renameFolderOrLabel: (id: string, name: string) => void removeFolderOrLabelRow: (id: string) => void addChildLabelRow: (parentId: string, name: string) => void } export function SidebarLabelItemRow({ item, unreadCount, isExpanded: labelRowExpanded, selectedFolder, touchNav, onSelectFolder, getNavItemPrefs, setNavItemSidebarVisibility, setNavItemMessageVisibility, updateFolderOrLabelColor, renameFolderOrLabel, removeFolderOrLabelRow, addChildLabelRow, navDragRef, navDropPlacementRef, beginNavDrag, clearNavDrag, updateNavDropTarget, clearNavDropTarget, commitNavDrop, }: SidebarLabelItemRowProps) { const { isOver, dropHandlers } = useEmailDropTarget(item.id, item.label) const isSelected = selectedFolder === item.id const hasUnread = unreadCount > 0 const [menuOpen, setMenuOpen] = useState(false) const [contextMenuOpen, setContextMenuOpen] = useState(false) const menuTriggerRef = useRef(null) const [renameOpen, setRenameOpen] = useState(false) const [renameDraft, setRenameDraft] = useState(item.label) const [sublabelOpen, setSublabelOpen] = useState(false) const [sublabelName, setSublabelName] = useState("") const labelRenameInputRef = useRef(null) const sublabelNameInputRef = useRef(null) const canDragLabel = labelRowExpanded && !isSystemNavLabelId(item.id) const { sheetOpen, setSheetOpen, touchRowProps, touchRowClassName, closeSheet } = useSidebarTouchOptionsMenu(touchNav && labelRowExpanded) useEffect(() => { setRenameDraft(item.label) }, [item.label]) const handleMenuOpenChange = (open: boolean) => { setMenuOpen(open) if (!open) { queueMicrotask(() => menuTriggerRef.current?.blur()) } } const rowHoverHeld = !isSelected && !isOver && (contextMenuOpen || menuOpen || sheetOpen) const prefs = getNavItemPrefs(item.id) const labelDotClass = item.color ?? "bg-gray-400" const labelMenuSurface = MAIL_SIDEBAR_MENU_SURFACE_CLASS const colorSub = (subKind: "dropdown" | "context") => { const Sub = subKind === "dropdown" ? DropdownMenuSub : ContextMenuSub const SubTr = subKind === "dropdown" ? DropdownMenuSubTrigger : ContextMenuSubTrigger const SubCo = subKind === "dropdown" ? DropdownMenuSubContent : ContextMenuSubContent return ( Couleur du libellé
{LABEL_MENU_COLOR_SWATCHES.map((sw) => (
) } const rowClass = cn( "group/labelrow relative flex h-8 w-full min-w-0 shrink-0 cursor-default items-center pl-6 pr-2 transition-colors", navRowRoundedWhenActive(isSelected || isOver || rowHoverHeld), isSelected ? "bg-mail-nav-selected text-mail-nav-selected font-medium" : isOver ? "bg-mail-nav-drop text-foreground" : rowHoverHeld ? "bg-mail-nav-hover text-foreground" : hasUnread ? "text-gray-900 hover:bg-mail-nav-hover" : "text-gray-700 hover:bg-mail-nav-hover", touchRowClassName ) const onLabelRowDragEnter = (e: DragEvent) => { const active = navDragRef.current if (active?.kind === "label" && active.id !== item.id) { e.preventDefault() return } dropHandlers.onDragEnter(e) } const onLabelRowDragOver = (e: DragEvent) => { const active = navDragRef.current if (active?.kind === "label") { e.preventDefault() e.stopPropagation() if (active.id === item.id) return e.dataTransfer.dropEffect = "move" updateNavDropTarget( e.currentTarget as HTMLElement, resolveNavDropPlacement(e, false) ) return } dropHandlers.onDragOver(e) } const onLabelRowDragLeave = (e: DragEvent) => { if (navDragRef.current?.kind === "label") { const rt = e.relatedTarget as Node | null if (rt && e.currentTarget instanceof Node && e.currentTarget.contains(rt)) return clearNavDropTarget(e.currentTarget as HTMLElement) return } dropHandlers.onDragLeave(e) } const onLabelRowDrop = (e: DragEvent) => { const payload = readSidebarNavDragData(e, navDragRef.current) if (payload?.kind === "label") { e.preventDefault() e.stopPropagation() const placement = navDropPlacementRef.current ?? resolveNavDropPlacement(e, false) if (placement !== "inside") { commitNavDrop(payload, item.id, placement, "label") } else { clearNavDrag() } return } dropHandlers.onDrop(e) } const onLabelDragHandleStart = (e: DragEvent) => { const payload = { kind: "label" as const, id: item.id } setSidebarNavDragData(e, payload) const rowEl = (e.currentTarget as HTMLElement).closest("[data-nav-row]") as HTMLElement | null beginNavDrag(payload, rowEl) } const overflowMenu = labelRowExpanded ? ( {!touchNav && ( {colorSub("dropdown")} Dans la liste des libellés setNavItemSidebarVisibility(item.id, "show")} > Afficher setNavItemSidebarVisibility(item.id, "showUnread")} > Afficher si messages non lus setNavItemSidebarVisibility(item.id, "hide")} > Masquer Dans la liste des messages setNavItemMessageVisibility(item.id, "show")} > Afficher setNavItemMessageVisibility(item.id, "hide")} > Masquer { setRenameDraft(item.label) setRenameOpen(true) setMenuOpen(false) }} > Renommer… { removeFolderOrLabelRow(item.id) setMenuOpen(false) }} > Supprimer le libellé { setSublabelName("") setSublabelOpen(true) setMenuOpen(false) }} > Ajouter un sous-libellé )} ) : null const labelOptionsSheet = touchNav && labelRowExpanded && ( { updateFolderOrLabelColor(item.id, sw) closeSheet() }} /> Dans la liste des libellés { setNavItemSidebarVisibility(item.id, "show") closeSheet() }} > Afficher { setNavItemSidebarVisibility(item.id, "showUnread") closeSheet() }} > Afficher si messages non lus { setNavItemSidebarVisibility(item.id, "hide") closeSheet() }} > Masquer Dans la liste des messages { setNavItemMessageVisibility(item.id, "show") closeSheet() }} > Afficher { setNavItemMessageVisibility(item.id, "hide") closeSheet() }} > Masquer { setRenameDraft(item.label) setRenameOpen(true) closeSheet() }} > Renommer… { removeFolderOrLabelRow(item.id) closeSheet() }} > Supprimer le libellé { setSublabelName("") setSublabelOpen(true) closeSheet() }} > Ajouter un sous-libellé ) const labelRowEl = (
{canDragLabel ? ( ) : null}
onSelectFolder(item.id)} onKeyDown={(e) => navRowActivate(e, () => onSelectFolder(item.id))} className={cn( "flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-4 py-0 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring/50", labelRowExpanded ? "pr-1" : "pr-3" )} > {labelRowExpanded && ( {item.label} )}
{overflowMenu}
) return ( <> {touchNav ? ( labelRowEl ) : ( {labelRowEl} {colorSub("context")} Dans la liste des libellés setNavItemSidebarVisibility(item.id, "show")} > Afficher setNavItemSidebarVisibility(item.id, "showUnread")} > Afficher si non lus setNavItemSidebarVisibility(item.id, "hide")} > Masquer Dans la liste des messages setNavItemMessageVisibility(item.id, "show")} > Afficher setNavItemMessageVisibility(item.id, "hide")} > Masquer { setRenameDraft(item.label) setRenameOpen(true) }} > Renommer… removeFolderOrLabelRow(item.id)} > Supprimer le libellé { setSublabelName("") setSublabelOpen(true) }} > Ajouter un sous-libellé )} {labelOptionsSheet} { e.preventDefault() window.requestAnimationFrame(() => labelRenameInputRef.current?.focus() ) }} > Renommer le libellé Nouveau nom pour « {item.label} ». setRenameDraft(e.target.value)} autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() renameFolderOrLabel(item.id, renameDraft) setRenameOpen(false) } }} /> { e.preventDefault() window.requestAnimationFrame(() => sublabelNameInputRef.current?.focus() ) }} > Sous-libellé Sera créé sous « {item.label} » (chemin type Parent/Enfant). setSublabelName(e.target.value)} placeholder="Nom du sous-libellé" autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() addChildLabelRow(item.id, sublabelName) setSublabelOpen(false) } }} /> ) }