floating selection box
This commit is contained in:
@@ -132,8 +132,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 2px 2px 4px var(--shadow-faint);
|
background: color-mix(in oklch, var(--surface-subtle) 90%, transparent);
|
||||||
background: color-mix(in oklch, var(--surface-subtle) 88%, transparent);
|
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +149,6 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
transition: transform 0.16s ease, opacity 0.2s ease, box-shadow 0.2s ease;
|
transition: transform 0.16s ease, opacity 0.2s ease, box-shadow 0.2s ease;
|
||||||
box-shadow: 2px 2px 4px var(--shadow-medium);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.desk-help-overlay {
|
.desk-help-overlay {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import React, {
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { resolveDocumentAssetUrl, createAssetView } from './asset_manager';
|
import { resolveDocumentAssetUrl, createAssetView } from './asset_manager';
|
||||||
import { useAssetNavigator } from './hooks/useAssetNavigator';
|
import { useAssetNavigator } from './hooks/useAssetNavigator';
|
||||||
import { ArrowLeftIcon, ArrowRightIcon, CloseIcon, FileIcon, FolderOutlineIcon } from './ui/icons';
|
import { ArrowLeftIcon, ArrowRightIcon, CloseIcon } from './ui/icons';
|
||||||
import SelectionFloatingActions from './documents/SelectionFloatingActions';
|
import SelectionFloatingActions from './documents/SelectionFloatingActions';
|
||||||
import { createDocumentsTableHeaderActions } from './documents/DocumentsPanel';
|
import { createDocumentsTableHeaderActions } from './documents/DocumentsPanel';
|
||||||
import createWorkspaceSurfaceConfig from './documents/workspaceHeader';
|
import createWorkspaceSurfaceConfig from './documents/workspaceHeader';
|
||||||
@@ -590,6 +590,7 @@ const DesktopWorkspace = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
|
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
|
||||||
|
|
||||||
|
const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:'));
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const layoutRef = useRef(new Map());
|
const layoutRef = useRef(new Map());
|
||||||
@@ -1120,7 +1121,7 @@ const recalcVisibleDocIds = useCallback(() => {
|
|||||||
|
|
||||||
const persistLayoutSnapshot = useCallback(
|
const persistLayoutSnapshot = useCallback(
|
||||||
async (snapshot, force = false) => {
|
async (snapshot, force = false) => {
|
||||||
if (!tenantId || !viewId) {
|
if (!allowLayoutPersistence || !tenantId || !viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!force && !layoutDirtyRef.current) {
|
if (!force && !layoutDirtyRef.current) {
|
||||||
@@ -1160,7 +1161,7 @@ const recalcVisibleDocIds = useCallback(() => {
|
|||||||
|
|
||||||
await upsertLayoutRecords({ tenantId, viewId, entries: records });
|
await upsertLayoutRecords({ tenantId, viewId, entries: records });
|
||||||
},
|
},
|
||||||
[tenantId, viewId],
|
[tenantId, viewId, allowLayoutPersistence],
|
||||||
);
|
);
|
||||||
|
|
||||||
const syncLayoutSnapshot = useCallback((force = false) => {
|
const syncLayoutSnapshot = useCallback((force = false) => {
|
||||||
@@ -1207,6 +1208,11 @@ const recalcVisibleDocIds = useCallback(() => {
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!allowLayoutPersistence) {
|
||||||
|
persistedLayoutRef.current = new Map();
|
||||||
|
layoutDirtyRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!tenantId || !viewId) {
|
if (!tenantId || !viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1245,7 +1251,7 @@ const recalcVisibleDocIds = useCallback(() => {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [tenantId, viewId]);
|
}, [tenantId, viewId, allowLayoutPersistence]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!containerRef.current || !canvasSize.width || !canvasSize.height) {
|
if (!containerRef.current || !canvasSize.width || !canvasSize.height) {
|
||||||
@@ -2496,6 +2502,7 @@ export const createDesktopSurface = ({
|
|||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
selectedFolderIds,
|
selectedFolderIds,
|
||||||
onDeleteSelection,
|
onDeleteSelection,
|
||||||
|
onClearSelection,
|
||||||
tags,
|
tags,
|
||||||
correspondents,
|
correspondents,
|
||||||
documentLookup,
|
documentLookup,
|
||||||
@@ -2506,6 +2513,7 @@ export const createDesktopSurface = ({
|
|||||||
onBulkCorrespondentRemove,
|
onBulkCorrespondentRemove,
|
||||||
onBulkReanalyze,
|
onBulkReanalyze,
|
||||||
} = workspaceProps;
|
} = workspaceProps;
|
||||||
|
|
||||||
const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
|
const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
|
||||||
const subtitle = Array.isArray(searchResults)
|
const subtitle = Array.isArray(searchResults)
|
||||||
? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
|
? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
|
||||||
@@ -2515,46 +2523,6 @@ export const createDesktopSurface = ({
|
|||||||
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
|
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
|
||||||
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
|
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
|
||||||
const selectionCount = documentSelectionCount + folderSelectionCount;
|
const selectionCount = documentSelectionCount + folderSelectionCount;
|
||||||
let selectionLabel = null;
|
|
||||||
if (selectionCount) {
|
|
||||||
const tokens = [];
|
|
||||||
|
|
||||||
if (documentSelectionCount) {
|
|
||||||
tokens.push({
|
|
||||||
key: 'documents',
|
|
||||||
count: documentSelectionCount,
|
|
||||||
icon: <FileIcon className="selection-summary__icon" size={16} />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderSelectionCount) {
|
|
||||||
tokens.push({
|
|
||||||
key: 'folders',
|
|
||||||
count: folderSelectionCount,
|
|
||||||
icon: <FolderOutlineIcon className="selection-summary__icon" size={16} />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.length) {
|
|
||||||
selectionLabel = (
|
|
||||||
<span className="selection-summary">
|
|
||||||
{tokens.map((token, index) => (
|
|
||||||
<React.Fragment key={token.key}>
|
|
||||||
{index > 0 ? <span className="selection-summary__separator">·</span> : null}
|
|
||||||
<span className="selection-summary__token">
|
|
||||||
<span className="selection-summary__count">{token.count}</span>
|
|
||||||
<span className="selection-summary__multiply">×</span>
|
|
||||||
{token.icon}
|
|
||||||
</span>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
selectionLabel = `${selectionCount} item${selectionCount === 1 ? '' : 's'}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = createDocumentsTableHeaderActions({
|
const actions = createDocumentsTableHeaderActions({
|
||||||
viewMode: viewMode || 'desk',
|
viewMode: viewMode || 'desk',
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
@@ -2562,11 +2530,12 @@ export const createDesktopSurface = ({
|
|||||||
onShowDeskHelp: viewMode === 'desk' ? workspaceProps?.onOpenHelp : null,
|
onShowDeskHelp: viewMode === 'desk' ? workspaceProps?.onOpenHelp : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const floatingActions = selectionLabel
|
const floatingActions = selectionCount > 0
|
||||||
? (
|
? (
|
||||||
<SelectionFloatingActions
|
<SelectionFloatingActions
|
||||||
selectionCount={selectionCount}
|
selectionCount={selectionCount}
|
||||||
selectedDocumentIds={selectedDocumentIds}
|
selectedDocumentIds={selectedDocumentIds}
|
||||||
|
selectedFolderIds={selectedFolderIds}
|
||||||
documentLookup={documentLookup}
|
documentLookup={documentLookup}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
tagLookupById={tagLookupById}
|
tagLookupById={tagLookupById}
|
||||||
@@ -2577,6 +2546,7 @@ export const createDesktopSurface = ({
|
|||||||
onBulkCorrespondentRemove={onBulkCorrespondentRemove}
|
onBulkCorrespondentRemove={onBulkCorrespondentRemove}
|
||||||
onBulkReanalyze={onBulkReanalyze}
|
onBulkReanalyze={onBulkReanalyze}
|
||||||
onDeleteSelection={onDeleteSelection}
|
onDeleteSelection={onDeleteSelection}
|
||||||
|
onClearSelection={onClearSelection}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
@@ -2593,7 +2563,7 @@ export const createDesktopSurface = ({
|
|||||||
onNavigateParent,
|
onNavigateParent,
|
||||||
actions,
|
actions,
|
||||||
breadcrumbs: workspaceProps?.breadcrumbs || null,
|
breadcrumbs: workspaceProps?.breadcrumbs || null,
|
||||||
selectionLabel,
|
selectionLabel: null,
|
||||||
floatingActions,
|
floatingActions,
|
||||||
content: <DesktopWorkspace {...workspaceProps} />,
|
content: <DesktopWorkspace {...workspaceProps} />,
|
||||||
detail,
|
detail,
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
MinusVerticalIcon,
|
MinusVerticalIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
FileIcon,
|
|
||||||
FolderOutlineIcon,
|
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||||
import createWorkspaceSurfaceConfig from './workspaceHeader';
|
import createWorkspaceSurfaceConfig from './workspaceHeader';
|
||||||
@@ -609,6 +607,7 @@ export const createDocumentsSurface = ({
|
|||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
selectedFolderIds,
|
selectedFolderIds,
|
||||||
onDeleteSelection,
|
onDeleteSelection,
|
||||||
|
onClearSelection,
|
||||||
tags,
|
tags,
|
||||||
correspondents,
|
correspondents,
|
||||||
documentLookup,
|
documentLookup,
|
||||||
@@ -629,45 +628,6 @@ export const createDocumentsSurface = ({
|
|||||||
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
|
const documentSelectionCount = Array.isArray(selectedDocumentIds) ? selectedDocumentIds.length : 0;
|
||||||
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
|
const folderSelectionCount = Array.isArray(selectedFolderIds) ? selectedFolderIds.length : 0;
|
||||||
const selectionCount = documentSelectionCount + folderSelectionCount;
|
const selectionCount = documentSelectionCount + folderSelectionCount;
|
||||||
let selectionLabel = null;
|
|
||||||
if (selectionCount) {
|
|
||||||
const tokens = [];
|
|
||||||
|
|
||||||
if (documentSelectionCount) {
|
|
||||||
tokens.push({
|
|
||||||
key: 'documents',
|
|
||||||
count: documentSelectionCount,
|
|
||||||
icon: <FileIcon className="selection-summary__icon" size={16} />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderSelectionCount) {
|
|
||||||
tokens.push({
|
|
||||||
key: 'folders',
|
|
||||||
count: folderSelectionCount,
|
|
||||||
icon: <FolderOutlineIcon className="selection-summary__icon" size={16} />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.length) {
|
|
||||||
selectionLabel = (
|
|
||||||
<span className="selection-summary">
|
|
||||||
{tokens.map((token, index) => (
|
|
||||||
<React.Fragment key={token.key}>
|
|
||||||
{index > 0 ? <span className="selection-summary__separator">·</span> : null}
|
|
||||||
<span className="selection-summary__token">
|
|
||||||
<span className="selection-summary__count">{token.count}</span>
|
|
||||||
<span className="selection-summary__multiply">×</span>
|
|
||||||
{token.icon}
|
|
||||||
</span>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
selectionLabel = `${selectionCount} item${selectionCount === 1 ? '' : 's'}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = createDocumentsTableHeaderActions({
|
const actions = createDocumentsTableHeaderActions({
|
||||||
viewMode,
|
viewMode,
|
||||||
@@ -675,11 +635,12 @@ export const createDocumentsSurface = ({
|
|||||||
onRefresh,
|
onRefresh,
|
||||||
});
|
});
|
||||||
|
|
||||||
const floatingActions = selectionLabel
|
const floatingActions = selectionCount > 0
|
||||||
? (
|
? (
|
||||||
<SelectionFloatingActions
|
<SelectionFloatingActions
|
||||||
selectionCount={selectionCount}
|
selectionCount={selectionCount}
|
||||||
selectedDocumentIds={selectedDocumentIds}
|
selectedDocumentIds={selectedDocumentIds}
|
||||||
|
selectedFolderIds={selectedFolderIds}
|
||||||
documentLookup={documentLookup}
|
documentLookup={documentLookup}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
tagLookupById={tagLookupById}
|
tagLookupById={tagLookupById}
|
||||||
@@ -690,6 +651,7 @@ export const createDocumentsSurface = ({
|
|||||||
onBulkCorrespondentRemove={onBulkCorrespondentRemove}
|
onBulkCorrespondentRemove={onBulkCorrespondentRemove}
|
||||||
onBulkReanalyze={onBulkReanalyze}
|
onBulkReanalyze={onBulkReanalyze}
|
||||||
onDeleteSelection={onDeleteSelection}
|
onDeleteSelection={onDeleteSelection}
|
||||||
|
onClearSelection={onClearSelection}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
@@ -708,7 +670,7 @@ export const createDocumentsSurface = ({
|
|||||||
onNavigateParent,
|
onNavigateParent,
|
||||||
actions,
|
actions,
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
selectionLabel,
|
selectionLabel: null,
|
||||||
floatingActions,
|
floatingActions,
|
||||||
content: <DocumentsPanel {...tableProps} showHeader={false} />,
|
content: <DocumentsPanel {...tableProps} showHeader={false} />,
|
||||||
detail,
|
detail,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { TrashIcon, AnalyzeIcon } from '../ui/icons';
|
import { TrashIcon, AnalyzeIcon, IconX } from '../ui/icons';
|
||||||
import SelectionAssignmentMenu from './SelectionAssignmentMenu';
|
import SelectionAssignmentMenu from './SelectionAssignmentMenu';
|
||||||
|
import SelectionSummary from './SelectionSummary';
|
||||||
|
|
||||||
const normalizeDocumentList = (selectedDocumentIds) =>
|
const normalizeDocumentList = (selectedDocumentIds) =>
|
||||||
Array.isArray(selectedDocumentIds) ? selectedDocumentIds.filter(Boolean) : [];
|
Array.isArray(selectedDocumentIds) ? selectedDocumentIds.filter(Boolean) : [];
|
||||||
@@ -112,6 +113,7 @@ const buildCorrespondentAssignments = (selectedDocuments, correspondents, total)
|
|||||||
const SelectionFloatingActions = ({
|
const SelectionFloatingActions = ({
|
||||||
selectionCount = 0,
|
selectionCount = 0,
|
||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
|
selectedFolderIds = [],
|
||||||
documentLookup,
|
documentLookup,
|
||||||
tags,
|
tags,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
@@ -122,12 +124,24 @@ const SelectionFloatingActions = ({
|
|||||||
onBulkCorrespondentRemove,
|
onBulkCorrespondentRemove,
|
||||||
onBulkReanalyze,
|
onBulkReanalyze,
|
||||||
onDeleteSelection,
|
onDeleteSelection,
|
||||||
|
onClearSelection = null,
|
||||||
}) => {
|
}) => {
|
||||||
const documentIdList = useMemo(
|
const documentIdList = useMemo(
|
||||||
() => normalizeDocumentList(selectedDocumentIds),
|
() => normalizeDocumentList(selectedDocumentIds),
|
||||||
[selectedDocumentIds],
|
[selectedDocumentIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const folderIdList = useMemo(
|
||||||
|
() => normalizeDocumentList(selectedFolderIds),
|
||||||
|
[selectedFolderIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const documentCount = documentIdList.length;
|
||||||
|
const folderCount = folderIdList.length;
|
||||||
|
const totalCount = typeof selectionCount === 'number'
|
||||||
|
? selectionCount
|
||||||
|
: documentCount + folderCount;
|
||||||
|
|
||||||
const selectedDocuments = useMemo(() => {
|
const selectedDocuments = useMemo(() => {
|
||||||
if (!documentIdList.length || !(documentLookup instanceof Map)) {
|
if (!documentIdList.length || !(documentLookup instanceof Map)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -203,52 +217,77 @@ const SelectionFloatingActions = ({
|
|||||||
[selectedDocCount, onBulkCorrespondentAdd, documentIdList],
|
[selectedDocCount, onBulkCorrespondentAdd, documentIdList],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const summaryNode = totalCount > 0 ? (
|
||||||
|
<SelectionSummary
|
||||||
|
documentCount={documentCount}
|
||||||
|
folderCount={folderCount}
|
||||||
|
totalCount={totalCount}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panel-floating-actions">
|
<>
|
||||||
<SelectionAssignmentMenu
|
{summaryNode ? (
|
||||||
label="Tags"
|
<span className="panel-floating__label">{summaryNode}</span>
|
||||||
items={tagAssignments}
|
|
||||||
placeholder="Search tags…"
|
|
||||||
emptyMessage="No tags"
|
|
||||||
createLabel="Create"
|
|
||||||
onToggle={handleToggleTagAssignment}
|
|
||||||
onCreate={handleCreateTagAssignment}
|
|
||||||
disabled={!selectedDocCount}
|
|
||||||
/>
|
|
||||||
<SelectionAssignmentMenu
|
|
||||||
label="Correspondents"
|
|
||||||
items={correspondentAssignments}
|
|
||||||
placeholder="Search correspondents…"
|
|
||||||
emptyMessage="No correspondents"
|
|
||||||
createLabel="Create"
|
|
||||||
onToggle={handleToggleCorrespondentAssignment}
|
|
||||||
onCreate={handleCreateCorrespondentAssignment}
|
|
||||||
disabled={!selectedDocCount}
|
|
||||||
/>
|
|
||||||
{typeof onBulkReanalyze === 'function' ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="icon-button panel-floating-actions__button"
|
|
||||||
onClick={() => onBulkReanalyze(documentIdList)}
|
|
||||||
aria-label="Re-run analysis for selection"
|
|
||||||
title="Re-run analysis for selection"
|
|
||||||
disabled={!documentIdList.length}
|
|
||||||
>
|
|
||||||
<AnalyzeIcon className="icon-inline" />
|
|
||||||
</button>
|
|
||||||
) : null}
|
) : null}
|
||||||
{typeof onDeleteSelection === 'function' ? (
|
<div className="panel-floating-actions">
|
||||||
<button
|
<SelectionAssignmentMenu
|
||||||
type="button"
|
label="Tags"
|
||||||
className="icon-button danger panel-floating-actions__button"
|
items={tagAssignments}
|
||||||
onClick={onDeleteSelection}
|
placeholder="Search tags…"
|
||||||
aria-label="Delete selected items"
|
emptyMessage="No tags"
|
||||||
disabled={selectionCount === 0}
|
createLabel="Create"
|
||||||
>
|
onToggle={handleToggleTagAssignment}
|
||||||
<TrashIcon className="icon-inline" />
|
onCreate={handleCreateTagAssignment}
|
||||||
</button>
|
disabled={!documentCount}
|
||||||
) : null}
|
/>
|
||||||
</div>
|
<SelectionAssignmentMenu
|
||||||
|
label="Correspondents"
|
||||||
|
items={correspondentAssignments}
|
||||||
|
placeholder="Search correspondents…"
|
||||||
|
emptyMessage="No correspondents"
|
||||||
|
createLabel="Create"
|
||||||
|
onToggle={handleToggleCorrespondentAssignment}
|
||||||
|
onCreate={handleCreateCorrespondentAssignment}
|
||||||
|
disabled={!documentCount}
|
||||||
|
/>
|
||||||
|
{typeof onBulkReanalyze === 'function' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button panel-floating-actions__button"
|
||||||
|
onClick={() => onBulkReanalyze(documentIdList)}
|
||||||
|
aria-label="Re-run analysis for selection"
|
||||||
|
title="Re-run analysis for selection"
|
||||||
|
disabled={documentIdList.length === 0}
|
||||||
|
>
|
||||||
|
<AnalyzeIcon className="icon-inline" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{typeof onDeleteSelection === 'function' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button danger panel-floating-actions__button"
|
||||||
|
onClick={onDeleteSelection}
|
||||||
|
aria-label="Delete selected items"
|
||||||
|
disabled={totalCount === 0}
|
||||||
|
>
|
||||||
|
<TrashIcon className="icon-inline" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{typeof onClearSelection === 'function' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button panel-floating-actions__button"
|
||||||
|
onClick={onClearSelection}
|
||||||
|
aria-label="Clear selection"
|
||||||
|
title="Clear selection"
|
||||||
|
disabled={totalCount === 0}
|
||||||
|
>
|
||||||
|
<IconX className="icon-inline" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FileIcon, FolderOutlineIcon } from '../ui/icons';
|
||||||
|
|
||||||
|
const SelectionSummary = ({ documentCount = 0, folderCount = 0, totalCount = 0 }) => {
|
||||||
|
const docCount = Number(documentCount) || 0;
|
||||||
|
const folderCountNumber = Number(folderCount) || 0;
|
||||||
|
const aggregateCount = docCount + folderCountNumber;
|
||||||
|
const resolvedTotal = Number(totalCount) || aggregateCount;
|
||||||
|
|
||||||
|
if (!docCount && !folderCountNumber && !resolvedTotal) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = [];
|
||||||
|
|
||||||
|
if (docCount) {
|
||||||
|
tokens.push({
|
||||||
|
key: 'documents',
|
||||||
|
count: docCount,
|
||||||
|
icon: <FileIcon className="selection-summary__icon" size={16} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderCountNumber) {
|
||||||
|
tokens.push({
|
||||||
|
key: 'folders',
|
||||||
|
count: folderCountNumber,
|
||||||
|
icon: <FolderOutlineIcon className="selection-summary__icon" size={16} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokens.length) {
|
||||||
|
const count = resolvedTotal;
|
||||||
|
return (
|
||||||
|
<span className="selection-summary selection-summary--text">
|
||||||
|
{`${count} item${count === 1 ? '' : 's'}`}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="selection-summary">
|
||||||
|
{tokens.map((token, index) => (
|
||||||
|
<React.Fragment key={token.key}>
|
||||||
|
{index > 0 ? <span className="selection-summary__separator">·</span> : null}
|
||||||
|
<span className="selection-summary__token">
|
||||||
|
<span className="selection-summary__count">{token.count}</span>
|
||||||
|
{token.icon}
|
||||||
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectionSummary;
|
||||||
@@ -2076,10 +2076,8 @@ button.danger:hover:not([disabled]) {
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-summary__multiply {
|
.selection-summary--text {
|
||||||
display: inline-flex;
|
font-weight: 600;
|
||||||
align-items: center;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-summary__separator {
|
.selection-summary__separator {
|
||||||
|
|||||||
Reference in New Issue
Block a user