2025-06-06 19:38:16 +00:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { FileTreeItem } from "../actions";
|
2025-06-09 19:51:35 +00:00
|
|
|
import { useEffect, useRef } from "react";
|
2025-06-06 19:38:16 +00:00
|
|
|
import clsx from "clsx";
|
|
|
|
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
|
|
|
|
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
|
2025-06-09 19:51:35 +00:00
|
|
|
import { FileTreeItemIcon } from "./fileTreeItemIcon";
|
2025-09-21 22:20:27 +00:00
|
|
|
import Link from "next/link";
|
2025-06-06 19:38:16 +00:00
|
|
|
|
|
|
|
|
export const FileTreeItemComponent = ({
|
|
|
|
|
node,
|
|
|
|
|
isActive,
|
|
|
|
|
depth,
|
|
|
|
|
isCollapsed = false,
|
|
|
|
|
isCollapseChevronVisible = true,
|
2025-09-21 22:20:27 +00:00
|
|
|
href,
|
2025-06-06 19:38:16 +00:00
|
|
|
onClick,
|
2025-09-21 22:20:27 +00:00
|
|
|
onNavigate,
|
2025-06-06 19:38:16 +00:00
|
|
|
parentRef,
|
|
|
|
|
}: {
|
|
|
|
|
node: FileTreeItem,
|
|
|
|
|
isActive: boolean,
|
|
|
|
|
depth: number,
|
|
|
|
|
isCollapsed?: boolean,
|
|
|
|
|
isCollapseChevronVisible?: boolean,
|
2025-09-21 22:20:27 +00:00
|
|
|
href: string,
|
|
|
|
|
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void,
|
|
|
|
|
onNavigate?: (e: { preventDefault: () => void }) => void,
|
2025-08-22 18:48:29 +00:00
|
|
|
parentRef: React.RefObject<HTMLDivElement | null>,
|
2025-06-06 19:38:16 +00:00
|
|
|
}) => {
|
2025-09-21 22:20:27 +00:00
|
|
|
const ref = useRef<HTMLAnchorElement>(null);
|
2025-06-06 19:38:16 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isActive && ref.current) {
|
|
|
|
|
scrollIntoView(ref.current, {
|
|
|
|
|
scrollMode: 'if-needed',
|
|
|
|
|
block: 'center',
|
|
|
|
|
behavior: 'instant',
|
|
|
|
|
// We only want to scroll if the element is hidden vertically
|
|
|
|
|
// in the parent element.
|
|
|
|
|
boundary: () => {
|
|
|
|
|
if (!parentRef.current || !ref.current) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rect = ref.current.getBoundingClientRect();
|
|
|
|
|
const parentRect = parentRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
const completelyAbove = rect.bottom <= parentRect.top;
|
|
|
|
|
const completelyBelow = rect.top >= parentRect.bottom;
|
|
|
|
|
return completelyAbove || completelyBelow;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [isActive, parentRef]);
|
|
|
|
|
|
|
|
|
|
return (
|
2025-09-21 22:20:27 +00:00
|
|
|
<Link
|
2025-06-06 19:38:16 +00:00
|
|
|
ref={ref}
|
2025-09-21 22:20:27 +00:00
|
|
|
href={href}
|
2025-06-06 19:38:16 +00:00
|
|
|
className={clsx("flex flex-row gap-1 items-center hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5", {
|
|
|
|
|
'bg-accent': isActive,
|
|
|
|
|
})}
|
|
|
|
|
style={{ paddingLeft: `${depth * 16}px` }}
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
onClick={onClick}
|
2025-09-21 22:20:27 +00:00
|
|
|
onNavigate={onNavigate}
|
2025-06-06 19:38:16 +00:00
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="flex flex-row gap-1 cursor-pointer w-4 h-4 flex-shrink-0"
|
|
|
|
|
>
|
|
|
|
|
{isCollapseChevronVisible && (
|
|
|
|
|
isCollapsed ? (
|
|
|
|
|
<ChevronRightIcon className="w-4 h-4 flex-shrink-0" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronDownIcon className="w-4 h-4 flex-shrink-0" />
|
|
|
|
|
)
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-06-09 19:51:35 +00:00
|
|
|
<FileTreeItemIcon item={node} />
|
2025-06-06 19:38:16 +00:00
|
|
|
<span className="text-sm">{node.name}</span>
|
2025-09-21 22:20:27 +00:00
|
|
|
</Link>
|
2025-06-06 19:38:16 +00:00
|
|
|
)
|
|
|
|
|
}
|