cleanup
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import DocumentsGrid from '../DocumentsGrid';
|
||||
import DocumentsList from '../DocumentsList';
|
||||
import DesktopWorkspace from '../../desktop/DesktopWorkspace';
|
||||
@@ -6,6 +7,10 @@ import { isTagTransferEvent } from '../tagTransfer';
|
||||
import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay';
|
||||
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer';
|
||||
import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext';
|
||||
import DocumentsPanelHeader, {
|
||||
DocumentsPanelHeaderConfig,
|
||||
DocumentsHeaderBreadcrumb,
|
||||
} from './DocumentsPanelHeader';
|
||||
|
||||
const DEFAULT_GRID_ICON_SIZE = 144;
|
||||
|
||||
@@ -15,6 +20,8 @@ const EntryType = {
|
||||
};
|
||||
|
||||
interface DocumentsPanelProps {
|
||||
headerConfig?: DocumentsPanelHeaderConfig;
|
||||
onBreadcrumbNavigate?: (crumb: DocumentsHeaderBreadcrumb) => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -23,6 +30,8 @@ const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
|
||||
export type PreviewEntryLike = { url?: string | null; contentType?: string | null };
|
||||
|
||||
const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
|
||||
headerConfig,
|
||||
onBreadcrumbNavigate,
|
||||
currentFolderName: _currentFolderName,
|
||||
breadcrumbs,
|
||||
subfolders,
|
||||
@@ -56,7 +65,7 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
|
||||
previewEntries,
|
||||
ensureDownloadUrl,
|
||||
deskWorkspaceProps = null,
|
||||
}) => {
|
||||
}): ReactNode => {
|
||||
const {
|
||||
selectedEntries,
|
||||
focusedRowKey,
|
||||
@@ -123,20 +132,8 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
|
||||
() => new Set(activeCorrespondentIds || []),
|
||||
[activeCorrespondentIds],
|
||||
);
|
||||
const scrollRef = useRef(null);
|
||||
const scrollRef = useRef<HTMLElement | null>(null);
|
||||
const suppressDocumentClickRef = useRef(false);
|
||||
const [, forceVisibilityTick] = useState(0);
|
||||
const lastScrollNodeRef = useRef(null);
|
||||
const assignScrollRef = useCallback((node) => {
|
||||
if (lastScrollNodeRef.current === node) {
|
||||
return;
|
||||
}
|
||||
lastScrollNodeRef.current = node;
|
||||
scrollRef.current = node;
|
||||
if (node) {
|
||||
forceVisibilityTick((value) => value + 1);
|
||||
}
|
||||
}, []);
|
||||
const isGridView = viewMode === 'grid';
|
||||
const isDeskView = viewMode === 'desk';
|
||||
|
||||
@@ -577,44 +574,34 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
|
||||
const showGridSearchEmptyState = isGridView && showingSearchResults && !hasDocumentEntries && !isSearchLoading;
|
||||
const renderBody = () => {
|
||||
if (isDeskView) {
|
||||
return (
|
||||
<div className="panel-section__body panel-section__body--desk">
|
||||
{deskWorkspaceProps ? (
|
||||
<DesktopWorkspace {...deskWorkspaceProps} />
|
||||
) : (
|
||||
<div className="empty-state empty-state--global">
|
||||
Desk view is unavailable.
|
||||
</div>
|
||||
)}
|
||||
return deskWorkspaceProps ? (
|
||||
<DesktopWorkspace {...deskWorkspaceProps} />
|
||||
) : (
|
||||
<div className="empty-state empty-state--global">
|
||||
Desk view is unavailable.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showDefaultEmptyState) {
|
||||
return (
|
||||
<div className="panel-section__body">
|
||||
<div className="empty-state">
|
||||
Drop files anywhere or onto a folder to upload documents.
|
||||
</div>
|
||||
<div className="empty-state">
|
||||
Drop files anywhere or onto a folder to upload documents.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showGridSearchEmptyState) {
|
||||
return (
|
||||
<div className="panel-section__body">
|
||||
<div className="empty-state empty-state--global">
|
||||
No documents match the current filters.
|
||||
</div>
|
||||
<div className="empty-state empty-state--global">
|
||||
No documents match the current filters.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showListSearchEmptyState) {
|
||||
return (
|
||||
<div className="panel-section__body">
|
||||
<div className="empty-state">No documents match the current filters.</div>
|
||||
</div>
|
||||
<div className="empty-state">No documents match the current filters.</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -622,102 +609,113 @@ const DocumentsPanel: React.FC<DocumentsPanelProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isGridView) {
|
||||
return (
|
||||
<DocumentsGrid
|
||||
entries={entries}
|
||||
draggingDocumentIdsSet={draggingSet}
|
||||
draggedFolderId={draggedFolderId}
|
||||
onFolderClick={handleFolderClick}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onFolderDragOver={onFolderDragOver}
|
||||
onFolderDragLeave={onFolderDragLeave}
|
||||
onFolderDrop={onFolderDrop}
|
||||
onFolderDragStart={onFolderDragStart}
|
||||
onFolderDragEnd={onFolderDragEnd}
|
||||
onDocumentClick={handleDocumentClick}
|
||||
onDocumentActivate={handleDocumentActivate}
|
||||
onDocumentDragStart={handleDocumentDragStartLocal}
|
||||
onDocumentDragEnd={handleDocumentDragEndLocal}
|
||||
onDocumentTagDragOver={handleDocumentTagDragOver}
|
||||
onDocumentTagDragLeave={handleDocumentTagDragLeave}
|
||||
onDocumentTagDrop={onDocumentTagDrop}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
getDocumentAsset={getDocumentAsset}
|
||||
gridIconSize={gridIconSize}
|
||||
tagLookupById={tagLookupById}
|
||||
onTagClick={onTagClick}
|
||||
scrollRef={scrollRef}
|
||||
onCorrespondentClick={onCorrespondentClick}
|
||||
activeCorrespondentIdSet={activeCorrespondentIdSet}
|
||||
onDocumentRename={onDocumentRename}
|
||||
onFolderRename={onFolderRename}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="panel-section__body">
|
||||
<div
|
||||
ref={assignScrollRef}
|
||||
className="documents-scroll"
|
||||
tabIndex={0}
|
||||
onFocus={(event) => {
|
||||
if (event.target === scrollRef.current) {
|
||||
handlePanelFocus();
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.target !== scrollRef.current) {
|
||||
return;
|
||||
}
|
||||
handlePanelKeyDown(event);
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
clearSelection();
|
||||
}
|
||||
}}
|
||||
aria-activedescendant={isGridView ? undefined : activeDescendantId}
|
||||
>
|
||||
{isGridView ? (
|
||||
<DocumentsGrid
|
||||
entries={entries}
|
||||
draggingDocumentIdsSet={draggingSet}
|
||||
draggedFolderId={draggedFolderId}
|
||||
onFolderClick={handleFolderClick}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onFolderDragOver={onFolderDragOver}
|
||||
onFolderDragLeave={onFolderDragLeave}
|
||||
onFolderDrop={onFolderDrop}
|
||||
onFolderDragStart={onFolderDragStart}
|
||||
onFolderDragEnd={onFolderDragEnd}
|
||||
onDocumentClick={handleDocumentClick}
|
||||
onDocumentActivate={handleDocumentActivate}
|
||||
onDocumentDragStart={handleDocumentDragStartLocal}
|
||||
onDocumentDragEnd={handleDocumentDragEndLocal}
|
||||
onDocumentTagDragOver={handleDocumentTagDragOver}
|
||||
onDocumentTagDragLeave={handleDocumentTagDragLeave}
|
||||
onDocumentTagDrop={handleDocumentTagDrop}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
getDocumentAsset={getDocumentAsset}
|
||||
gridIconSize={gridIconSize}
|
||||
tagLookupById={tagLookupById}
|
||||
onTagClick={onTagClick}
|
||||
scrollRef={scrollRef}
|
||||
onCorrespondentClick={onCorrespondentClick}
|
||||
activeCorrespondentIdSet={activeCorrespondentIdSet}
|
||||
onDocumentRename={onDocumentRename}
|
||||
onFolderRename={onFolderRename}
|
||||
/>
|
||||
) : (
|
||||
<DocumentsList
|
||||
entries={entries}
|
||||
focusedRowKey={focusedRowKey}
|
||||
draggingDocumentIdsSet={draggingSet}
|
||||
draggedFolderId={draggedFolderId}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
getDocumentAsset={getDocumentAsset}
|
||||
onFolderClick={handleFolderClick}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onFolderDragOver={onFolderDragOver}
|
||||
onFolderDragLeave={onFolderDragLeave}
|
||||
onFolderDrop={onFolderDrop}
|
||||
onFolderDragStart={onFolderDragStart}
|
||||
onFolderDragEnd={onFolderDragEnd}
|
||||
onFolderRename={onFolderRename}
|
||||
onDocumentClick={handleDocumentClick}
|
||||
onDocumentActivate={handleDocumentActivate}
|
||||
onDocumentDragStart={handleDocumentDragStartLocal}
|
||||
onDocumentDragEnd={handleDocumentDragEndLocal}
|
||||
onDocumentTagDragOver={handleDocumentTagDragOver}
|
||||
onDocumentTagDragLeave={handleDocumentTagDragLeave}
|
||||
onDocumentTagDrop={handleDocumentTagDrop}
|
||||
onDocumentRename={onDocumentRename}
|
||||
tagLookupById={tagLookupById}
|
||||
onTagClick={onTagClick}
|
||||
onCorrespondentClick={onCorrespondentClick}
|
||||
activeCorrespondentIdSet={activeCorrespondentIdSet}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DocumentsList
|
||||
entries={entries}
|
||||
focusedRowKey={focusedRowKey}
|
||||
draggingDocumentIdsSet={draggingSet}
|
||||
draggedFolderId={draggedFolderId}
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
getDocumentAsset={getDocumentAsset}
|
||||
onFolderClick={handleFolderClick}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onFolderDragOver={onFolderDragOver}
|
||||
onFolderDragLeave={onFolderDragLeave}
|
||||
onFolderDrop={onFolderDrop}
|
||||
onFolderDragStart={onFolderDragStart}
|
||||
onFolderDragEnd={onFolderDragEnd}
|
||||
onFolderRename={onFolderRename}
|
||||
onDocumentClick={handleDocumentClick}
|
||||
onDocumentActivate={handleDocumentActivate}
|
||||
onDocumentDragStart={handleDocumentDragStartLocal}
|
||||
onDocumentDragEnd={handleDocumentDragEndLocal}
|
||||
onDocumentTagDragOver={handleDocumentTagDragOver}
|
||||
onDocumentTagDragLeave={handleDocumentTagDragLeave}
|
||||
onDocumentTagDrop={handleDocumentTagDrop}
|
||||
onDocumentRename={onDocumentRename}
|
||||
tagLookupById={tagLookupById}
|
||||
onTagClick={onTagClick}
|
||||
onCorrespondentClick={onCorrespondentClick}
|
||||
activeCorrespondentIdSet={activeCorrespondentIdSet}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const panelVariant = isDeskView ? 'desk' : isGridView ? 'grid' : 'list';
|
||||
const shouldHandlePanelInteractions = !isDeskView && showTableRows;
|
||||
|
||||
const handleSectionFocus = useCallback((event: React.FocusEvent<HTMLElement>) => {
|
||||
if (!shouldHandlePanelInteractions || event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
handlePanelFocus();
|
||||
}, [shouldHandlePanelInteractions, handlePanelFocus]);
|
||||
|
||||
const handleSectionKeyDown = useCallback((event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (!shouldHandlePanelInteractions || event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
handlePanelKeyDown(event);
|
||||
}, [shouldHandlePanelInteractions, handlePanelKeyDown]);
|
||||
|
||||
const handleSectionClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
|
||||
if (!shouldHandlePanelInteractions || event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
clearSelection();
|
||||
}, [shouldHandlePanelInteractions, clearSelection]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerConfig ? (
|
||||
<DocumentsPanelHeader
|
||||
header={headerConfig}
|
||||
onBreadcrumbClick={onBreadcrumbNavigate}
|
||||
/>
|
||||
) : null}
|
||||
<section
|
||||
ref={scrollRef}
|
||||
className={`documents-panel documents-panel--view-${panelVariant}`}
|
||||
tabIndex={shouldHandlePanelInteractions ? 0 : undefined}
|
||||
onFocus={handleSectionFocus}
|
||||
onKeyDown={handleSectionKeyDown}
|
||||
onClick={handleSectionClick}
|
||||
aria-activedescendant={shouldHandlePanelInteractions && !isGridView ? activeDescendantId : undefined}
|
||||
>
|
||||
{renderBody()}
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import PanelHeader from '../../ui/PanelHeader';
|
||||
import BreadcrumbTrail from '../../ui/BreadcrumbTrail';
|
||||
|
||||
type Identifier = string | number;
|
||||
|
||||
export interface DocumentsHeaderBreadcrumb {
|
||||
id?: Identifier;
|
||||
name?: string;
|
||||
label?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface DocumentsPanelHeaderConfig {
|
||||
title?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
leading?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
breadcrumbs?: DocumentsHeaderBreadcrumb[] | null;
|
||||
floatingActions?: ReactNode;
|
||||
}
|
||||
|
||||
interface DocumentsPanelHeaderProps {
|
||||
header?: DocumentsPanelHeaderConfig | null;
|
||||
onBreadcrumbClick?: (crumb: DocumentsHeaderBreadcrumb) => void;
|
||||
}
|
||||
|
||||
const DocumentsPanelHeader: React.FC<DocumentsPanelHeaderProps> = ({
|
||||
header,
|
||||
onBreadcrumbClick,
|
||||
}) => {
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const breadcrumbEntries = Array.isArray(header.breadcrumbs)
|
||||
? header.breadcrumbs.filter(Boolean)
|
||||
: [];
|
||||
const lastIndex = breadcrumbEntries.length - 1;
|
||||
const trailEntries = breadcrumbEntries.length
|
||||
? breadcrumbEntries.map((crumb, index) => ({
|
||||
id: crumb.id ?? index,
|
||||
label: crumb.name ?? crumb.label ?? crumb.title ?? '',
|
||||
onClick: index < lastIndex && onBreadcrumbClick
|
||||
? () => onBreadcrumbClick(crumb)
|
||||
: null,
|
||||
}))
|
||||
: [{ id: 'current-location', label: header.title }];
|
||||
|
||||
const headerTitle = (
|
||||
<h2>
|
||||
<BreadcrumbTrail entries={trailEntries} separator="/" />
|
||||
{header.subtitle ? (
|
||||
<span className="panel-header__subtitle">{header.subtitle}</span>
|
||||
) : null}
|
||||
</h2>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelHeader
|
||||
leading={header.leading}
|
||||
title={headerTitle}
|
||||
titleTag="h2"
|
||||
actions={header.actions}
|
||||
/>
|
||||
{header.floatingActions}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentsPanelHeader;
|
||||
@@ -111,7 +111,7 @@ export const createDocumentsTableHeaderActions = ({
|
||||
<div className="view-toggle" role="group" aria-label="Change view">
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isListView ? ' active' : ''}`}
|
||||
className={`toggle-button${isListView ? ' active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('list')}
|
||||
aria-pressed={isListView}
|
||||
title="List view"
|
||||
@@ -120,7 +120,7 @@ export const createDocumentsTableHeaderActions = ({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isGridView ? ' active' : ''}`}
|
||||
className={`toggle-button${isGridView ? ' active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('grid')}
|
||||
aria-pressed={isGridView}
|
||||
title="Icons view"
|
||||
@@ -129,7 +129,7 @@ export const createDocumentsTableHeaderActions = ({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isDeskView ? ' active' : ''}`}
|
||||
className={`toggle-button${isDeskView ? ' active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('desk')}
|
||||
aria-pressed={isDeskView}
|
||||
title="Desk view"
|
||||
|
||||
@@ -50,7 +50,7 @@ const SortFieldQuickMenu: React.FC<SortFieldQuickMenuProps> = ({ sortField, onCh
|
||||
className="documents-sort__quickmenu"
|
||||
options={options}
|
||||
onSelectOption={handleSelect}
|
||||
triggerClassName="view-toggle__button documents-sort__trigger quick-add__trigger"
|
||||
triggerClassName="toggle-button documents-sort__trigger quick-add__trigger"
|
||||
triggerContent={(
|
||||
<span className="documents-sort__trigger-content">
|
||||
<span className="documents-sort__label">{label}</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { ReactNode } from 'react';
|
||||
import DocumentViewerPanel from '../../preview/DocumentViewerPanel';
|
||||
import createWorkspaceSurfaceConfig from '../workspaceHeader';
|
||||
import DocumentsPanel from './DocumentsPanel';
|
||||
import type { DocumentsPanelHeaderConfig } from './DocumentsPanelHeader';
|
||||
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
|
||||
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
||||
|
||||
@@ -100,6 +101,15 @@ const createDocumentsSurface = ({
|
||||
})()
|
||||
: null;
|
||||
|
||||
const headerConfig: DocumentsPanelHeaderConfig = {
|
||||
title,
|
||||
subtitle,
|
||||
leading: sidebarToggle,
|
||||
actions,
|
||||
breadcrumbs,
|
||||
floatingActions,
|
||||
};
|
||||
|
||||
return createWorkspaceSurfaceConfig({
|
||||
key: isDeskView ? 'workspace' : 'documents',
|
||||
variant: isDeskView ? 'workspace' : 'documents',
|
||||
@@ -113,6 +123,7 @@ const createDocumentsSurface = ({
|
||||
content: (
|
||||
<DocumentsPanel
|
||||
{...tableProps}
|
||||
headerConfig={headerConfig}
|
||||
onInspectDocument={onInspectDocument}
|
||||
/>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user