panel actions
This commit is contained in:
@@ -177,6 +177,7 @@ const DocumentsTable = ({
|
|||||||
viewMode = 'list',
|
viewMode = 'list',
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
onClearSelection,
|
onClearSelection,
|
||||||
|
showHeader = true,
|
||||||
}) => {
|
}) => {
|
||||||
const showingSearchResults = searchResults !== null;
|
const showingSearchResults = searchResults !== null;
|
||||||
const rows = showingSearchResults ? searchResults : documents;
|
const rows = showingSearchResults ? searchResults : documents;
|
||||||
@@ -220,6 +221,11 @@ const DocumentsTable = ({
|
|||||||
},
|
},
|
||||||
[onViewModeChange],
|
[onViewModeChange],
|
||||||
);
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}, [viewMode]);
|
||||||
const isTagDragEvent = useCallback((event) => {
|
const isTagDragEvent = useCallback((event) => {
|
||||||
const types = Array.from(event.dataTransfer?.types || []);
|
const types = Array.from(event.dataTransfer?.types || []);
|
||||||
return TAG_MIME_TYPES.some((type) => types.includes(type));
|
return TAG_MIME_TYPES.some((type) => types.includes(type));
|
||||||
@@ -338,6 +344,7 @@ const DocumentsTable = ({
|
|||||||
<section
|
<section
|
||||||
className={`documents-panel column documents-panel--view-${isGridView ? 'grid' : 'list'}`}
|
className={`documents-panel column documents-panel--view-${isGridView ? 'grid' : 'list'}`}
|
||||||
>
|
>
|
||||||
|
{showHeader ? (
|
||||||
<div className="column-header">
|
<div className="column-header">
|
||||||
<div className="column-header__titles">
|
<div className="column-header__titles">
|
||||||
<h2>{currentFolderName}</h2>
|
<h2>{currentFolderName}</h2>
|
||||||
@@ -381,6 +388,7 @@ const DocumentsTable = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
{showDefaultEmptyState && (
|
{showDefaultEmptyState && (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
Drop files anywhere or onto a folder to upload documents.
|
Drop files anywhere or onto a folder to upload documents.
|
||||||
|
|||||||
+199
-12
@@ -32,6 +32,16 @@ import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
|
|||||||
import { CORRESPONDENT_ROLES } from './constants/correspondents';
|
import { CORRESPONDENT_ROLES } from './constants/correspondents';
|
||||||
import TagManager from './tag_manager';
|
import TagManager from './tag_manager';
|
||||||
import Sidebar from './sidebar/Sidebar';
|
import Sidebar from './sidebar/Sidebar';
|
||||||
|
import {
|
||||||
|
ChevronsRightIcon,
|
||||||
|
ChevronsLeftIcon,
|
||||||
|
ViewListIcon,
|
||||||
|
ViewGridIcon,
|
||||||
|
FolderPlusIcon,
|
||||||
|
RefreshIcon,
|
||||||
|
ArrowUpIcon,
|
||||||
|
MinusVerticalIcon,
|
||||||
|
} from './ui/icons';
|
||||||
import DocumentsTable from './documents/DocumentsTable';
|
import DocumentsTable from './documents/DocumentsTable';
|
||||||
import { AppShellContext, useAppShell } from './appShellContext';
|
import { AppShellContext, useAppShell } from './appShellContext';
|
||||||
import DocumentViewerRoute from './routes/DocumentViewerRoute';
|
import DocumentViewerRoute from './routes/DocumentViewerRoute';
|
||||||
@@ -311,9 +321,9 @@ const LoginView = ({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const DocumentsLayout = ({ sidebarProps, children }) => (
|
const DocumentsLayout = ({ sidebarProps, children, sidebarCollapsed }) => (
|
||||||
<main className="documents-main">
|
<main className={`documents-main${sidebarCollapsed ? ' documents-main--sidebar-collapsed' : ''}`}>
|
||||||
<Sidebar {...sidebarProps} />
|
{!sidebarCollapsed ? <Sidebar {...sidebarProps} /> : null}
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -337,6 +347,9 @@ const AppLayout = () => {
|
|||||||
({ message, variant }) => setStatusMessage(message, variant),
|
({ message, variant }) => setStatusMessage(message, variant),
|
||||||
[setStatusMessage],
|
[setStatusMessage],
|
||||||
);
|
);
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
|
const collapseSidebar = useCallback(() => setSidebarCollapsed(true), []);
|
||||||
|
const expandSidebar = useCallback(() => setSidebarCollapsed(false), []);
|
||||||
const reportApiError = useApiError({
|
const reportApiError = useApiError({
|
||||||
onReport: handleApiReport,
|
onReport: handleApiReport,
|
||||||
});
|
});
|
||||||
@@ -4488,6 +4501,7 @@ const AppLayout = () => {
|
|||||||
isFilterActive,
|
isFilterActive,
|
||||||
onLogout: handleLogout,
|
onLogout: handleLogout,
|
||||||
status,
|
status,
|
||||||
|
onCollapse: collapseSidebar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveThumbnailUrlForDoc = useCallback(
|
const resolveThumbnailUrlForDoc = useCallback(
|
||||||
@@ -4847,36 +4861,209 @@ const DocumentsRoute = () => {
|
|||||||
skeuoWorkspaceProps,
|
skeuoWorkspaceProps,
|
||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
|
exitSkeuoWorkspace,
|
||||||
} = useAppShell();
|
} = useAppShell();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
|
const collapseSidebar = useCallback(() => setSidebarCollapsed(true), []);
|
||||||
|
const expandSidebar = useCallback(() => setSidebarCollapsed(false), []);
|
||||||
|
|
||||||
const sidebarPropsWithActions = useMemo(
|
const sidebarPropsWithActions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...sidebarProps,
|
...sidebarProps,
|
||||||
onManageTags: openTagsModal,
|
onManageTags: openTagsModal,
|
||||||
onManageCorrespondents: openCorrespondentsModal,
|
onManageCorrespondents: openCorrespondentsModal,
|
||||||
|
onCollapse: collapseSidebar,
|
||||||
}),
|
}),
|
||||||
[sidebarProps, openTagsModal, openCorrespondentsModal],
|
[sidebarProps, openTagsModal, openCorrespondentsModal, collapseSidebar],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentFolderName,
|
||||||
|
viewMode,
|
||||||
|
onViewModeChange,
|
||||||
|
onRequestCreateFolder,
|
||||||
|
creatingFolder,
|
||||||
|
onRefresh,
|
||||||
|
onShowSkeuoWorkspace,
|
||||||
|
searchResults,
|
||||||
|
breadcrumbs,
|
||||||
|
} = documentsTableProps;
|
||||||
|
|
||||||
|
const isGridView = viewMode === 'grid';
|
||||||
|
const showingSearchResults = Array.isArray(searchResults);
|
||||||
|
const headerTitle = showingSearchResults ? 'Search results' : currentFolderName;
|
||||||
|
const headerSubtitle = showingSearchResults
|
||||||
|
? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
|
||||||
|
: null;
|
||||||
|
const parentBreadcrumb = breadcrumbs && breadcrumbs.length > 1 ? breadcrumbs[breadcrumbs.length - 2] : null;
|
||||||
|
const handleNavigateParent = parentBreadcrumb
|
||||||
|
? () => {
|
||||||
|
const target = parentBreadcrumb.id === 'root'
|
||||||
|
? '/documents'
|
||||||
|
: `/documents/folder/${parentBreadcrumb.id}`;
|
||||||
|
navigate(target);
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const detailHasContent = detailPanelProps.selectedDocuments?.length > 0;
|
||||||
|
const toggleSidebar = sidebarCollapsed ? expandSidebar : collapseSidebar;
|
||||||
|
const ToggleIcon = sidebarCollapsed ? ChevronsRightIcon : ChevronsLeftIcon;
|
||||||
|
const toggleLabel = sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar';
|
||||||
|
|
||||||
if (workspaceMode === 'skeuo') {
|
if (workspaceMode === 'skeuo') {
|
||||||
return (
|
return (
|
||||||
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
|
<DocumentsLayout
|
||||||
|
sidebarProps={sidebarPropsWithActions}
|
||||||
|
sidebarCollapsed={sidebarCollapsed}
|
||||||
|
>
|
||||||
<div className="main-content">
|
<div className="main-content">
|
||||||
|
<div className="panel-header main-content__header">
|
||||||
|
<div className="panel-actions main-content__actions">
|
||||||
|
{sidebarCollapsed ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
aria-label={toggleLabel}
|
||||||
|
title={toggleLabel}
|
||||||
|
>
|
||||||
|
<ToggleIcon />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{parentBreadcrumb ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={handleNavigateParent}
|
||||||
|
aria-label="Go to parent folder"
|
||||||
|
title="Go to parent folder"
|
||||||
|
>
|
||||||
|
<ArrowUpIcon />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<h2 className="main-content__title">
|
||||||
|
{headerTitle}
|
||||||
|
{headerSubtitle ? (
|
||||||
|
<span className="main-content__subtitle">{headerSubtitle}</span>
|
||||||
|
) : null}
|
||||||
|
</h2>
|
||||||
|
<div className="spacer" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={skeuoWorkspaceProps.onRefresh}
|
||||||
|
aria-label="Refresh"
|
||||||
|
title="Refresh"
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
</button>
|
||||||
|
<button type="button" className="secondary" onClick={exitSkeuoWorkspace}>
|
||||||
|
Back to List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="main-content__body">
|
||||||
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</DocumentsLayout>
|
</DocumentsLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailHasContent = detailPanelProps.selectedDocuments?.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
|
<DocumentsLayout
|
||||||
<div
|
sidebarProps={sidebarPropsWithActions}
|
||||||
className={`main-content main-content--documents${detailHasContent ? ' main-content--has-detail' : ''}`}
|
sidebarCollapsed={sidebarCollapsed}
|
||||||
>
|
>
|
||||||
<DocumentsTable {...documentsTableProps} />
|
<div className={`main-content main-content--documents${detailHasContent ? ' main-content--has-detail' : ''}`}>
|
||||||
{detailHasContent ? <DetailPanel {...detailPanelProps} /> : null}
|
<div className="panel-header main-content__header">
|
||||||
|
<div className="panel-actions main-content__actions">
|
||||||
|
{sidebarCollapsed ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
aria-label={toggleLabel}
|
||||||
|
title={toggleLabel}
|
||||||
|
>
|
||||||
|
<ToggleIcon />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{parentBreadcrumb ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={handleNavigateParent}
|
||||||
|
aria-label="Go to parent folder"
|
||||||
|
title="Go to parent folder"
|
||||||
|
>
|
||||||
|
<ArrowUpIcon />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<h2 className="main-content__title">
|
||||||
|
{headerTitle}
|
||||||
|
{headerSubtitle ? (
|
||||||
|
<span className="main-content__subtitle">{headerSubtitle}</span>
|
||||||
|
) : null}
|
||||||
|
</h2>
|
||||||
|
<div className="spacer" />
|
||||||
|
<div className="view-toggle" role="group" aria-label="Change view">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`view-toggle__button${isGridView ? '' : ' active'}`}
|
||||||
|
onClick={() => onViewModeChange?.('list')}
|
||||||
|
aria-pressed={!isGridView}
|
||||||
|
title="List view"
|
||||||
|
>
|
||||||
|
<ViewListIcon className="view-toggle__icon" size={18} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`view-toggle__button${isGridView ? ' active' : ''}`}
|
||||||
|
onClick={() => onViewModeChange?.('grid')}
|
||||||
|
aria-pressed={isGridView}
|
||||||
|
title="Icons view"
|
||||||
|
>
|
||||||
|
<ViewGridIcon className="view-toggle__icon" size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span className="main-content__actions-divider" aria-hidden="true">
|
||||||
|
<MinusVerticalIcon />
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={onRequestCreateFolder}
|
||||||
|
disabled={creatingFolder}
|
||||||
|
aria-label={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||||
|
title={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||||
|
>
|
||||||
|
<FolderPlusIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={onRefresh}
|
||||||
|
aria-label="Refresh"
|
||||||
|
title="Refresh"
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
</button>
|
||||||
|
<button className="secondary" type="button" onClick={onShowSkeuoWorkspace}>
|
||||||
|
Desk View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`main-content__body main-content__body--documents${detailHasContent ? ' main-content__body--has-detail' : ''}`}
|
||||||
|
>
|
||||||
|
<DocumentsTable {...documentsTableProps} showHeader={false} />
|
||||||
|
</div>
|
||||||
|
{detailHasContent ? (
|
||||||
|
<DetailPanel {...detailPanelProps} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</DocumentsLayout>
|
</DocumentsLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { ChevronIcon, TrashIcon, EditIcon, FolderIcon } from '../ui/icons';
|
import { ChevronIcon, TrashIcon, EditIcon, FolderIcon, ChevronsLeftIcon } from '../ui/icons';
|
||||||
|
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
|
|
||||||
@@ -148,6 +148,7 @@ const Sidebar = ({
|
|||||||
previewActive,
|
previewActive,
|
||||||
onLogout,
|
onLogout,
|
||||||
status,
|
status,
|
||||||
|
onCollapse,
|
||||||
}) => {
|
}) => {
|
||||||
const sortedCorrespondents = useMemo(
|
const sortedCorrespondents = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -234,9 +235,30 @@ const Sidebar = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<div className="sidebar__meta">
|
<div className="panel-header sidebar__header">
|
||||||
|
<div className="panel-actions">
|
||||||
<h1 className="sidebar__title">Papercrate</h1>
|
<h1 className="sidebar__title">Papercrate</h1>
|
||||||
|
<div className="spacer" />
|
||||||
|
{onCollapse ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button ghost"
|
||||||
|
onClick={onCollapse}
|
||||||
|
aria-label="Collapse sidebar"
|
||||||
|
title="Collapse sidebar"
|
||||||
|
>
|
||||||
|
<ChevronsLeftIcon />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="panel-body sidebar__body">
|
||||||
<span className="sidebar__hint">{hintText}</span>
|
<span className="sidebar__hint">{hintText}</span>
|
||||||
|
{status && (
|
||||||
|
<div className="sidebar__status">
|
||||||
|
<div className={`status-banner ${status.variant}`}>{status.message}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{onSearchChange && (
|
{onSearchChange && (
|
||||||
<form className="sidebar__search" onSubmit={handleSearchFormSubmit}>
|
<form className="sidebar__search" onSubmit={handleSearchFormSubmit}>
|
||||||
<input
|
<input
|
||||||
@@ -253,12 +275,6 @@ const Sidebar = ({
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
{status && (
|
|
||||||
<div className="sidebar__status">
|
|
||||||
<div className={`status-banner ${status.variant}`}>{status.message}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="sidebar-section sidebar-section--folders">
|
<div className="sidebar-section sidebar-section--folders">
|
||||||
<div className="sidebar-section__header">
|
<div className="sidebar-section__header">
|
||||||
<h3>Folders</h3>
|
<h3>Folders</h3>
|
||||||
@@ -365,6 +381,7 @@ const Sidebar = ({
|
|||||||
Log out
|
Log out
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,55 +15,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeuo-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.75rem 1rem 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-header__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-header__meta h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-breadcrumbs {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.35rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-crumb {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-crumb.is-current {
|
|
||||||
color: var(--fg);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-crumb-separator {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-header__actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeuo-canvas {
|
.skeuo-canvas {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -547,10 +547,6 @@ const getContrastingTextColor = (hex) => getReadableTextColor(hex, { light: '#1f
|
|||||||
const SkeuomorphicWorkspace = ({
|
const SkeuomorphicWorkspace = ({
|
||||||
documents = [],
|
documents = [],
|
||||||
searchResults = null,
|
searchResults = null,
|
||||||
breadcrumbs = [],
|
|
||||||
currentFolderName = 'Folder',
|
|
||||||
onExit,
|
|
||||||
onRefresh,
|
|
||||||
onDocumentOpen,
|
onDocumentOpen,
|
||||||
resolveThumbnailUrl,
|
resolveThumbnailUrl,
|
||||||
onAssignTagToDocument = null,
|
onAssignTagToDocument = null,
|
||||||
@@ -560,7 +556,6 @@ const SkeuomorphicWorkspace = ({
|
|||||||
activeTagIds = [],
|
activeTagIds = [],
|
||||||
}) => {
|
}) => {
|
||||||
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
|
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
|
||||||
const showingSearchResults = searchResults !== null;
|
|
||||||
|
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
@@ -1491,20 +1486,6 @@ const SkeuomorphicWorkspace = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="skeuo-shell">
|
<div className="skeuo-shell">
|
||||||
<header className="skeuo-header">
|
|
||||||
<div className="skeuo-header__meta">
|
|
||||||
<h2>{currentFolderName}</h2>
|
|
||||||
{showingSearchResults && <span className="meta">Showing search results</span>}
|
|
||||||
</div>
|
|
||||||
<div className="skeuo-header__actions">
|
|
||||||
<button type="button" className="secondary" onClick={onRefresh}>
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={onExit}>
|
|
||||||
Back to List
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div
|
<div
|
||||||
className="skeuo-canvas"
|
className="skeuo-canvas"
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|||||||
+97
-37
@@ -352,39 +352,34 @@ button.danger:hover:not([disabled]) {
|
|||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.view-toggle {
|
.view-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 999px;
|
gap: 0.25rem;
|
||||||
background: var(--surface-subtle);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-toggle__button {
|
.view-toggle__button {
|
||||||
width: 2.5rem;
|
border: 1px solid var(--border);
|
||||||
height: 2.2rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s ease, color 0.15s ease;
|
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-toggle__button:hover,
|
.view-toggle__button:hover,
|
||||||
.view-toggle__button:focus-visible {
|
.view-toggle__button:focus-visible {
|
||||||
background: transparent;
|
background: var(--surface-subtle);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-toggle__button.active {
|
.view-toggle__button.active {
|
||||||
background: var(--accent-soft, rgba(63, 106, 216, 0.16));
|
background: var(--accent);
|
||||||
color: var(--accent);
|
color: var(--on-accent);
|
||||||
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-toggle__icon {
|
.view-toggle__icon {
|
||||||
@@ -403,35 +398,96 @@ button.danger:hover:not([disabled]) {
|
|||||||
|
|
||||||
.documents-main {
|
.documents-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 20rem minmax(0, 1fr);
|
grid-template-columns: 20rem minmax(0, 1fr);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
height: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.documents-main--sidebar-collapsed {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
position: relative;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documents-main .main-content--documents > * {
|
.main-content__header {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content__body {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content__body > * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documents-main .main-content--documents > *:not(.detail-panel) {
|
.main-content__body > *:not(.detail-panel) {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documents-main .main-content--documents.main-content--has-detail {
|
.main-content--has-detail {
|
||||||
padding-right: var(--detail-panel-width);
|
padding-right: var(--detail-panel-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-content__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content__subtitle {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.documents-main--sidebar-collapsed {
|
||||||
|
position: relative;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .panel-actions .icon,
|
||||||
|
.main-content__header .panel-actions .icon,
|
||||||
|
.detail-panel .panel-actions .icon {
|
||||||
|
width: 1.35rem;
|
||||||
|
height: 1.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content__actions-divider {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
.panels-main {
|
.panels-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -936,25 +992,26 @@ button.danger:hover:not([disabled]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
gap: 0.28rem;
|
gap: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1.25rem;
|
|
||||||
color: var(--sidebar-fg);
|
color: var(--sidebar-fg);
|
||||||
box-shadow: inset -12px 0 24px -12px var(--shadow-faint);
|
box-shadow: inset -12px 0 24px -12px var(--shadow-faint);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__meta {
|
.sidebar__body {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.2rem;
|
padding: 0.5rem 1rem;
|
||||||
margin-bottom: 1rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__title {
|
.sidebar__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.2rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
@@ -968,7 +1025,7 @@ button.danger:hover:not([disabled]) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin-top: 0.6rem;
|
margin: 0 0 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__search input[type='search'] {
|
.sidebar__search input[type='search'] {
|
||||||
@@ -994,10 +1051,6 @@ button.danger:hover:not([disabled]) {
|
|||||||
background: var(--sidebar-hover-bg);
|
background: var(--sidebar-hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__status {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__footer {
|
.sidebar__footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
@@ -1181,7 +1234,7 @@ button.danger:hover:not([disabled]) {
|
|||||||
|
|
||||||
|
|
||||||
.documents-panel {
|
.documents-panel {
|
||||||
padding: 1.25rem 1.25rem 0 1.25rem;
|
padding: 0 0.75rem 0.75rem 0.75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -1653,9 +1706,12 @@ button.danger:hover:not([disabled]) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0.25rem;
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0.25rem;
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-actions {
|
.panel-actions {
|
||||||
@@ -1664,6 +1720,11 @@ button.danger:hover:not([disabled]) {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-actions > h1:first-child,
|
||||||
|
.panel-actions > h2:first-child {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-actions .spacer {
|
.panel-actions .spacer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@@ -2033,7 +2094,6 @@ form.inline {
|
|||||||
padding: 0.45rem 0.8rem;
|
padding: 0.45rem 0.8rem;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-banner.info {
|
.status-banner.info {
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ import {
|
|||||||
IconLayoutGrid,
|
IconLayoutGrid,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
|
IconArrowUp,
|
||||||
IconChevronsRight,
|
IconChevronsRight,
|
||||||
|
IconChevronsLeft,
|
||||||
IconAnalyze,
|
IconAnalyze,
|
||||||
IconWindowMaximize,
|
IconWindowMaximize,
|
||||||
IconTextScan2,
|
IconTextScan2,
|
||||||
|
IconFolderPlus,
|
||||||
|
IconRefresh,
|
||||||
|
IconMinusVertical,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import FolderSvg from '../assets/folder.svg';
|
import FolderSvg from '../assets/folder.svg';
|
||||||
|
|
||||||
@@ -124,6 +129,15 @@ export const ArrowRightIcon = ({ className, size = '1em', stroke = 1.6, ...rest
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ChevronsLeftIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconChevronsLeft
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const ChevronsRightIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const ChevronsRightIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconChevronsRight
|
<IconChevronsRight
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
@@ -133,6 +147,42 @@ export const ChevronsRightIcon = ({ className, size = '1em', stroke = 1.6, ...re
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const FolderPlusIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconFolderPlus
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RefreshIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconRefresh
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ArrowUpIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconArrowUp
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MinusVerticalIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconMinusVertical
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconAnalyze
|
<IconAnalyze
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
@@ -161,9 +211,14 @@ export default {
|
|||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ViewListIcon,
|
ViewListIcon,
|
||||||
ViewGridIcon,
|
ViewGridIcon,
|
||||||
|
ChevronsLeftIcon,
|
||||||
ChevronsRightIcon,
|
ChevronsRightIcon,
|
||||||
AnalyzeIcon,
|
AnalyzeIcon,
|
||||||
WindowMaximizeIcon,
|
WindowMaximizeIcon,
|
||||||
|
FolderPlusIcon,
|
||||||
|
RefreshIcon,
|
||||||
|
ArrowUpIcon,
|
||||||
|
MinusVerticalIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextScanIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const TextScanIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user