Fix drag and drop on collapsed folders
This commit is contained in:
parent
a1f069c0ae
commit
df8772de37
4 changed files with 49 additions and 52 deletions
|
|
@ -1,11 +1,11 @@
|
|||
import { Fragment } from 'react';
|
||||
import { Fragment, KeyboardEvent } from 'react';
|
||||
import { Dialog, DialogPanel, DialogTitle, Input, Transition, TransitionChild } from '@headlessui/react';
|
||||
|
||||
interface CreateFolderDialogProps {
|
||||
isOpen: boolean;
|
||||
folderName: string;
|
||||
onFolderNameChange: (name: string) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useState, DragEvent } from 'react';
|
||||
import { Button } from '@headlessui/react';
|
||||
import { DocumentListItem } from './DocumentListItem';
|
||||
import { Folder, DocumentListDocument } from '@/types/documents';
|
||||
|
|
@ -11,12 +11,9 @@ interface DocumentFolderProps {
|
|||
sortedDocuments: DocumentListDocument[];
|
||||
onDocumentDelete: (doc: DocumentListDocument) => void;
|
||||
draggedDoc: DocumentListDocument | null;
|
||||
dropTargetDoc: DocumentListDocument | null;
|
||||
onDragStart: (doc: DocumentListDocument) => void;
|
||||
onDragEnd: () => void;
|
||||
onDragOver: (e: React.DragEvent, doc: DocumentListDocument) => void;
|
||||
onDragLeave: () => void;
|
||||
onDrop: (e: React.DragEvent, doc: DocumentListDocument) => void;
|
||||
onDrop: (e: DragEvent, folderId: string) => void;
|
||||
}
|
||||
|
||||
const ChevronIcon = ({ className = "w-5 h-5" }) => (
|
||||
|
|
@ -37,38 +34,33 @@ export function DocumentFolder({
|
|||
sortedDocuments,
|
||||
onDocumentDelete,
|
||||
draggedDoc,
|
||||
dropTargetDoc,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
onDragOver,
|
||||
onDragLeave,
|
||||
onDrop,
|
||||
}: DocumentFolderProps) {
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const isDropTarget = isHovering && draggedDoc && !draggedDoc.folderId;
|
||||
const isDropTarget = isHovering && draggedDoc && !draggedDoc.folderId && draggedDoc.id !== folder.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsHovering(true);
|
||||
if (draggedDoc && !draggedDoc.folderId) {
|
||||
onDragOver(e, { ...draggedDoc, folderId: folder.id });
|
||||
setIsHovering(true);
|
||||
}
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsHovering(false);
|
||||
onDragLeave();
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsHovering(false);
|
||||
if (!draggedDoc || draggedDoc.folderId) return;
|
||||
onDrop(e, { ...draggedDoc, folderId: folder.id });
|
||||
onDrop(e, folder.id);
|
||||
}}
|
||||
className={`rounded-lg p-2 transition-all bg-offbase shadow hover:shadow-md ${
|
||||
isDropTarget ? 'ring-2 ring-accent bg-primary/10' : ''
|
||||
|
|
@ -105,13 +97,10 @@ export function DocumentFolder({
|
|||
key={`${doc.type}-${doc.id}`}
|
||||
doc={doc}
|
||||
onDelete={onDocumentDelete}
|
||||
dragEnabled={true}
|
||||
dragEnabled={false} // Documents in folders can't be dragged to other documents
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={onDrop}
|
||||
isDropTarget={dropTargetDoc?.id === doc.id}
|
||||
isDropTarget={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { useCallback, useState, useEffect, DragEvent, KeyboardEvent } from 'react';
|
||||
import { useDocuments } from '@/contexts/DocumentContext';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
|
@ -172,7 +172,7 @@ export function DocumentList() {
|
|||
setDropTargetDoc(null);
|
||||
}, []);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent, doc: DocumentListDocument) => {
|
||||
const handleDragOver = useCallback((e: DragEvent, doc: DocumentListDocument) => {
|
||||
e.preventDefault();
|
||||
if (draggedDoc && draggedDoc.id !== doc.id && !draggedDoc.folderId) {
|
||||
// Only highlight target if neither document is in a folder
|
||||
|
|
@ -186,26 +186,14 @@ export function DocumentList() {
|
|||
setDropTargetDoc(null);
|
||||
}, []);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent, targetDoc: DocumentListDocument) => {
|
||||
const handleDrop = useCallback((e: DragEvent, targetDoc: DocumentListDocument) => {
|
||||
e.preventDefault();
|
||||
console.log('Dropped', draggedDoc?.name, 'on', targetDoc.name);
|
||||
|
||||
if (!draggedDoc || draggedDoc.id === targetDoc.id || draggedDoc.folderId) return;
|
||||
|
||||
// If target has a folderId, we're dropping into an existing folder
|
||||
if (targetDoc.folderId) {
|
||||
const targetFolder = folders.find(f => f.id === targetDoc.folderId);
|
||||
if (targetFolder && !targetFolder.documents.some(d => d.id === draggedDoc.id)) {
|
||||
setFolders(prev => prev.map(f => {
|
||||
if (f.id === targetDoc.folderId) {
|
||||
return {
|
||||
...f,
|
||||
documents: [...f.documents, { ...draggedDoc, folderId: f.id }]
|
||||
};
|
||||
}
|
||||
return f;
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// Create new folder when dropping on an unfoldered document
|
||||
// If target doc is unfoldered, create a new folder
|
||||
if (!targetDoc.folderId) {
|
||||
setPendingFolderDocs({
|
||||
source: draggedDoc,
|
||||
target: targetDoc
|
||||
|
|
@ -213,6 +201,25 @@ export function DocumentList() {
|
|||
setNewFolderName('');
|
||||
}
|
||||
|
||||
setDraggedDoc(null);
|
||||
setDropTargetDoc(null);
|
||||
}, [draggedDoc]);
|
||||
|
||||
const handleFolderDrop = useCallback((e: DragEvent, folderId: string) => {
|
||||
e.preventDefault();
|
||||
if (!draggedDoc || draggedDoc.folderId) return;
|
||||
|
||||
// Add document to existing folder
|
||||
setFolders(folders.map(f => {
|
||||
if (f.id === folderId && !f.documents.some(d => d.id === draggedDoc.id)) {
|
||||
return {
|
||||
...f,
|
||||
documents: [...f.documents, { ...draggedDoc, folderId }]
|
||||
};
|
||||
}
|
||||
return f;
|
||||
}));
|
||||
|
||||
setDraggedDoc(null);
|
||||
setDropTargetDoc(null);
|
||||
}, [draggedDoc, folders]);
|
||||
|
|
@ -255,7 +262,7 @@ export function DocumentList() {
|
|||
setShowHint(false);
|
||||
}, [pendingFolderDocs, newFolderName]);
|
||||
|
||||
const handleFolderNameKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const handleFolderNameKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
createFolder();
|
||||
|
|
@ -316,16 +323,12 @@ export function DocumentList() {
|
|||
sortedDocuments={sortDocuments(folder.documents)}
|
||||
onDocumentDelete={setDocumentToDelete}
|
||||
draggedDoc={draggedDoc}
|
||||
dropTargetDoc={dropTargetDoc}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onDrop={handleFolderDrop}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
{sortDocuments(unfolderedDocuments).map(doc => (
|
||||
<DocumentListItem
|
||||
key={`${doc.type}-${doc.id}`}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Link from 'next/link';
|
||||
import { DragEvent } from 'react';
|
||||
import { Button } from '@headlessui/react';
|
||||
import { PDFIcon, EPUBIcon } from '@/components/icons/Icons';
|
||||
import { DocumentListDocument } from '@/types/documents';
|
||||
|
|
@ -9,9 +10,9 @@ interface DocumentListItemProps {
|
|||
dragEnabled?: boolean;
|
||||
onDragStart?: (doc: DocumentListDocument) => void;
|
||||
onDragEnd?: () => void;
|
||||
onDragOver?: (e: React.DragEvent, doc: DocumentListDocument) => void;
|
||||
onDragOver?: (e: DragEvent, doc: DocumentListDocument) => void;
|
||||
onDragLeave?: () => void;
|
||||
onDrop?: (e: React.DragEvent, doc: DocumentListDocument) => void;
|
||||
onDrop?: (e: DragEvent, doc: DocumentListDocument) => void;
|
||||
isDropTarget?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -26,17 +27,21 @@ export function DocumentListItem({
|
|||
onDrop,
|
||||
isDropTarget = false,
|
||||
}: DocumentListItemProps) {
|
||||
// Only allow drag and drop interactions for documents not in folders
|
||||
const isDraggable = dragEnabled && !doc.folderId;
|
||||
const allowDropTarget = !doc.folderId;
|
||||
|
||||
return (
|
||||
<div
|
||||
draggable={dragEnabled && !doc.folderId}
|
||||
draggable={isDraggable}
|
||||
onDragStart={() => onDragStart?.(doc)}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragOver={(e) => onDragOver?.(e, doc)}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={(e) => onDrop?.(e, doc)}
|
||||
onDragOver={(e) => allowDropTarget && onDragOver?.(e, doc)}
|
||||
onDragLeave={() => allowDropTarget && onDragLeave?.()}
|
||||
onDrop={(e) => allowDropTarget && onDrop?.(e, doc)}
|
||||
className={`
|
||||
w-full
|
||||
${!doc.folderId && isDropTarget ? 'ring-2 ring-accent bg-primary/10' : ''}
|
||||
${allowDropTarget && isDropTarget ? 'ring-2 ring-accent bg-primary/10' : ''}
|
||||
bg-background rounded-lg p-2 shadow hover:shadow-md transition-shadow
|
||||
`}
|
||||
>
|
||||
|
|
|
|||
Loading…
Reference in a new issue