Skip to content

Commit 66269be

Browse files
authored
Merge pull request #558 from mfts/feat/dndkit
feat: make dataroom sortable by index
2 parents 8d27ad6 + c6ba266 commit 66269be

26 files changed

Lines changed: 1057 additions & 438 deletions

File tree

components/datarooms/dataroom-document-card.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import { useTheme } from "next-themes";
1010
import { toast } from "sonner";
1111
import { mutate } from "swr";
1212

13-
import { MoveToFolderModal } from "@/components/documents/move-folder-modal";
1413
import BarChart from "@/components/shared/icons/bar-chart";
15-
import Check from "@/components/shared/icons/check";
16-
import Copy from "@/components/shared/icons/copy";
1714
import NotionIcon from "@/components/shared/icons/notion";
1815
import { Button } from "@/components/ui/button";
1916
import {
@@ -27,20 +24,21 @@ import {
2724

2825
import { type DataroomFolderDocument } from "@/lib/swr/use-dataroom";
2926
import { type DocumentWithLinksAndLinkCountAndViewCount } from "@/lib/types";
30-
import { nFormatter, timeAgo } from "@/lib/utils";
31-
import { useCopyToClipboard } from "@/lib/utils/use-copy-to-clipboard";
27+
import { cn, nFormatter, timeAgo } from "@/lib/utils";
3228

3329
import { MoveToDataroomFolderModal } from "./move-dataroom-folder-modal";
3430

3531
type DocumentsCardProps = {
3632
document: DataroomFolderDocument;
3733
teamInfo: TeamContextType | null;
3834
dataroomId: string;
35+
isDragging?: boolean;
3936
};
4037
export default function DataroomDocumentCard({
4138
document: dataroomDocument,
4239
teamInfo,
4340
dataroomId,
41+
isDragging,
4442
}: DocumentsCardProps) {
4543
const { theme, systemTheme } = useTheme();
4644
const isLight =
@@ -147,9 +145,24 @@ export default function DataroomDocumentCard({
147145
}
148146
};
149147

148+
const handleCardClick = (e: React.MouseEvent) => {
149+
if (isDragging) {
150+
e.preventDefault();
151+
e.stopPropagation();
152+
return;
153+
}
154+
router.push(`/documents/${dataroomDocument.document.id}`);
155+
};
156+
150157
return (
151158
<>
152-
<li className="group/row relative flex items-center justify-between rounded-lg border-0 p-3 ring-1 ring-gray-200 transition-all hover:bg-secondary hover:ring-gray-300 dark:bg-secondary dark:ring-gray-700 hover:dark:ring-gray-500 sm:p-4">
159+
<div
160+
onClick={handleCardClick}
161+
className={cn(
162+
"group/row relative flex items-center justify-between rounded-lg border-0 bg-white p-3 ring-1 ring-gray-200 transition-all hover:bg-secondary hover:ring-gray-300 dark:bg-secondary dark:ring-gray-700 hover:dark:ring-gray-500 sm:p-4",
163+
isDragging ? "cursor-grabbing" : "cursor-pointer",
164+
)}
165+
>
153166
<div className="flex min-w-0 shrink items-center space-x-2 sm:space-x-4">
154167
<div className="mx-0.5 flex w-8 items-center justify-center text-center sm:mx-1">
155168
{dataroomDocument.document.type === "notion" ? (
@@ -167,13 +180,7 @@ export default function DataroomDocumentCard({
167180
<div className="flex-col">
168181
<div className="flex items-center">
169182
<h2 className="min-w-0 max-w-[150px] truncate text-sm font-semibold leading-6 text-foreground sm:max-w-md">
170-
<Link
171-
href={`/documents/${dataroomDocument.document.id}`}
172-
className="w-full truncate"
173-
>
174-
<span>{dataroomDocument.document.name}</span>
175-
<span className="absolute inset-0" />
176-
</Link>
183+
{dataroomDocument.document.name}
177184
</h2>
178185
</div>
179186
<div className="mt-1 flex items-center space-x-1 text-xs leading-5 text-muted-foreground">
@@ -216,7 +223,12 @@ export default function DataroomDocumentCard({
216223
</DropdownMenuTrigger>
217224
<DropdownMenuContent align="end" ref={dropdownRef}>
218225
<DropdownMenuLabel>Actions</DropdownMenuLabel>
219-
<DropdownMenuItem onClick={() => setMoveFolderOpen(true)}>
226+
<DropdownMenuItem
227+
onClick={(e) => {
228+
e.stopPropagation();
229+
setMoveFolderOpen(true);
230+
}}
231+
>
220232
<FolderInputIcon className="mr-2 h-4 w-4" />
221233
Move to folder
222234
</DropdownMenuItem>
@@ -239,7 +251,7 @@ export default function DataroomDocumentCard({
239251
</DropdownMenuContent>
240252
</DropdownMenu>
241253
</div>
242-
</li>
254+
</div>
243255
{moveFolderOpen ? (
244256
<MoveToDataroomFolderModal
245257
open={moveFolderOpen}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { useState } from "react";
2+
3+
import { TeamContextType } from "@/context/team-context";
4+
5+
import { EmptyDocuments } from "@/components/documents/empty-document";
6+
import FolderCard from "@/components/documents/folder-card";
7+
import { UploadNotificationDrawer } from "@/components/upload-notification";
8+
import UploadZone from "@/components/upload-zone";
9+
10+
import {
11+
DataroomFolderDocument,
12+
DataroomFolderWithCount,
13+
} from "@/lib/swr/use-dataroom";
14+
15+
import DataroomDocumentCard from "./dataroom-document-card";
16+
17+
type FolderOrDocument =
18+
| (DataroomFolderWithCount & { itemType: "folder" })
19+
| (DataroomFolderDocument & { itemType: "document" });
20+
21+
export function DataroomItemsList({
22+
mixedItems,
23+
teamInfo,
24+
folderPathName,
25+
dataroomId,
26+
}: {
27+
mixedItems: FolderOrDocument[] | [];
28+
teamInfo: TeamContextType | null;
29+
folderPathName?: string[];
30+
dataroomId: string;
31+
}) {
32+
const [uploads, setUploads] = useState<
33+
{ fileName: string; progress: number; documentId?: string }[]
34+
>([]);
35+
const [rejectedFiles, setRejectedFiles] = useState<
36+
{ fileName: string; message: string }[]
37+
>([]);
38+
39+
const [showDrawer, setShowDrawer] = useState(false);
40+
41+
const renderItem = (item: FolderOrDocument) => {
42+
const itemId = `${item.itemType}-${item.id}`;
43+
44+
return (
45+
<>
46+
{item.itemType === "folder" ? (
47+
<FolderCard
48+
key={itemId}
49+
folder={item}
50+
teamInfo={teamInfo}
51+
isDataroom={!!dataroomId}
52+
dataroomId={dataroomId}
53+
/>
54+
) : (
55+
<DataroomDocumentCard
56+
key={itemId}
57+
document={item as DataroomFolderDocument}
58+
teamInfo={teamInfo}
59+
dataroomId={dataroomId}
60+
/>
61+
)}
62+
</>
63+
);
64+
};
65+
66+
return (
67+
<>
68+
<UploadZone
69+
folderPathName={folderPathName?.join("/")}
70+
onUploadStart={(newUploads) => {
71+
setUploads(newUploads);
72+
setShowDrawer(true);
73+
}}
74+
onUploadProgress={(index, progress, documentId) => {
75+
setUploads((prevUploads) =>
76+
prevUploads.map((upload, i) =>
77+
i === index ? { ...upload, progress, documentId } : upload,
78+
),
79+
);
80+
}}
81+
onUploadRejected={(rejected) => {
82+
setRejectedFiles(rejected);
83+
setShowDrawer(true);
84+
}}
85+
setUploads={setUploads}
86+
setRejectedFiles={setRejectedFiles}
87+
dataroomId={dataroomId}
88+
>
89+
<ul role="list" className="space-y-4">
90+
{mixedItems.map(renderItem)}
91+
</ul>
92+
93+
{mixedItems.length === 0 && (
94+
<div className="flex h-full justify-center">
95+
<EmptyDocuments />
96+
</div>
97+
)}
98+
</UploadZone>
99+
{showDrawer ? (
100+
<UploadNotificationDrawer
101+
open={showDrawer}
102+
onOpenChange={setShowDrawer}
103+
uploads={uploads}
104+
setUploads={setUploads}
105+
rejectedFiles={rejectedFiles}
106+
setRejectedFiles={setRejectedFiles}
107+
/>
108+
) : null}
109+
</>
110+
);
111+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from "react";
2+
3+
import { useSortable } from "@dnd-kit/sortable";
4+
import { CSS } from "@dnd-kit/utilities";
5+
6+
export type ItemCategory = "folder" | "document";
7+
8+
interface SortableItemProps {
9+
id: string;
10+
category: ItemCategory;
11+
children: React.ReactElement;
12+
}
13+
14+
export const SortableItem: React.FC<SortableItemProps> = ({
15+
id,
16+
category,
17+
children,
18+
}) => {
19+
const { attributes, listeners, setNodeRef, transform, isDragging } =
20+
useSortable({
21+
id: id,
22+
data: {
23+
category: category,
24+
id: id.replace(category, ""),
25+
},
26+
});
27+
28+
const style = {
29+
transform: CSS.Transform.toString(transform),
30+
opacity: isDragging ? 0.5 : 1,
31+
};
32+
33+
const childWithProps = React.cloneElement(children, {
34+
isDragging,
35+
});
36+
37+
return (
38+
<li
39+
ref={setNodeRef}
40+
style={style}
41+
{...attributes}
42+
{...listeners}
43+
className="cursor-move *:pointer-events-none"
44+
>
45+
{childWithProps}
46+
</li>
47+
);
48+
};

0 commit comments

Comments
 (0)