feat: implement document preview functionality in DocumentsSidebar and enhance mobile experience with vaul drawer

This commit is contained in:
Anish Sarkar 2026-03-29 04:20:22 +05:30
parent 37e018e94f
commit 838d38a67a
6 changed files with 101 additions and 38 deletions

View file

@ -7,6 +7,7 @@ import { useParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { MarkdownViewer } from "@/components/markdown-viewer";
import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems";
import { DocumentsFilters } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters";
import {
@ -27,6 +28,14 @@ import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
import { FolderTreeView } from "@/components/documents/FolderTreeView";
import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerHandle,
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
import { Spinner } from "@/components/ui/spinner";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
@ -34,7 +43,9 @@ import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { useDocumentSearch } from "@/hooks/use-document-search";
import { useDocuments } from "@/hooks/use-documents";
import { useMediaQuery } from "@/hooks/use-media-query";
import { useIsMobile } from "@/hooks/use-mobile";
import { foldersApiService } from "@/lib/apis/folders-api.service";
import { documentsApiService } from "@/lib/apis/documents-api.service";
import { authenticatedFetch } from "@/lib/auth-utils";
import { queries } from "@/zero/queries/index";
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
@ -81,6 +92,8 @@ export function DocumentsSidebar({
const { data: connectors } = useAtomValue(connectorsAtom);
const connectorCount = connectors?.length ?? 0;
const isMobileLayout = useIsMobile();
const [search, setSearch] = useState("");
const debouncedSearch = useDebouncedValue(search, 250);
const [activeTypes, setActiveTypes] = useState<DocumentTypeEnum[]>([]);
@ -333,6 +346,31 @@ export function DocumentsSidebar({
[]
);
// Document popup viewer state (for tree view "Open" and mobile preview)
const [viewingDoc, setViewingDoc] = useState<DocumentNodeDoc | null>(null);
const [viewingContent, setViewingContent] = useState<string>("");
const [viewingLoading, setViewingLoading] = useState(false);
const handleViewDocumentPopup = useCallback(async (doc: DocumentNodeDoc) => {
setViewingDoc(doc);
setViewingLoading(true);
try {
const fullDoc = await documentsApiService.getDocument({ id: doc.id });
setViewingContent(fullDoc.content);
} catch (err) {
console.error("[DocumentsSidebar] Failed to fetch document content:", err);
setViewingContent("Failed to load document content.");
} finally {
setViewingLoading(false);
}
}, []);
const handleCloseViewer = useCallback(() => {
setViewingDoc(null);
setViewingContent("");
setViewingLoading(false);
}, []);
const handleToggleChatMention = useCallback(
(doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => {
if (isMentioned) {
@ -622,6 +660,11 @@ export function DocumentsSidebar({
mentionedDocIds={mentionedDocIds}
onToggleChatMention={handleToggleChatMention}
isSearchMode={isSearchMode || activeTypes.length > 0}
onOpenInTab={!isMobileLayout ? (doc) => openDocumentTab({
documentId: doc.id,
searchSpaceId,
title: doc.title,
}) : undefined}
/>
) : (
<FolderTreeView
@ -636,18 +679,24 @@ export function DocumentsSidebar({
onMoveFolder={handleMoveFolder}
onCreateFolder={handleCreateFolder}
onPreviewDocument={(doc) => {
openDocumentTab({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
if (isMobileLayout) {
handleViewDocumentPopup(doc);
} else {
openDocumentTab({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}
}}
onEditDocument={(doc) => {
openDocumentTab({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
if (!isMobileLayout) {
openDocumentTab({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}
}}
onDeleteDocument={(doc) => handleDeleteDocument(doc.id)}
onMoveDocument={handleMoveDocument}
@ -675,6 +724,26 @@ export function DocumentsSidebar({
parentFolderName={createFolderParentName}
onConfirm={handleCreateFolderConfirm}
/>
<Drawer open={!!viewingDoc} onOpenChange={(open) => !open && handleCloseViewer()}>
<DrawerContent className="max-h-[85vh] flex flex-col">
<DrawerHandle />
<DrawerHeader className="text-left shrink-0">
<DrawerTitle className="text-base leading-tight break-words">
{viewingDoc?.title}
</DrawerTitle>
</DrawerHeader>
<div className="overflow-y-auto flex-1 min-h-0 px-4 pb-6 select-text text-xs [&_h1]:text-base! [&_h1]:mt-3! [&_h2]:text-sm! [&_h2]:mt-2! [&_h3]:text-xs! [&_h3]:mt-2!">
{viewingLoading ? (
<div className="flex items-center justify-center py-12">
<Spinner size="lg" className="text-muted-foreground" />
</div>
) : (
<MarkdownViewer content={viewingContent} />
)}
</div>
</DrawerContent>
</Drawer>
</>
);