'use client'; import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'; import { Fragment, useRef, type CSSProperties, type ReactNode } from 'react'; import { useDrop } from 'react-dnd'; import type { Folder, SidebarFilter } from '@/types/documents'; import { PDFIcon, EPUBIcon, FileIcon, DotsHorizontalIcon } from '@/components/icons/Icons'; import { FolderIcon, HomeIcon, ClockIcon, FolderPlusIcon } from './finderIcons'; import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes'; interface FinderSidebarProps { filter: SidebarFilter; onFilterChange: (filter: SidebarFilter) => void; folders: Folder[]; counts: { all: number; pdf: number; epub: number; html: number }; onDeleteFolder: (folderId: string) => void; onNewFolder: () => void; onClearFolders: () => void; /** When dragging onto a folder row, move dropped docs into that folder. */ onDropOnFolder: (folderId: string, item: DocumentDragItem) => void; /** Width controls (desktop only). */ width: number; onWidthChange: (px: number) => void; topSlot?: ReactNode; bottomSlot?: ReactNode; /** Fired for explicit row/button actions (used to close mobile drawer). */ onRowAction?: () => void; } const MIN_WIDTH = 168; const MAX_WIDTH = 320; interface SidebarRowProps { active: boolean; onClick: () => void; icon: ReactNode; label: string; count?: number; countClassName?: string; trailing?: ReactNode; isDropTarget?: boolean; } function SidebarRow({ active, onClick, icon, label, count, countClassName, trailing, isDropTarget, }: SidebarRowProps) { return ( ); } function FolderRow({ folder, active, onClick, onDelete, onDropOnFolder, }: { folder: Folder; active: boolean; onClick: () => void; onDelete: () => void; onDropOnFolder: (folderId: string, item: DocumentDragItem) => void; }) { const [{ isOver, canDrop }, dropRef] = useDrop< DocumentDragItem, void, { isOver: boolean; canDrop: boolean } >(() => ({ accept: DND_DOCUMENT, drop: (item) => { onDropOnFolder(folder.id, item); }, canDrop: (item) => { // Don't accept if all items are already in this folder. return item.docs.some((d) => d.folderId !== folder.id); }, collect: (monitor) => ({ isOver: monitor.isOver({ shallow: true }), canDrop: monitor.canDrop(), }), }), [folder.id, onDropOnFolder]); const isDropTarget = isOver && canDrop; return (
} className="group/folder relative" > } label={folder.name} count={folder.documents.length} countClassName="group-hover/folder:-translate-x-6 group-focus-within/folder:-translate-x-6" isDropTarget={isDropTarget} />
); } function SectionHeader({ children, isFirst, rightSlot, }: { children: ReactNode; isFirst?: boolean; rightSlot?: ReactNode; }) { return (
{children} {rightSlot && {rightSlot}}
); } export function FinderSidebar({ filter, onFilterChange, folders, counts, onDeleteFolder, onNewFolder, onClearFolders, onDropOnFolder, width, onWidthChange, topSlot, bottomSlot, onRowAction, }: FinderSidebarProps) { const startRef = useRef<{ x: number; w: number } | null>(null); const onResizeStart = (e: React.PointerEvent) => { (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); startRef.current = { x: e.clientX, w: width }; }; const onResizeMove = (e: React.PointerEvent) => { if (!startRef.current) return; const delta = e.clientX - startRef.current.x; const next = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, startRef.current.w + delta)); onWidthChange(next); }; const onResizeEnd = (e: React.PointerEvent) => { (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); startRef.current = null; }; return ( ); }