feat: Replaced document preview with a new fullscreen viewer

This commit is contained in:
2025-12-11 00:44:11 +01:00
parent f6bd6e2feb
commit e8636ce7da
15 changed files with 177 additions and 173 deletions
+4 -4
View File
@@ -16,7 +16,7 @@ import {
sortCorrespondents,
} from './components/DocumentSummarySection';
import DocumentDownloadLink from '../documents/components/DocumentDownloadLink';
import { usePreviewContext } from './PreviewContext';
import { useFullscreenPreviewContext } from './FullscreenPreviewContext';
import type { DocumentSummarySectionProps } from './components/DocumentSummarySection';
import { extractDocumentMetadataPayload } from './logic/documentSummary';
import { resolveDocumentAssetUrl } from '../lib/assets/AssetManager';
@@ -218,14 +218,14 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
[hasOcr, loadOcrContent],
);
const { openPreview } = usePreviewContext();
const { openFullscreenPreview } = useFullscreenPreviewContext();
const handleZoomOpen = useCallback(() => {
if (!document) {
return;
}
openPreview(document);
}, [document, openPreview]);
openFullscreenPreview(document);
}, [document, openFullscreenPreview]);
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
@@ -1,33 +1,33 @@
import React, { useState, useCallback, useMemo, useRef } from 'react';
import PreviewZoomOverlay from './components/PreviewZoomOverlay';
import FullscreenPreviewOverlay from './components/FullscreenPreviewOverlay';
import type { Document } from '../types/documents';
import type { Identifier } from '../types/identifiers';
import { createSafeContext } from '../utils/createSafeContext';
interface PreviewContextType {
openPreview: (doc: Document) => void;
closePreview: () => void;
interface FullscreenPreviewContextType {
openFullscreenPreview: (doc: Document) => void;
closeFullscreenPreview: () => void;
}
const [PreviewContext, usePreviewContext] = createSafeContext<PreviewContextType>('Preview');
const [FullscreenPreviewContext, useFullscreenPreviewContext] = createSafeContext<FullscreenPreviewContextType>('FullscreenPreview');
interface PreviewProviderProps {
interface FullscreenPreviewProviderProps {
children: React.ReactNode;
onNavigate?: (documentId: Identifier) => void;
}
export const PreviewProvider: React.FC<PreviewProviderProps> = ({ children, onNavigate }) => {
export const FullscreenPreviewProvider: React.FC<FullscreenPreviewProviderProps> = ({ children, onNavigate }) => {
const [previewDoc, setPreviewDoc] = useState<Document | null>(null);
const lastFocusedElement = useRef<HTMLElement | null>(null);
const openPreview = useCallback((doc: Document) => {
const openFullscreenPreview = useCallback((doc: Document) => {
if (!lastFocusedElement.current) {
lastFocusedElement.current = document.activeElement as HTMLElement;
}
setPreviewDoc(doc);
}, []);
const closePreview = useCallback(() => {
const closeFullscreenPreview = useCallback(() => {
setPreviewDoc(null);
if (lastFocusedElement.current) {
lastFocusedElement.current.focus();
@@ -38,26 +38,26 @@ export const PreviewProvider: React.FC<PreviewProviderProps> = ({ children, onNa
const handleMaximize = useCallback(() => {
if (previewDoc && onNavigate) {
onNavigate(previewDoc.id);
closePreview();
closeFullscreenPreview();
}
}, [previewDoc, onNavigate, closePreview]);
}, [previewDoc, onNavigate, closeFullscreenPreview]);
const value = useMemo(() => ({
openPreview,
closePreview,
}), [openPreview, closePreview]);
openFullscreenPreview,
closeFullscreenPreview,
}), [openFullscreenPreview, closeFullscreenPreview]);
return (
<PreviewContext.Provider value={value}>
<FullscreenPreviewContext.Provider value={value}>
{children}
<PreviewZoomOverlay
<FullscreenPreviewOverlay
open={Boolean(previewDoc)}
onClose={closePreview}
onClose={closeFullscreenPreview}
onMaximize={onNavigate ? handleMaximize : undefined}
document={previewDoc}
/>
</PreviewContext.Provider>
</FullscreenPreviewContext.Provider>
);
};
export { usePreviewContext };
export { useFullscreenPreviewContext };
@@ -7,14 +7,14 @@ import { IconX, FileInfoIcon } from '../../components/icons';
import type { Document } from '../../types/documents';
interface PreviewZoomOverlayProps {
interface FullscreenPreviewOverlayProps {
open?: boolean;
onClose: () => void;
onMaximize?: () => void;
document?: Document | null;
}
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
const FullscreenPreviewOverlay: React.FC<FullscreenPreviewOverlayProps> = ({
open = false,
onClose,
onMaximize,
@@ -81,12 +81,12 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
}
};
const stageClassName = 'preview-zoom__stage';
const containerClassName = 'preview-zoom__scroll';
const stageClassName = 'fullscreen-preview__stage';
const containerClassName = 'fullscreen-preview__scroll';
const backdropClassName = [
'preview-zoom-backdrop',
isBackdropVisible ? 'preview-zoom-backdrop--visible' : '',
'fullscreen-preview-backdrop',
isBackdropVisible ? 'fullscreen-preview-backdrop--visible' : '',
]
.filter(Boolean)
.join(' ');
@@ -160,4 +160,4 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
);
};
export default PreviewZoomOverlay;
export default FullscreenPreviewOverlay;
@@ -1,4 +1,4 @@
.preview-zoom-backdrop {
.fullscreen-preview-backdrop {
position: fixed;
inset: 0;
background: var(--overlay-backdrop);
@@ -14,13 +14,13 @@
-webkit-backdrop-filter: blur(8px);
}
.preview-zoom-backdrop--visible {
.fullscreen-preview-backdrop--visible {
opacity: 1;
background: var(--overlay-backdrop);
pointer-events: auto;
}
.preview-zoom__stage {
.fullscreen-preview__stage {
position: relative;
display: flex;
align-items: center;
@@ -32,7 +32,7 @@
--header-clearance: calc(2.5rem + 0.5rem);
}
.preview-zoom__scroll .document-viewer__object--image {
.fullscreen-preview__scroll .document-viewer__object--image {
width: auto;
height: auto;
box-shadow: 0 32px 120px var(--shadow-deep);
@@ -42,7 +42,7 @@
touch-action: manipulation;
}
.preview-zoom__scroll {
.fullscreen-preview__scroll {
display: flex;
align-items: center;
justify-content: center;
@@ -55,21 +55,21 @@
--pdf-viewer-stack-padding: var(--preview-padding);
}
.preview-zoom__scroll:focus {
.fullscreen-preview__scroll:focus {
outline: none;
}
.preview-zoom__scroll .document-viewer__object--pdf {
.fullscreen-preview__scroll .document-viewer__object--pdf {
width: 100%;
height: 100%;
}
.preview-zoom__stage .pdf-viewer__page-wrapper {
.fullscreen-preview__stage .pdf-viewer__page-wrapper {
position: relative;
overflow: visible;
}
.preview-zoom__stage .pdf-viewer__page-wrapper::after {
.fullscreen-preview__stage .pdf-viewer__page-wrapper::after {
content: '';
position: absolute;
inset: 0;
@@ -80,7 +80,7 @@
}
/* Header positioning for preview zoom overlay */
.preview-zoom-backdrop .panel-header--dark {
.fullscreen-preview-backdrop .panel-header--dark {
position: absolute;
top: 0;
left: 0;
@@ -94,6 +94,6 @@
}
/* Prevent header clicks from closing backdrop */
.preview-zoom-backdrop .panel-header--dark * {
.fullscreen-preview-backdrop .panel-header--dark * {
pointer-events: auto;
}
@@ -22,7 +22,7 @@ interface UseDetailWorkspaceArgs {
folderNodes: Map<Identifier | 'root', FolderNode>;
detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>;
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
openDocumentPreview?: (args: { documentIds: Identifier[] }) => void;
openDocumentViewer?: (args: { documentIds: Identifier[] }) => void;
handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise<boolean> | boolean;
handleDocumentTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
@@ -54,7 +54,7 @@ const useDetailWorkspace = ({
folderNodes,
detailPanelControlRef,
detailFolderFetchRef,
openDocumentPreview,
openDocumentViewer,
handleDocumentTitleUpdate,
handleDocumentIssuedUpdate,
handleDocumentTagAdd,
@@ -176,7 +176,7 @@ const useDetailWorkspace = ({
correspondentLookupById,
onTagAdd: handleDetailTagAdd,
onTagRemove: handleDetailTagRemove,
onOpenPreview: openDocumentPreview,
onOpenViewer: openDocumentViewer,
onUpdateTitle: handleDocumentTitleUpdate,
onUpdateIssued: handleDocumentIssuedUpdate,
ensureAssetUrl,