"use client" import { Inbox, Star, Clock, ClockArrowUp, Send, FileText, Tag, ChevronDown, ChevronRight, Pencil, ShoppingCart, MapPin, Share2, Bell, MessageSquare, BadgePercent, Plus, Bot, Folder, MoreVertical, Sparkles, Newspaper, LayoutGrid, Rss, CreditCard, Mail, ShieldAlert, Check, } from "lucide-react" import { cn, formatCount } from "@/lib/utils" import { readXsMatches } from "@/hooks/use-xs" import { useState, useRef, useEffect, useMemo, type ReactNode, type CSSProperties } from "react" import { useEmailDropTarget } from "@/lib/drag-context" import { useComposeActions } from "@/lib/compose-context" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { type FolderTreeNode } 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 { UltiMailLogo } from "@/components/ultimail-logo" 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" /** Retourne les ids des parents à ouvrir pour afficher `targetId`, ou null. */ function ancestorFolderIdsForTarget( nodes: FolderTreeNode[], targetId: string, chain: string[] = [] ): string[] | null { for (const n of nodes) { if (n.id === targetId) return chain if (n.children?.length) { const found = ancestorFolderIdsForTarget(n.children, targetId, [...chain, n.id]) if (found) return found } } return null } function folderSubtreeContainsId(node: FolderTreeNode, targetId: string): boolean { if (node.id === targetId) return true return node.children?.some((c) => folderSubtreeContainsId(c, targetId)) ?? false } interface SidebarProps { selectedFolder: string onSelectFolder: (folder: string) => void collapsed: boolean /** Nombre de messages non lus par id de ligne (boîte, catégorie, dossier, libellé). */ folderUnreadCounts?: Record } const mainItems = [ { id: "inbox", label: "Boîte de réception", icon: Inbox }, { id: "starred", label: "Messages suivis", icon: Star }, { id: "snoozed", label: "En attente", icon: Clock }, { id: "important", label: "Important", icon: Tag }, { id: "sent", label: "Messages envoyés", icon: Send }, { id: "drafts", label: "Brouillons", icon: FileText }, { id: "scheduled", label: "Planifié", icon: ClockArrowUp }, { id: "spam", label: "Indésirables", icon: ShieldAlert }, ] const categoryItemsSource = [ { id: "purchases", label: "Achats", icon: ShoppingCart }, { id: "travel", label: "Déplacements", icon: MapPin }, { id: "social", label: "Réseaux sociaux", icon: Share2 }, { id: "notifications", label: "Notifications", icon: Bell }, { id: "updates", label: "Mises à jour", icon: Sparkles }, { id: "forums", label: "Forums", icon: MessageSquare }, { id: "finance", label: "Finance", icon: CreditCard }, { id: "promotions", label: "Promotions", icon: BadgePercent }, ] /** Ids catégories : menu ⋮ (Afficher / Masquer) du survol. */ const CATEGORY_MENU_IDS = new Set(categoryItemsSource.map((c) => c.id)) /** Catégories affichées sous « Plus » uniquement (Mises à jour, Finance, …). */ const CATEGORY_IDS_IN_PLUS_ONLY = new Set(["updates", "finance"]) /** Liens secondaires sous la liste (jusqu’à Gérer les abonnements). */ const sidebarSecondaryActions = [ { id: "customize-inbox", label: "Personnaliser la zone de réception", icon: LayoutGrid }, { id: "manage-sections", label: "Gérer les sections", icon: Newspaper }, { id: "manage-news", label: "Gérer les actualités", icon: Rss }, { id: "manage-subscriptions", label: "Gérer les abonnements", icon: Mail }, ] as const const hasPlusOnlyExtras = categoryItemsSource.some((c) => CATEGORY_IDS_IN_PLUS_ONLY.has(c.id)) || sidebarSecondaryActions.length > 0 /** Pastilles sous-menu « Couleur du libellé » (démo UI). */ const LABEL_MENU_COLOR_SWATCHES = [ "bg-gray-500", "bg-red-400", "bg-orange-400", "bg-amber-500", "bg-yellow-400", "bg-lime-500", "bg-emerald-500", "bg-teal-500", "bg-blue-500", "bg-indigo-500", "bg-purple-500", "bg-pink-500", ] as const function LabelMenuOptionWithCheck({ checked, onPick, children, }: { checked: boolean onPick: () => void children: ReactNode }) { return ( { e.stopPropagation() onPick() }} className="mx-1 flex cursor-pointer items-center justify-between gap-3 px-3 py-2 text-sm text-gray-800 focus:bg-gray-100" > {children} {checked ? ( ) : null} ) } function ContextLabelMenuOptionWithCheck({ checked, onPick, children, }: { checked: boolean onPick: () => void children: ReactNode }) { return ( onPick()} className="mx-1 flex cursor-pointer items-center justify-between gap-3 px-3 py-2 text-sm" > {children} {checked ? ( ) : null} ) } function folderParentSelectOptions(tree: FolderTreeNode[]): { value: string label: string }[] { const out: { value: string; label: string }[] = [ { value: "__root__", label: "Racine" }, ] const walk = (nodes: FolderTreeNode[], depth: number) => { for (const n of nodes) { out.push({ value: n.id, label: `${"\u2003".repeat(depth * 2)}${n.label}`, }) if (n.children?.length) walk(n.children, depth + 1) } } walk(tree, 0) return out } type CategoryNavSourceItem = (typeof categoryItemsSource)[number] /** Pill à droite seulement quand le fond d’accent est visible (évite frange sur fond neutre). */ function navRowRoundedWhenActive(active: boolean) { return active ? "rounded-r-full" : "rounded-r-none hover:rounded-r-full" } /** Colonne droite : compteur et ⋮ partagent le même emplacement (style Gmail). */ function SidebarOverflowColumn({ unread, menuOpen, hoverGroup, isSelected, hasUnread, className, children, }: { unread: number menuOpen: boolean hoverGroup: "folderrow" | "labelrow" | "catnav" isSelected?: boolean hasUnread?: boolean className?: string children: ReactNode }) { const countHoverHide = `group-hover/${hoverGroup}:opacity-0` const menuHoverShow = `group-hover/${hoverGroup}:opacity-100 [&:has(button:focus-visible)]:opacity-100` return (
{unread > 0 && ( {formatCount(unread)} )}
{children}
) } const sidebarOverflowMenuButtonClass = "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full text-gray-600 outline-none hover:bg-black/8 focus-visible:ring-2 focus-visible:ring-ring/50" function CategoryNavRow({ item, isSelected, isExpanded, unreadCount, onSelectFolder, onHideCategory, onShowCategory, variant = "listed", }: { item: CategoryNavSourceItem isSelected: boolean isExpanded: boolean unreadCount: number onSelectFolder: (id: string) => void onHideCategory: (id: string) => void onShowCategory: (id: string) => void variant?: "listed" | "hidden" }) { const { isOver, dropHandlers } = useEmailDropTarget(item.id, item.label) const [menuOpen, setMenuOpen] = useState(false) const menuTriggerRef = useRef(null) const isHiddenRow = variant === "hidden" const showCategoryMenu = CATEGORY_MENU_IDS.has(item.id) && isExpanded const hasUnread = unreadCount > 0 const handleMenuOpenChange = (open: boolean) => { setMenuOpen(open) if (!open) { queueMicrotask(() => menuTriggerRef.current?.blur()) } } if (isHiddenRow) { return (
{ onShowCategory(item.id) setMenuOpen(false) }} > Afficher Masquer
) } return (
{showCategoryMenu && ( Afficher { onHideCategory(item.id) setMenuOpen(false) }} > Masquer )}
) } export function Sidebar({ selectedFolder, onSelectFolder, collapsed, folderUnreadCounts = {}, }: SidebarProps) { const { openCompose } = useComposeActions() const [hoverExpanded, setHoverExpanded] = useState(false) const [navMoreOpen, setNavMoreOpen] = useState(false) const [expandedFolderIds, setExpandedFolderIds] = useState>(() => new Set()) const [hiddenCategoryIds, setHiddenCategoryIds] = useState>(() => new Set()) const hoverTimeoutRef = useRef(null) const sidebarRef = useRef(null) const isExpanded = !collapsed || hoverExpanded const { folderTree, labelRows, folderIdToLabel, addFolder, addLabelRowFromSidebar, getNavItemPrefs, setNavItemSidebarVisibility, setNavItemMessageVisibility, updateFolderOrLabelColor, renameFolderOrLabel, removeFolderOrLabelRow, moveFolder, addSubfolder, addChildLabelRow, } = useSidebarNav() const visibleNavLabelRows = useMemo(() => { return labelRows.filter((row) => { 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 c of categoryItemsSource) s.add(c.id) for (const k of Object.keys(folderIdToLabel)) s.add(k) return s }, [folderIdToLabel]) useEffect(() => { if (!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 vis = categoryItemsSource.filter((c) => !hiddenCategoryIds.has(c.id)) return { primaryVisibleCategories: vis.filter((c) => !CATEGORY_IDS_IN_PLUS_ONLY.has(c.id)), plusOnlyVisibleCategories: vis.filter((c) => CATEGORY_IDS_IN_PLUS_ONLY.has(c.id)), } }, [hiddenCategoryIds]) const hiddenCategoryItems = useMemo( () => categoryItemsSource.filter((c) => hiddenCategoryIds.has(c.id)), [hiddenCategoryIds] ) const visibleMainItems = useMemo(() => { const scheduledTotal = folderUnreadCounts.scheduled ?? 0 if (scheduledTotal > 0) return mainItems return mainItems.filter((item) => item.id !== "scheduled") }, [folderUnreadCounts.scheduled]) const hideCategory = (id: string) => { setHiddenCategoryIds((prev) => { const next = new Set(prev) next.add(id) return next }) } const showCategory = (id: string) => { setHiddenCategoryIds((prev) => { const next = new Set(prev) next.delete(id) return next }) } 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(() => { if (hiddenCategoryIds.has(selectedFolder)) { onSelectFolder("inbox") } }, [hiddenCategoryIds, 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 (readXsMatches()) return if (collapsed) { hoverTimeoutRef.current = setTimeout(() => { setHoverExpanded(true) }, 300) } } const handleMouseLeave = () => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current) hoverTimeoutRef.current = null } if (readXsMatches()) return setHoverExpanded(false) } 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" /** 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 } isSelected: boolean unreadCount: number }) => { const { isOver, dropHandlers } = useEmailDropTarget(item.id, item.label) const hasUnread = unreadCount > 0 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 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) useEffect(() => { setRenameDraft(node.label) }, [node.label]) const handleMenuOpenChange = (open: boolean) => { setMenuOpen(open) if (!open) { queueMicrotask(() => menuTriggerRef.current?.blur()) } } const prefs = getNavItemPrefs(node.id) const moveTargets = useMemo( () => folderMoveParentOptions(folderTree, node.id), [folderTree, node.id] ) const folderMenuSurface = "min-w-[240px] border-gray-200 bg-white p-0 py-1.5 shadow-md" 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 flex h-8 w-full min-w-0 shrink-0 cursor-default items-center gap-2 pr-3 text-sm", isSelected || isOver ? "rounded-r-full" : "rounded-r-none", isStickyBranch && "sticky border-b border-gray-200/70", isStickyBranch && !isSelected && "bg-app-canvas", isSelected && "bg-[#d3e3fd] font-medium text-gray-900", !isSelected && hasUnread && "text-gray-900", isOver && "bg-yellow-100 text-gray-900" ) const rowStyle: CSSProperties = { paddingLeft: 24 + depth * 16, ...(isStickyBranch ? { top: stickyTopPx, zIndex: 30 - depth } : {}), } const overflowMenu = ( {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 ) return ( <>
{hasChildren ? ( ) : ( )} {overflowMenu}
{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
{ 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 renderExpandedFolderSubtree = ( nodes: FolderTreeNode[], depth: number ): ReactNode => 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 }) .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 isHighlighted = folderSubtreeContainsId(node, selectedFolder) const unread = folderUnreadCounts[node.id] ?? 0 const hasUnread = unread > 0 return ( ) } 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 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) useEffect(() => { setRenameDraft(item.label) }, [item.label]) const handleMenuOpenChange = (open: boolean) => { setMenuOpen(open) if (!open) { queueMicrotask(() => menuTriggerRef.current?.blur()) } } const prefs = getNavItemPrefs(item.id) const labelDotClass = item.color ?? "bg-gray-400" const labelMenuSurface = "min-w-[240px] border-gray-200 bg-white p-0 py-1.5 shadow-md" 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 flex h-8 w-full min-w-0 shrink-0 cursor-default items-center pl-6 pr-2 transition-colors", navRowRoundedWhenActive(isSelected || isOver), isSelected ? "bg-[#d3e3fd] text-gray-900 font-medium" : isOver ? "bg-yellow-100 text-gray-900" : hasUnread ? "text-gray-900 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100" ) const overflowMenu = labelRowExpanded ? ( {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 return ( <>
{overflowMenu}
{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é
{ 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) } }} /> ) } return ( ) }