"use client" import { useState, useRef, useEffect, useMemo, type CSSProperties, type DragEvent, } from "react" import { MoreVertical } from "lucide-react" import { cn } from "@/lib/utils" import { useEmailDropTarget } from "@/lib/drag-context" import { MAIL_SIDEBAR_BLUR_SURFACE_CLASS, 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import type { FolderTreeNode } from "@/lib/sidebar-nav-data" import { folderMoveParentOptions } from "@/lib/sidebar-nav-context" import type { LabelInMessageListVisibility, LabelListSidebarVisibility, NavItemPrefs, } from "@/lib/sidebar-nav-context" import { ancestorFolderIdsForTarget } from "@/lib/sidebar-folder-tree-utils" import { readSidebarNavDragData, resolveNavDropPlacement, setSidebarNavDragData, } from "@/lib/sidebar-nav-dnd" import { LABEL_MENU_COLOR_SWATCHES, mailSidebarFolderBranchStickyTopPx, mailSidebarFolderBranchStickyZ, } 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, SidebarNavDragHandle, SidebarNavIconSlot, FolderTreeNavIcon, SidebarOverflowColumn, sidebarOverflowMenuButtonClass, navRowActivate, } from "@/components/gmail/sidebar/sidebar-nav-primitives" import type { SidebarNavDragBindings } from "@/components/gmail/sidebar/sidebar-nav-drag-bindings" export type SidebarFolderRowExpandedProps = SidebarNavDragBindings & { node: FolderTreeNode depth: number selectedFolder: string folderUnreadCounts: Record expandedFolderIds: Set isExpanded: boolean isOverlayOpen: boolean touchNav: boolean folderTree: FolderTreeNode[] onSelectFolder: (id: string) => void toggleFolderExpanded: (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 moveFolder: (id: string, parentId: string | null) => void addSubfolder: (parentId: string, name: string) => void } export function SidebarFolderRowExpanded({ node, depth, selectedFolder, folderUnreadCounts, expandedFolderIds, isExpanded, isOverlayOpen, touchNav, folderTree, onSelectFolder, toggleFolderExpanded, getNavItemPrefs, setNavItemSidebarVisibility, setNavItemMessageVisibility, updateFolderOrLabelColor, renameFolderOrLabel, removeFolderOrLabelRow, moveFolder, addSubfolder, navDragRef, navDropPlacementRef, beginNavDrag, clearNavDrag, updateNavDropTarget, clearNavDropTarget, commitNavDrop, }: SidebarFolderRowExpandedProps) { const { isOver, dropHandlers } = useEmailDropTarget(node.id, node.label) const hasChildren = !!(node.children?.length) const isBranchOpen = expandedFolderIds.has(node.id) const dotClass = node.color ?? "bg-gray-400" const isSelected = selectedFolder === node.id const unread = folderUnreadCounts[node.id] ?? 0 const hasUnread = unread > 0 const isStickyBranch = hasChildren && isBranchOpen const stickyTopPx = mailSidebarFolderBranchStickyTopPx(depth) const stickyZIndex = mailSidebarFolderBranchStickyZ(depth) const [menuOpen, setMenuOpen] = useState(false) const [contextMenuOpen, setContextMenuOpen] = useState(false) const menuTriggerRef = useRef(null) const [renameOpen, setRenameOpen] = useState(false) const [renameDraft, setRenameDraft] = useState(node.label) const [moveOpen, setMoveOpen] = useState(false) const [moveParent, setMoveParent] = useState("__root__") const [subfolderOpen, setSubfolderOpen] = useState(false) const [subfolderName, setSubfolderName] = useState("") const folderRenameInputRef = useRef(null) const subfolderNameInputRef = useRef(null) const { sheetOpen, setSheetOpen, touchRowProps, touchRowClassName, closeSheet } = useSidebarTouchOptionsMenu(touchNav && isExpanded) useEffect(() => { setRenameDraft(node.label) }, [node.label]) const handleMenuOpenChange = (open: boolean) => { setMenuOpen(open) if (!open) { queueMicrotask(() => menuTriggerRef.current?.blur()) } } const rowHoverHeld = !isSelected && !isOver && (contextMenuOpen || menuOpen || sheetOpen) const prefs = getNavItemPrefs(node.id) const moveTargets = useMemo( () => folderMoveParentOptions(folderTree, node.id), [folderTree, node.id] ) const folderMenuSurface = 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 dossier
{LABEL_MENU_COLOR_SWATCHES.map((sw) => (
) } const rowClass = cn( "group/folderrow relative flex h-8 w-full min-w-0 shrink-0 cursor-default items-center gap-2 pr-3 text-sm transition-colors", isSelected || isOver || rowHoverHeld ? "rounded-r-full" : "rounded-r-none", isStickyBranch && "sticky border-b border-gray-200/70", isStickyBranch && MAIL_SIDEBAR_BLUR_SURFACE_CLASS, isSelected && "bg-mail-nav-selected font-medium text-mail-nav-selected", !isSelected && hasUnread && "text-gray-900", isOver && "bg-mail-nav-drop text-foreground", rowHoverHeld && "bg-mail-nav-hover text-foreground", touchRowClassName ) const rowStyle: CSSProperties = { paddingLeft: 24 + depth * 16, ...(isStickyBranch ? { top: stickyTopPx, zIndex: stickyZIndex } : {}), } const overflowMenu = ( {!touchNav && ( {colorSub("dropdown")} Dans la liste des dossiers setNavItemSidebarVisibility(node.id, "show")} > Afficher setNavItemSidebarVisibility(node.id, "showUnread")} > Afficher si messages non lus setNavItemSidebarVisibility(node.id, "hide")} > Masquer Dans la liste des messages setNavItemMessageVisibility(node.id, "show")} > Afficher setNavItemMessageVisibility(node.id, "hide")} > Masquer { setRenameDraft(node.label) setRenameOpen(true) setMenuOpen(false) }} > Renommer… { setMoveParent("__root__") setMoveOpen(true) setMenuOpen(false) }} > Déplacer… { setSubfolderName("") setSubfolderOpen(true) setMenuOpen(false) }} > Nouveau sous-dossier… { removeFolderOrLabelRow(node.id) setMenuOpen(false) }} > Supprimer le dossier )} ) const folderOptionsSheet = touchNav && isExpanded && ( { updateFolderOrLabelColor(node.id, sw) closeSheet() }} /> Dans la liste des dossiers { setNavItemSidebarVisibility(node.id, "show") closeSheet() }} > Afficher { setNavItemSidebarVisibility(node.id, "showUnread") closeSheet() }} > Afficher si messages non lus { setNavItemSidebarVisibility(node.id, "hide") closeSheet() }} > Masquer Dans la liste des messages { setNavItemMessageVisibility(node.id, "show") closeSheet() }} > Afficher { setNavItemMessageVisibility(node.id, "hide") closeSheet() }} > Masquer { setRenameDraft(node.label) setRenameOpen(true) closeSheet() }} > Renommer… { setMoveParent("__root__") setMoveOpen(true) closeSheet() }} > Déplacer… { setSubfolderName("") setSubfolderOpen(true) closeSheet() }} > Nouveau sous-dossier… { removeFolderOrLabelRow(node.id) closeSheet() }} > Supprimer le dossier ) const onFolderRowDragEnter = (e: DragEvent) => { const active = navDragRef.current if (active?.kind === "folder" && active.id !== node.id) { e.preventDefault() return } dropHandlers.onDragEnter(e) } const onFolderRowDragOver = (e: DragEvent) => { const active = navDragRef.current if (active?.kind === "folder") { e.preventDefault() e.stopPropagation() if (active.id === node.id) return const ancestors = ancestorFolderIdsForTarget(folderTree, node.id) if (ancestors?.includes(active.id)) return e.dataTransfer.dropEffect = "move" updateNavDropTarget( e.currentTarget as HTMLElement, resolveNavDropPlacement(e, true) ) return } dropHandlers.onDragOver(e) } const onFolderRowDragLeave = (e: DragEvent) => { if (navDragRef.current?.kind === "folder") { 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 onFolderRowDrop = (e: DragEvent) => { const payload = readSidebarNavDragData(e, navDragRef.current) if (payload?.kind === "folder") { e.preventDefault() e.stopPropagation() const placement = navDropPlacementRef.current ?? resolveNavDropPlacement(e, true) commitNavDrop(payload, node.id, placement, "folder") return } dropHandlers.onDrop(e) } const onFolderDragHandleStart = (e: DragEvent) => { const payload = { kind: "folder" as const, id: node.id } setSidebarNavDragData(e, payload) const rowEl = (e.currentTarget as HTMLElement).closest("[data-nav-row]") as HTMLElement | null beginNavDrag(payload, rowEl) } const folderRowEl = (
{isExpanded ? ( ) : null}
onSelectFolder(node.id)} onKeyDown={(e) => navRowActivate(e, () => onSelectFolder(node.id))} className={cn( "flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-3 py-0 pr-1 text-left transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring/50", !isSelected && !isOver && !rowHoverHeld && "rounded-r-none hover:rounded-r-full hover:bg-mail-nav-hover", rowHoverHeld && !isSelected && !isOver && "rounded-r-full", isSelected ? "text-gray-900" : isOver ? "text-gray-900" : "text-gray-700" )} > {hasChildren ? ( ) : ( )}
{node.label}
{overflowMenu}
) return ( <> {touchNav ? ( folderRowEl ) : ( {folderRowEl} {colorSub("context")} Dans la liste des dossiers setNavItemSidebarVisibility(node.id, "show")} > Afficher setNavItemSidebarVisibility(node.id, "showUnread")} > Afficher si non lus setNavItemSidebarVisibility(node.id, "hide")} > Masquer Dans la liste des messages setNavItemMessageVisibility(node.id, "show")} > Afficher setNavItemMessageVisibility(node.id, "hide")} > Masquer { setRenameDraft(node.label) setRenameOpen(true) }} > Renommer… { setMoveParent("__root__") setMoveOpen(true) }} > Déplacer… { setSubfolderName("") setSubfolderOpen(true) }} > Nouveau sous-dossier… removeFolderOrLabelRow(node.id)} > Supprimer le dossier )} {folderOptionsSheet} { e.preventDefault() window.requestAnimationFrame(() => folderRenameInputRef.current?.focus() ) }} > Renommer le dossier Nouveau nom pour « {node.label} ». setRenameDraft(e.target.value)} autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() renameFolderOrLabel(node.id, renameDraft) setRenameOpen(false) } }} /> Déplacer le dossier Choisissez le dossier parent. { e.preventDefault() window.requestAnimationFrame(() => subfolderNameInputRef.current?.focus() ) }} > Nouveau sous-dossier Sous « {node.label} ». setSubfolderName(e.target.value)} placeholder="Nom du dossier" autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() addSubfolder(node.id, subfolderName) setSubfolderOpen(false) } }} /> ) }