833 lines
29 KiB
TypeScript
833 lines
29 KiB
TypeScript
"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_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 { NavColorPicker } from "@/components/gmail/nav/nav-color-picker"
|
|
import { normalizeNavColorClass } from "@/lib/nav-color"
|
|
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<string, number>
|
|
expandedFolderIds: Set<string>
|
|
isExpanded: boolean
|
|
isOverlayOpen: boolean
|
|
touchNav: boolean
|
|
folderTree: FolderTreeNode[]
|
|
onSelectFolder: (id: string) => void
|
|
toggleFolderExpanded: (id: string) => void
|
|
getNavItemPrefs: (id: string) => Required<Pick<NavItemPrefs, "sidebar" | "messages">>
|
|
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 = normalizeNavColorClass(node.color)
|
|
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<HTMLButtonElement>(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<HTMLInputElement>(null)
|
|
const subfolderNameInputRef = useRef<HTMLInputElement>(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 (
|
|
<Sub>
|
|
<SubTr
|
|
className={cn(
|
|
MAIL_SIDEBAR_MENU_SUB_TRIGGER_CLASS,
|
|
subKind === "context" && "flex items-center gap-2"
|
|
)}
|
|
>
|
|
<span className="flex size-5 shrink-0 items-center justify-center rounded-full border border-border bg-mail-surface">
|
|
<span
|
|
className={cn(
|
|
"block size-3 rounded-sm border border-black/10",
|
|
dotClass
|
|
)}
|
|
aria-hidden
|
|
/>
|
|
</span>
|
|
<span className="flex-1 text-left text-sm">Couleur du dossier</span>
|
|
</SubTr>
|
|
<SubCo className={MAIL_SIDEBAR_COLOR_PICKER_CLASS}>
|
|
<NavColorPicker
|
|
variant="menu"
|
|
value={dotClass}
|
|
swatches={LABEL_MENU_COLOR_SWATCHES}
|
|
onChange={(sw) => {
|
|
updateFolderOrLabelColor(node.id, sw)
|
|
setMenuOpen(false)
|
|
}}
|
|
/>
|
|
</SubCo>
|
|
</Sub>
|
|
)
|
|
}
|
|
|
|
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 = (
|
|
<SidebarOverflowColumn
|
|
unread={unread}
|
|
menuOpen={menuOpen || sheetOpen}
|
|
hoverGroup="folderrow"
|
|
isSelected={isSelected}
|
|
hasUnread={hasUnread}
|
|
className={cn(!isExpanded && "hidden", "mr-[-11px]")}
|
|
showMenuButton={!touchNav}
|
|
>
|
|
{!touchNav && (
|
|
<DropdownMenu open={menuOpen} onOpenChange={handleMenuOpenChange}>
|
|
<DropdownMenuTrigger asChild>
|
|
<button
|
|
ref={menuTriggerRef}
|
|
type="button"
|
|
className={cn(sidebarOverflowMenuButtonClass, isSelected && "text-gray-900")}
|
|
aria-label={`Options pour ${node.label}`}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<MoreVertical className="h-4 w-4" />
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className={folderMenuSurface}>
|
|
{colorSub("dropdown")}
|
|
<DropdownMenuSeparator className={MAIL_SIDEBAR_MENU_SEPARATOR_CLASS} />
|
|
<DropdownMenuLabel className="px-3 py-1 text-[11px] font-normal normal-case tracking-normal text-muted-foreground">
|
|
Dans la liste des dossiers
|
|
</DropdownMenuLabel>
|
|
<LabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "show"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "show")}
|
|
>
|
|
Afficher
|
|
</LabelMenuOptionWithCheck>
|
|
<LabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "showUnread"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "showUnread")}
|
|
>
|
|
Afficher si messages non lus
|
|
</LabelMenuOptionWithCheck>
|
|
<LabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "hide"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "hide")}
|
|
>
|
|
Masquer
|
|
</LabelMenuOptionWithCheck>
|
|
<DropdownMenuSeparator className={MAIL_SIDEBAR_MENU_SEPARATOR_CLASS} />
|
|
<DropdownMenuLabel className="px-3 py-1 text-[11px] font-normal normal-case tracking-normal text-muted-foreground">
|
|
Dans la liste des messages
|
|
</DropdownMenuLabel>
|
|
<LabelMenuOptionWithCheck
|
|
checked={prefs.messages === "show"}
|
|
onPick={() => setNavItemMessageVisibility(node.id, "show")}
|
|
>
|
|
Afficher
|
|
</LabelMenuOptionWithCheck>
|
|
<LabelMenuOptionWithCheck
|
|
checked={prefs.messages === "hide"}
|
|
onPick={() => setNavItemMessageVisibility(node.id, "hide")}
|
|
>
|
|
Masquer
|
|
</LabelMenuOptionWithCheck>
|
|
<DropdownMenuSeparator className={MAIL_SIDEBAR_MENU_SEPARATOR_CLASS} />
|
|
<DropdownMenuItem
|
|
className={MAIL_SIDEBAR_MENU_PLAIN_ITEM_CLASS}
|
|
onClick={() => {
|
|
setRenameDraft(node.label)
|
|
setRenameOpen(true)
|
|
setMenuOpen(false)
|
|
}}
|
|
>
|
|
Renommer…
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
className={MAIL_SIDEBAR_MENU_PLAIN_ITEM_CLASS}
|
|
onClick={() => {
|
|
setMoveParent("__root__")
|
|
setMoveOpen(true)
|
|
setMenuOpen(false)
|
|
}}
|
|
>
|
|
Déplacer…
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
className={MAIL_SIDEBAR_MENU_PLAIN_ITEM_CLASS}
|
|
onClick={() => {
|
|
setSubfolderName("")
|
|
setSubfolderOpen(true)
|
|
setMenuOpen(false)
|
|
}}
|
|
>
|
|
Nouveau sous-dossier…
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
variant="destructive"
|
|
className="mx-1 cursor-pointer px-3 py-2 text-sm focus:bg-destructive/15"
|
|
onClick={() => {
|
|
removeFolderOrLabelRow(node.id)
|
|
setMenuOpen(false)
|
|
}}
|
|
>
|
|
Supprimer le dossier
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</SidebarOverflowColumn>
|
|
)
|
|
|
|
const folderOptionsSheet = touchNav && isExpanded && (
|
|
<SidebarNavOptionsSheet
|
|
open={sheetOpen}
|
|
onOpenChange={setSheetOpen}
|
|
title={node.label}
|
|
colorDotClass={dotClass}
|
|
>
|
|
<SidebarNavSheetColorPicker
|
|
title="Couleur du dossier"
|
|
dotClass={dotClass}
|
|
swatches={LABEL_MENU_COLOR_SWATCHES}
|
|
onPick={(sw) => {
|
|
updateFolderOrLabelColor(node.id, sw)
|
|
closeSheet()
|
|
}}
|
|
/>
|
|
<SidebarNavSheetDivider />
|
|
<SidebarNavSheetSectionLabel>Dans la liste des dossiers</SidebarNavSheetSectionLabel>
|
|
<SidebarNavSheetCheckOption
|
|
checked={prefs.sidebar === "show"}
|
|
onPick={() => {
|
|
setNavItemSidebarVisibility(node.id, "show")
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Afficher
|
|
</SidebarNavSheetCheckOption>
|
|
<SidebarNavSheetCheckOption
|
|
checked={prefs.sidebar === "showUnread"}
|
|
onPick={() => {
|
|
setNavItemSidebarVisibility(node.id, "showUnread")
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Afficher si messages non lus
|
|
</SidebarNavSheetCheckOption>
|
|
<SidebarNavSheetCheckOption
|
|
checked={prefs.sidebar === "hide"}
|
|
onPick={() => {
|
|
setNavItemSidebarVisibility(node.id, "hide")
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Masquer
|
|
</SidebarNavSheetCheckOption>
|
|
<SidebarNavSheetDivider />
|
|
<SidebarNavSheetSectionLabel>Dans la liste des messages</SidebarNavSheetSectionLabel>
|
|
<SidebarNavSheetCheckOption
|
|
checked={prefs.messages === "show"}
|
|
onPick={() => {
|
|
setNavItemMessageVisibility(node.id, "show")
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Afficher
|
|
</SidebarNavSheetCheckOption>
|
|
<SidebarNavSheetCheckOption
|
|
checked={prefs.messages === "hide"}
|
|
onPick={() => {
|
|
setNavItemMessageVisibility(node.id, "hide")
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Masquer
|
|
</SidebarNavSheetCheckOption>
|
|
<SidebarNavSheetDivider />
|
|
<SidebarNavSheetAction
|
|
onClick={() => {
|
|
setRenameDraft(node.label)
|
|
setRenameOpen(true)
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Renommer…
|
|
</SidebarNavSheetAction>
|
|
<SidebarNavSheetAction
|
|
onClick={() => {
|
|
setMoveParent("__root__")
|
|
setMoveOpen(true)
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Déplacer…
|
|
</SidebarNavSheetAction>
|
|
<SidebarNavSheetAction
|
|
onClick={() => {
|
|
setSubfolderName("")
|
|
setSubfolderOpen(true)
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Nouveau sous-dossier…
|
|
</SidebarNavSheetAction>
|
|
<SidebarNavSheetAction
|
|
destructive
|
|
onClick={() => {
|
|
removeFolderOrLabelRow(node.id)
|
|
closeSheet()
|
|
}}
|
|
>
|
|
Supprimer le dossier
|
|
</SidebarNavSheetAction>
|
|
</SidebarNavOptionsSheet>
|
|
)
|
|
|
|
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<HTMLSpanElement>) => {
|
|
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 = (
|
|
<div
|
|
data-nav-row
|
|
{...touchRowProps}
|
|
onDragEnter={onFolderRowDragEnter}
|
|
onDragOver={onFolderRowDragOver}
|
|
onDragLeave={onFolderRowDragLeave}
|
|
onDrop={onFolderRowDrop}
|
|
className={rowClass}
|
|
style={rowStyle}
|
|
>
|
|
{isExpanded ? (
|
|
<SidebarNavDragHandle
|
|
label={node.label}
|
|
onDragStart={onFolderDragHandleStart}
|
|
onDragEnd={clearNavDrag}
|
|
/>
|
|
) : null}
|
|
<div
|
|
role="button"
|
|
tabIndex={0}
|
|
onClick={() => 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 ? (
|
|
<button
|
|
type="button"
|
|
draggable={false}
|
|
className="flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded outline-none hover:bg-black/5 focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
aria-expanded={isBranchOpen}
|
|
aria-label={
|
|
isBranchOpen
|
|
? `Replier le dossier ${node.label}`
|
|
: `Déplier le dossier ${node.label}`
|
|
}
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
toggleFolderExpanded(node.id)
|
|
}}
|
|
>
|
|
<SidebarNavIconSlot showUnreadDot={hasUnread}>
|
|
<FolderTreeNavIcon
|
|
hasChildren
|
|
open={isBranchOpen}
|
|
colorBgClass={dotClass}
|
|
/>
|
|
</SidebarNavIconSlot>
|
|
</button>
|
|
) : (
|
|
<SidebarNavIconSlot showUnreadDot={hasUnread}>
|
|
<FolderTreeNavIcon
|
|
hasChildren={false}
|
|
open={false}
|
|
colorBgClass={dotClass}
|
|
/>
|
|
</SidebarNavIconSlot>
|
|
)}
|
|
<div className="flex min-w-0 flex-1 items-baseline gap-3">
|
|
<span className="min-w-0 flex-1 truncate leading-5">
|
|
<span
|
|
className={cn(
|
|
hasUnread && !isSelected && "font-semibold text-gray-900"
|
|
)}
|
|
>
|
|
{node.label}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{overflowMenu}
|
|
</div>
|
|
)
|
|
|
|
return (
|
|
<>
|
|
{touchNav ? (
|
|
folderRowEl
|
|
) : (
|
|
<ContextMenu onOpenChange={setContextMenuOpen}>
|
|
<ContextMenuTrigger asChild>{folderRowEl}</ContextMenuTrigger>
|
|
<ContextMenuContent className={folderMenuSurface}>
|
|
{colorSub("context")}
|
|
<ContextMenuSeparator className={MAIL_SIDEBAR_MENU_SEPARATOR_CLASS} />
|
|
<ContextMenuLabel className="px-3 py-1 text-[11px] font-normal normal-case tracking-normal text-muted-foreground">
|
|
Dans la liste des dossiers
|
|
</ContextMenuLabel>
|
|
<ContextLabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "show"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "show")}
|
|
>
|
|
Afficher
|
|
</ContextLabelMenuOptionWithCheck>
|
|
<ContextLabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "showUnread"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "showUnread")}
|
|
>
|
|
Afficher si non lus
|
|
</ContextLabelMenuOptionWithCheck>
|
|
<ContextLabelMenuOptionWithCheck
|
|
checked={prefs.sidebar === "hide"}
|
|
onPick={() => setNavItemSidebarVisibility(node.id, "hide")}
|
|
>
|
|
Masquer
|
|
</ContextLabelMenuOptionWithCheck>
|
|
<ContextMenuSeparator className={MAIL_SIDEBAR_MENU_SEPARATOR_CLASS} />
|
|
<ContextMenuLabel className="px-3 py-1 text-[11px] font-normal normal-case tracking-normal text-muted-foreground">
|
|
Dans la liste des messages
|
|
</ContextMenuLabel>
|
|
<ContextLabelMenuOptionWithCheck
|
|
checked={prefs.messages === "show"}
|
|
onPick={() => setNavItemMessageVisibility(node.id, "show")}
|
|
>
|
|
Afficher
|
|
</ContextLabelMenuOptionWithCheck>
|
|
<ContextLabelMenuOptionWithCheck
|
|
checked={prefs.messages === "hide"}
|
|
onPick={() => setNavItemMessageVisibility(node.id, "hide")}
|
|
>
|
|
Masquer
|
|
</ContextLabelMenuOptionWithCheck>
|
|
<ContextMenuSeparator className="my-1.5 bg-gray-200" />
|
|
<ContextMenuItem
|
|
className="mx-1 cursor-pointer px-3 py-2 text-sm"
|
|
onClick={() => {
|
|
setRenameDraft(node.label)
|
|
setRenameOpen(true)
|
|
}}
|
|
>
|
|
Renommer…
|
|
</ContextMenuItem>
|
|
<ContextMenuItem
|
|
className="mx-1 cursor-pointer px-3 py-2 text-sm"
|
|
onClick={() => {
|
|
setMoveParent("__root__")
|
|
setMoveOpen(true)
|
|
}}
|
|
>
|
|
Déplacer…
|
|
</ContextMenuItem>
|
|
<ContextMenuItem
|
|
className="mx-1 cursor-pointer px-3 py-2 text-sm"
|
|
onClick={() => {
|
|
setSubfolderName("")
|
|
setSubfolderOpen(true)
|
|
}}
|
|
>
|
|
Nouveau sous-dossier…
|
|
</ContextMenuItem>
|
|
<ContextMenuItem
|
|
variant="destructive"
|
|
className="mx-1 cursor-pointer px-3 py-2 text-sm"
|
|
onClick={() => removeFolderOrLabelRow(node.id)}
|
|
>
|
|
Supprimer le dossier
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
)}
|
|
{folderOptionsSheet}
|
|
|
|
<Dialog open={renameOpen} onOpenChange={setRenameOpen}>
|
|
<DialogContent
|
|
className="sm:max-w-md"
|
|
showCloseButton
|
|
onOpenAutoFocus={(e) => {
|
|
e.preventDefault()
|
|
window.requestAnimationFrame(() =>
|
|
folderRenameInputRef.current?.focus()
|
|
)
|
|
}}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle>Renommer le dossier</DialogTitle>
|
|
<DialogDescription>Nouveau nom pour « {node.label} ».</DialogDescription>
|
|
</DialogHeader>
|
|
<Input
|
|
ref={folderRenameInputRef}
|
|
value={renameDraft}
|
|
onChange={(e) => setRenameDraft(e.target.value)}
|
|
autoComplete="off"
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault()
|
|
renameFolderOrLabel(node.id, renameDraft)
|
|
setRenameOpen(false)
|
|
}
|
|
}}
|
|
/>
|
|
<DialogFooter>
|
|
<Button variant="outline" type="button" onClick={() => setRenameOpen(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
onClick={() => {
|
|
renameFolderOrLabel(node.id, renameDraft)
|
|
setRenameOpen(false)
|
|
}}
|
|
>
|
|
Enregistrer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={moveOpen} onOpenChange={setMoveOpen}>
|
|
<DialogContent className="sm:max-w-md" showCloseButton>
|
|
<DialogHeader>
|
|
<DialogTitle>Déplacer le dossier</DialogTitle>
|
|
<DialogDescription>Choisissez le dossier parent.</DialogDescription>
|
|
</DialogHeader>
|
|
<Select value={moveParent} onValueChange={setMoveParent}>
|
|
<SelectTrigger className="w-full min-w-0" size="sm">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent position="popper" className="max-h-72">
|
|
{moveTargets.map((o) => (
|
|
<SelectItem key={o.value} value={o.value}>
|
|
{o.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<DialogFooter>
|
|
<Button variant="outline" type="button" onClick={() => setMoveOpen(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
onClick={() => {
|
|
moveFolder(
|
|
node.id,
|
|
moveParent === "__root__" ? null : moveParent
|
|
)
|
|
setMoveOpen(false)
|
|
}}
|
|
>
|
|
Déplacer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={subfolderOpen} onOpenChange={setSubfolderOpen}>
|
|
<DialogContent
|
|
className="sm:max-w-md"
|
|
showCloseButton
|
|
onOpenAutoFocus={(e) => {
|
|
e.preventDefault()
|
|
window.requestAnimationFrame(() =>
|
|
subfolderNameInputRef.current?.focus()
|
|
)
|
|
}}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle>Nouveau sous-dossier</DialogTitle>
|
|
<DialogDescription>Sous « {node.label} ».</DialogDescription>
|
|
</DialogHeader>
|
|
<Input
|
|
ref={subfolderNameInputRef}
|
|
value={subfolderName}
|
|
onChange={(e) => setSubfolderName(e.target.value)}
|
|
placeholder="Nom du dossier"
|
|
autoComplete="off"
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault()
|
|
addSubfolder(node.id, subfolderName)
|
|
setSubfolderOpen(false)
|
|
}
|
|
}}
|
|
/>
|
|
<DialogFooter>
|
|
<Button variant="outline" type="button" onClick={() => setSubfolderOpen(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
onClick={() => {
|
|
addSubfolder(node.id, subfolderName)
|
|
setSubfolderOpen(false)
|
|
}}
|
|
>
|
|
Créer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|