"use client" import { Inbox, Star, Clock, ClockArrowUp, Send, FileText, ChevronDown, GripVertical, Pencil, Plus, Bot, Folder, MoreVertical, Newspaper, LayoutGrid, Rss, Mail, ShieldAlert, Check, Trash2, } from "lucide-react" import { cn, formatCount } from "@/lib/utils" import { MAIL_SIDEBAR_COLOR_PICKER_CLASS, MAIL_SIDEBAR_COLOR_SWATCH_RING_CLASS, MAIL_SIDEBAR_MENU_ITEM_CLASS, MAIL_SIDEBAR_MENU_PLAIN_ITEM_CLASS, MAIL_SIDEBAR_MENU_SEPARATOR_CLASS, MAIL_SIDEBAR_MENU_SUB_TRIGGER_CLASS, MAIL_SIDEBAR_MENU_SURFACE_CLASS, mailNavRowClass, } from "@/lib/mail-chrome-classes" import { useIsXs } from "@/hooks/use-xs" import { readTouchNavMatches, useTouchNav } from "@/hooks/use-touch-nav" import { useState, useRef, useEffect, useMemo, useCallback, type ReactNode, type CSSProperties, } from "react" import { useEmailDropTarget } from "@/lib/drag-context" import { readSidebarNavDragData, resolveNavDropPlacement, setSidebarNavDragData, type SidebarNavDragPayload, type SidebarNavDropPlacement, } from "@/lib/sidebar-nav-dnd" import { useComposeActions } from "@/lib/compose-context" import { useMailSettingsStore } from "@/lib/stores/mail-settings-store" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { type FolderTreeNode, isSystemNavLabelId, SYSTEM_NAV_LABEL_DEFAULTS } from "@/lib/sidebar-nav-data" import { folderMoveParentOptions, useSidebarNav } from "@/lib/sidebar-nav-context" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Icon, addCollection } from "@iconify/react" import { icons as mdiIcons } from "@iconify-json/mdi" import { FOLDER_SECTION_ICON, folderTreeNavIconName, navFolderIconColorFromBgClass, } from "@/lib/folder-nav-icons" import { UltiMailLogo } from "@/components/ultimail-logo" import { SidebarNavOptionsSheet, SidebarNavSheetAction, SidebarNavSheetCheckOption, SidebarNavSheetColorPicker, SidebarNavSheetDivider, SidebarNavSheetSectionLabel, } from "@/components/gmail/sidebar-nav-options-sheet" import { useSidebarTouchOptionsMenu } from "@/components/gmail/use-sidebar-touch-options" addCollection(mdiIcons) import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from "@/components/ui/context-menu" import { ancestorFolderIdsForTarget, folderSubtreeContainsId, } from "@/lib/sidebar-folder-tree-utils" import { mainItems, CATEGORY_IDS_IN_PLUS_ONLY, sortSystemLabelRows, sidebarSecondaryActions, hasPlusOnlyExtras, LABEL_MENU_COLOR_SWATCHES, type CategoryNavSourceItem, } from "@/components/gmail/sidebar/sidebar-nav-constants" import { LabelMenuOptionWithCheck, ContextLabelMenuOptionWithCheck, folderParentSelectOptions, navRowRoundedWhenActive, SidebarNavIconSlot, navRowActivate, FolderTreeNavIcon, SidebarNavDragHandle, SidebarOverflowColumn, sidebarOverflowMenuButtonClass, } from "@/components/gmail/sidebar/sidebar-nav-primitives" import { CategoryNavRow } from "@/components/gmail/sidebar/category-nav-row" import { useSidebarNavDrag } from "@/hooks/use-sidebar-nav-drag" import { MAIL_SIDEBAR_PANEL_SURFACE_CLASS, MAIL_SIDEBAR_PANEL_SURFACE_MOBILE_CLASS, } from "@/lib/mail-chrome-classes" interface SidebarProps { selectedFolder: string onSelectFolder: (folder: string) => void collapsed: boolean folderUnreadCounts?: Record splitView?: boolean } export function Sidebar({ selectedFolder, onSelectFolder, collapsed, folderUnreadCounts = {}, splitView = false, }: SidebarProps) { const { openCompose } = useComposeActions() const [hoverExpanded, setHoverExpanded] = useState(false) const [navMoreOpen, setNavMoreOpen] = useState(false) const [expandedFolderIds, setExpandedFolderIds] = useState>(() => new Set()) const hoverTimeoutRef = useRef(null) const sidebarRef = useRef(null) const touchNav = useTouchNav() const isXs = useIsXs() const isExpanded = !collapsed || (!touchNav && hoverExpanded) const isOverlayOpen = touchNav && !collapsed const { folderTree, labelRows, folderIdToLabel, addFolder, addLabelRowFromSidebar, getNavItemPrefs, setNavItemSidebarVisibility, setNavItemMessageVisibility, updateFolderOrLabelColor, renameFolderOrLabel, removeFolderOrLabelRow, moveFolder, reorderLabelRows, moveFolderRelative, addSubfolder, addChildLabelRow, setLabelRowEnabled, } = useSidebarNav() const { navDragRef, navDropPlacementRef, beginNavDrag, clearNavDrag, updateNavDropTarget, clearNavDropTarget, commitNavDrop, } = useSidebarNavDrag({ reorderLabelRows, moveFolderRelative, setExpandedFolderIds, }) const visibleNavLabelRows = useMemo(() => { return labelRows.filter((row) => { if (row.enabled === false) return false if (isSystemNavLabelId(row.id)) return false const p = getNavItemPrefs(row.id) if (p.sidebar === "hide") return false if ( p.sidebar === "showUnread" && (folderUnreadCounts[row.id] ?? 0) === 0 ) { return false } return true }) }, [labelRows, getNavItemPrefs, folderUnreadCounts]) const validNavFolderIds = useMemo(() => { const s = new Set() for (const i of mainItems) s.add(i.id) for (const k of Object.keys(folderIdToLabel)) s.add(k) return s }, [folderIdToLabel]) useEffect(() => { if (selectedFolder !== "search" && !validNavFolderIds.has(selectedFolder)) { onSelectFolder("inbox") } }, [validNavFolderIds, selectedFolder, onSelectFolder]) const [folderDialogOpen, setFolderDialogOpen] = useState(false) const [labelDialogOpen, setLabelDialogOpen] = useState(false) const [newFolderName, setNewFolderName] = useState("") const [newFolderParent, setNewFolderParent] = useState("__root__") const [newLabelName, setNewLabelName] = useState("") const newFolderNameInputRef = useRef(null) const newLabelNameInputRef = useRef(null) const folderParentOptions = useMemo( () => folderParentSelectOptions(folderTree), [folderTree] ) const { primaryVisibleCategories, plusOnlyVisibleCategories } = useMemo(() => { const systemEnabled = sortSystemLabelRows( labelRows.filter((r) => r.enabled !== false && isSystemNavLabelId(r.id)) ).map((r) => ({ id: r.id, label: r.label, icon: r.icon })) return { primaryVisibleCategories: systemEnabled.filter( (c) => !CATEGORY_IDS_IN_PLUS_ONLY.has(c.id) ), plusOnlyVisibleCategories: systemEnabled.filter((c) => CATEGORY_IDS_IN_PLUS_ONLY.has(c.id) ), } }, [labelRows]) const disabledSystemNavItems = useMemo(() => { return sortSystemLabelRows( labelRows.filter((r) => r.enabled === false && isSystemNavLabelId(r.id)) ).map((r) => ({ id: r.id, label: r.label, icon: r.icon })) }, [labelRows]) const visibleMainItems = useMemo(() => { const scheduledTotal = folderUnreadCounts.scheduled ?? 0 if (scheduledTotal > 0) return mainItems return mainItems.filter((item) => item.id !== "scheduled") }, [folderUnreadCounts.scheduled]) const toggleFolderExpanded = (id: string) => { setExpandedFolderIds((prev) => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } const handleSubmitNewFolder = () => { const name = newFolderName.trim() if (!name) return const parentId = newFolderParent === "__root__" ? null : newFolderParent addFolder(parentId, name) setNewFolderName("") setFolderDialogOpen(false) } const handleSubmitNewLabel = () => { const name = newLabelName.trim() if (!name) return addLabelRowFromSidebar(name) setNewLabelName("") setLabelDialogOpen(false) } useEffect(() => { const row = labelRows.find((r) => r.id === selectedFolder) if (row && row.enabled === false) { onSelectFolder("inbox") } }, [labelRows, selectedFolder, onSelectFolder]) useEffect(() => { if (selectedFolder !== "scheduled") return if ((folderUnreadCounts.scheduled ?? 0) > 0) return onSelectFolder("inbox") }, [folderUnreadCounts.scheduled, selectedFolder, onSelectFolder]) useEffect(() => { if (CATEGORY_IDS_IN_PLUS_ONLY.has(selectedFolder) && !navMoreOpen) { setNavMoreOpen(true) } }, [selectedFolder, navMoreOpen]) useEffect(() => { const ancestors = ancestorFolderIdsForTarget(folderTree, selectedFolder) if (ancestors?.length) { setExpandedFolderIds((prev) => { const next = new Set(prev) ancestors.forEach((id) => next.add(id)) return next }) } }, [selectedFolder]) const handleMouseEnter = () => { if (readTouchNavMatches()) return if (collapsed) { hoverTimeoutRef.current = setTimeout(() => { setHoverExpanded(true) }, 300) } } const handleMouseLeave = () => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current) hoverTimeoutRef.current = null } if (readTouchNavMatches()) return setHoverExpanded(false) } useEffect(() => { if (touchNav) setHoverExpanded(false) }, [touchNav, collapsed]) useEffect(() => { return () => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current) } } }, []) /** Inset rows from sidebar right edge (padding works with w-full; margin-right often clips under overflow-x-hidden). */ const navRailInset = "pr-3.5" /** pl-6 + demi-largeur icône nav (h-5) → axe à 34px ; picto split (size-9) centré sur cet axe. */ const splitViewLogoIconClass = "size-9 shrink-0" /** Aligné sur la barre split (pt-1 shell + py-2 + recherche h-12) et le bouton menu size-9. */ const splitViewLogoHeaderClass = "box-border min-h-[80px] pt-3 pl-4 pr-3.5 pb-4" /** Same row geometry collapsed / expanded / hover so icons never jump (h-8, pl-6 icon column). */ const NavItem = ({ item, isSelected, unreadCount, }: { item: { id: string; label: string; icon: React.ElementType | string } isSelected: boolean unreadCount: number }) => { const { isOver, dropHandlers } = useEmailDropTarget(item.id, item.label) const hasUnread = unreadCount > 0 const iconClassName = cn( "h-5 w-5 shrink-0", hasUnread && !isSelected && "text-gray-900" ) return ( ) } const FolderRowExpanded = ({ node, depth, }: { node: FolderTreeNode depth: number }) => { 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 = 32 + depth * 32 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 && !isSelected && !rowHoverHeld && (isOverlayOpen ? "mail-sidebar-overlay-panel" : "bg-app-canvas"), 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: 30 - depth } : {}), } 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: React.DragEvent) => { const active = navDragRef.current if (active?.kind === "folder" && active.id !== node.id) { e.preventDefault() return } dropHandlers.onDragEnter(e) } const onFolderRowDragOver = (e: React.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: React.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: React.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: React.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) } }} /> ) } const sidebarVisibleFolderNodes = (nodes: FolderTreeNode[]) => nodes.filter((node) => { const p = getNavItemPrefs(node.id) if (p.sidebar === "hide") return false if ( p.sidebar === "showUnread" && (folderUnreadCounts[node.id] ?? 0) === 0 ) { return false } return true }) const renderExpandedFolderSubtree = ( nodes: FolderTreeNode[], depth: number ): ReactNode => sidebarVisibleFolderNodes(nodes).map((node) => { const isBranchOpen = expandedFolderIds.has(node.id) const kids = node.children return ( /* Limite le sticky au sous-arbre (évite l’empilement hors contexte). */
{kids?.length && isBranchOpen ? (
{renderExpandedFolderSubtree(kids, depth + 1)}
) : null}
) }) const FolderButtonCollapsed = ({ node }: { node: FolderTreeNode }) => { const { isOver, dropHandlers } = useEmailDropTarget(node.id, node.label) const dotClass = node.color ?? "bg-gray-400" const hasChildFolders = !!(node.children?.length) const isHighlighted = folderSubtreeContainsId(node, selectedFolder) const unread = folderUnreadCounts[node.id] ?? 0 const hasUnread = unread > 0 return ( ) } /** Rail repliée : mêmes dossiers visibles que lorsque les branches sont dépliées. */ const renderCollapsedFolderList = (nodes: FolderTreeNode[]): ReactNode => { const walk = (list: FolderTreeNode[]): ReactNode[] => { const out: ReactNode[] = [] for (const node of sidebarVisibleFolderNodes(list)) { out.push() if (node.children?.length && expandedFolderIds.has(node.id)) { out.push(...walk(node.children)) } } return out } return walk(nodes) } const LabelItemRow = ({ item, unreadCount, isExpanded: labelRowExpanded, }: { item: { id: string; label: string; color: string; count?: number } unreadCount: number isExpanded: boolean }) => { 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: React.DragEvent) => { const active = navDragRef.current if (active?.kind === "label" && active.id !== item.id) { e.preventDefault() return } dropHandlers.onDragEnter(e) } const onLabelRowDragOver = (e: React.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: React.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: React.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: React.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) } }} /> ) } const panelSurfaceClass = isOverlayOpen ? MAIL_SIDEBAR_PANEL_SURFACE_MOBILE_CLASS : MAIL_SIDEBAR_PANEL_SURFACE_CLASS return ( ) }