import type { FC } from "react"; import { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { attachInstruction, extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; import { Pin, PinOff } from "lucide-react"; // plane imports import type { IWorkspaceSidebarNavigationItem } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Tooltip } from "@plane/propel/tooltip"; import { DragHandle, DropIndicator } from "@plane/ui"; import { cn } from "@plane/utils"; // components import { SidebarNavItem } from "@/components/sidebar/sidebar-navigation"; // hooks import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUser, useUserPermissions } from "@/hooks/store/user"; // plane web imports // local imports import { UpgradeBadge } from "../upgrade-badge"; import { getSidebarNavigationItemIcon } from "./helper"; type TExtendedSidebarItemProps = { item: IWorkspaceSidebarNavigationItem; handleOnNavigationItemDrop?: ( sourceId: string | undefined, destinationId: string | undefined, shouldDropAtEnd: boolean ) => void; disableDrag?: boolean; disableDrop?: boolean; isLastChild: boolean; }; export const ExtendedSidebarItem: FC = observer((props) => { const { item, handleOnNavigationItemDrop, disableDrag = false, disableDrop = false, isLastChild } = props; const { t } = useTranslation(); // states const [isDragging, setIsDragging] = useState(false); const [instruction, setInstruction] = useState<"DRAG_OVER" | "DRAG_BELOW" | undefined>(undefined); // refs const navigationIemRef = useRef(null); const dragHandleRef = useRef(null); // nextjs hooks const pathname = usePathname(); const { workspaceSlug } = useParams(); // store hooks const { getNavigationPreferences, updateSidebarPreference } = useWorkspace(); const { toggleExtendedSidebar } = useAppTheme(); const { data } = useUser(); const { allowPermissions } = useUserPermissions(); // derived values const sidebarPreference = getNavigationPreferences(workspaceSlug.toString()); const isPinned = sidebarPreference?.[item.key]?.is_pinned; const handleLinkClick = () => toggleExtendedSidebar(true); if (!allowPermissions(item.access as any, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString())) { return null; } const itemHref = item.key === "your_work" ? `/${workspaceSlug.toString()}${item.href}${data?.id}` : `/${workspaceSlug.toString()}${item.href}`; const isActive = itemHref === pathname; const pinNavigationItem = (workspaceSlug: string, key: string) => { updateSidebarPreference(workspaceSlug, key, { is_pinned: true }); }; const unPinNavigationItem = (workspaceSlug: string, key: string) => { updateSidebarPreference(workspaceSlug, key, { is_pinned: false }); }; const icon = getSidebarNavigationItemIcon(item.key); useEffect(() => { const element = navigationIemRef.current; const dragHandleElement = dragHandleRef.current; if (!element) return; return combine( draggable({ element, canDrag: () => !disableDrag, dragHandle: dragHandleElement ?? undefined, getInitialData: () => ({ id: item.key, dragInstanceId: "NAVIGATION" }), // var1 onDragStart: () => { setIsDragging(true); }, onDrop: () => { setIsDragging(false); }, }), dropTargetForElements({ element, canDrop: ({ source }) => !disableDrop && source?.data?.id !== item.key && source?.data?.dragInstanceId === "NAVIGATION", getData: ({ input, element }) => { const data = { id: item.key }; // attach instruction for last in list return attachInstruction(data, { input, element, currentLevel: 0, indentPerLevel: 0, mode: isLastChild ? "last-in-group" : "standard", }); }, onDrag: ({ self }) => { const extractedInstruction = extractInstruction(self?.data)?.type; // check if the highlight is to be shown above or below setInstruction( extractedInstruction ? extractedInstruction === "reorder-below" && isLastChild ? "DRAG_BELOW" : "DRAG_OVER" : undefined ); }, onDragLeave: () => { setInstruction(undefined); }, onDrop: ({ self, source }) => { setInstruction(undefined); const extractedInstruction = extractInstruction(self?.data)?.type; const currentInstruction = extractedInstruction ? extractedInstruction === "reorder-below" && isLastChild ? "DRAG_BELOW" : "DRAG_OVER" : undefined; if (!currentInstruction) return; const sourceId = source?.data?.id as string | undefined; const destinationId = self?.data?.id as string | undefined; if (handleOnNavigationItemDrop) handleOnNavigationItemDrop(sourceId, destinationId, currentInstruction === "DRAG_BELOW"); }, }) ); }, [isLastChild, handleOnNavigationItemDrop, disableDrag, disableDrop, item.key]); return (
{!disableDrag && ( )} handleLinkClick()} className="group flex-grow">
{icon}

{t(item.labelTranslationKey)}

{item.key === "active_cycles" && (
)} {isPinned ? ( unPinNavigationItem(workspaceSlug.toString(), item.key)} /> ) : ( pinNavigationItem(workspaceSlug.toString(), item.key)} /> )}
{isLastChild && }
); });