panel actions

This commit is contained in:
2025-10-26 19:14:35 +01:00
parent b163a07e84
commit b6300ffa30
7 changed files with 522 additions and 263 deletions
+44 -36
View File
@@ -177,6 +177,7 @@ const DocumentsTable = ({
viewMode = 'list',
onViewModeChange,
onClearSelection,
showHeader = true,
}) => {
const showingSearchResults = searchResults !== null;
const rows = showingSearchResults ? searchResults : documents;
@@ -220,6 +221,11 @@ const DocumentsTable = ({
},
[onViewModeChange],
);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = 0;
}
}, [viewMode]);
const isTagDragEvent = useCallback((event) => {
const types = Array.from(event.dataTransfer?.types || []);
return TAG_MIME_TYPES.some((type) => types.includes(type));
@@ -338,49 +344,51 @@ const DocumentsTable = ({
<section
className={`documents-panel column documents-panel--view-${isGridView ? 'grid' : 'list'}`}
>
<div className="column-header">
<div className="column-header__titles">
<h2>{currentFolderName}</h2>
{showingSearchResults && (
<div className="column-subtitle">Search results</div>
)}
</div>
<div className="header-actions">
<div className="view-toggle" role="group" aria-label="Change view">
{showHeader ? (
<div className="column-header">
<div className="column-header__titles">
<h2>{currentFolderName}</h2>
{showingSearchResults && (
<div className="column-subtitle">Search results</div>
)}
</div>
<div className="header-actions">
<div className="view-toggle" role="group" aria-label="Change view">
<button
type="button"
className={`view-toggle__button${isGridView ? '' : ' active'}`}
onClick={() => handleSetViewMode('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={() => handleSetViewMode('grid')}
aria-pressed={isGridView}
title="Icons view"
>
<ViewGridIcon className="view-toggle__icon" size={18} />
</button>
</div>
<button
type="button"
className={`view-toggle__button${isGridView ? '' : ' active'}`}
onClick={() => handleSetViewMode('list')}
aria-pressed={!isGridView}
title="List view"
onClick={onRequestCreateFolder}
disabled={creatingFolder}
>
<ViewListIcon className="view-toggle__icon" size={18} />
{creatingFolder ? 'Creating…' : 'New folder'}
</button>
<button
type="button"
className={`view-toggle__button${isGridView ? ' active' : ''}`}
onClick={() => handleSetViewMode('grid')}
aria-pressed={isGridView}
title="Icons view"
>
<ViewGridIcon className="view-toggle__icon" size={18} />
<button className="secondary" onClick={onRefresh}>
Refresh
</button>
<button className="secondary" type="button" onClick={onShowSkeuoWorkspace}>
Desk View
</button>
</div>
<button
type="button"
onClick={onRequestCreateFolder}
disabled={creatingFolder}
>
{creatingFolder ? 'Creating…' : 'New folder'}
</button>
<button className="secondary" onClick={onRefresh}>
Refresh
</button>
<button className="secondary" type="button" onClick={onShowSkeuoWorkspace}>
Desk View
</button>
</div>
</div>
) : null}
{showDefaultEmptyState && (
<div className="empty-state">
Drop files anywhere or onto a folder to upload documents.
+201 -14
View File
@@ -32,6 +32,16 @@ import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
import { CORRESPONDENT_ROLES } from './constants/correspondents';
import TagManager from './tag_manager';
import Sidebar from './sidebar/Sidebar';
import {
ChevronsRightIcon,
ChevronsLeftIcon,
ViewListIcon,
ViewGridIcon,
FolderPlusIcon,
RefreshIcon,
ArrowUpIcon,
MinusVerticalIcon,
} from './ui/icons';
import DocumentsTable from './documents/DocumentsTable';
import { AppShellContext, useAppShell } from './appShellContext';
import DocumentViewerRoute from './routes/DocumentViewerRoute';
@@ -311,9 +321,9 @@ const LoginView = ({
const DocumentsLayout = ({ sidebarProps, children }) => (
<main className="documents-main">
<Sidebar {...sidebarProps} />
const DocumentsLayout = ({ sidebarProps, children, sidebarCollapsed }) => (
<main className={`documents-main${sidebarCollapsed ? ' documents-main--sidebar-collapsed' : ''}`}>
{!sidebarCollapsed ? <Sidebar {...sidebarProps} /> : null}
{children}
</main>
);
@@ -337,6 +347,9 @@ const AppLayout = () => {
({ message, variant }) => setStatusMessage(message, variant),
[setStatusMessage],
);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const collapseSidebar = useCallback(() => setSidebarCollapsed(true), []);
const expandSidebar = useCallback(() => setSidebarCollapsed(false), []);
const reportApiError = useApiError({
onReport: handleApiReport,
});
@@ -4488,6 +4501,7 @@ const AppLayout = () => {
isFilterActive,
onLogout: handleLogout,
status,
onCollapse: collapseSidebar,
};
const resolveThumbnailUrlForDoc = useCallback(
@@ -4847,36 +4861,209 @@ const DocumentsRoute = () => {
skeuoWorkspaceProps,
openTagsModal,
openCorrespondentsModal,
exitSkeuoWorkspace,
} = useAppShell();
const navigate = useNavigate();
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const collapseSidebar = useCallback(() => setSidebarCollapsed(true), []);
const expandSidebar = useCallback(() => setSidebarCollapsed(false), []);
const sidebarPropsWithActions = useMemo(
() => ({
...sidebarProps,
onManageTags: openTagsModal,
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') {
return (
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
<DocumentsLayout
sidebarProps={sidebarPropsWithActions}
sidebarCollapsed={sidebarCollapsed}
>
<div className="main-content">
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
<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} />
</div>
</div>
</DocumentsLayout>
);
}
const detailHasContent = detailPanelProps.selectedDocuments?.length > 0;
return (
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
<div
className={`main-content main-content--documents${detailHasContent ? ' main-content--has-detail' : ''}`}
>
<DocumentsTable {...documentsTableProps} />
{detailHasContent ? <DetailPanel {...detailPanelProps} /> : null}
<DocumentsLayout
sidebarProps={sidebarPropsWithActions}
sidebarCollapsed={sidebarCollapsed}
>
<div className={`main-content main-content--documents${detailHasContent ? ' main-content--has-detail' : ''}`}>
<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>
</DocumentsLayout>
);
+125 -108
View File
@@ -1,5 +1,5 @@
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';
@@ -148,6 +148,7 @@ const Sidebar = ({
previewActive,
onLogout,
status,
onCollapse,
}) => {
const sortedCorrespondents = useMemo(
() =>
@@ -234,9 +235,30 @@ const Sidebar = ({
return (
<aside className="sidebar">
<div className="sidebar__meta">
<h1 className="sidebar__title">Papercrate</h1>
<div className="panel-header sidebar__header">
<div className="panel-actions">
<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>
{status && (
<div className="sidebar__status">
<div className={`status-banner ${status.variant}`}>{status.message}</div>
</div>
)}
{onSearchChange && (
<form className="sidebar__search" onSubmit={handleSearchFormSubmit}>
<input
@@ -253,117 +275,112 @@ const Sidebar = ({
)}
</form>
)}
{status && (
<div className="sidebar__status">
<div className={`status-banner ${status.variant}`}>{status.message}</div>
<div className="sidebar-section sidebar-section--folders">
<div className="sidebar-section__header">
<h3>Folders</h3>
</div>
)}
</div>
<div className="sidebar-section sidebar-section--folders">
<div className="sidebar-section__header">
<h3>Folders</h3>
<ul className="folder-tree">
{rootNode && renderNodes([rootNode.id], 0)}
</ul>
</div>
<ul className="folder-tree">
{rootNode && renderNodes([rootNode.id], 0)}
</ul>
</div>
<div className="sidebar-section">
<div className="sidebar-section__header">
<button
type="button"
className="sidebar-section__title"
onClick={handleManageTags}
<div className="sidebar-section">
<div className="sidebar-section__header">
<button
type="button"
className="sidebar-section__title"
onClick={handleManageTags}
>
<h3>Tags</h3>
<span className="meta">{tags.length}</span>
</button>
</div>
<div
className={`sidebar-tag-cloud${
activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
}`}
role="list"
>
<h3>Tags</h3>
<span className="meta">{tags.length}</span>
</button>
</div>
<div
className={`sidebar-tag-cloud${
activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
}`}
role="list"
>
{tags.map((tag) => {
const isActive = activeTagSet.has(tag.id);
const style = getTagColorStyle(tag.color);
const className = `sidebar-tag-pill${isActive ? ' active' : ''}`;
return (
<button
key={tag.id}
type="button"
role="listitem"
className={className}
style={style || undefined}
onClick={() => handleToggleTag(tag.id)}
aria-pressed={isActive}
draggable
onDragStart={(event) => {
try {
const payload = JSON.stringify({
id: tag.id,
label: tag.label,
color: tag.color || null,
});
event.dataTransfer.effectAllowed = 'copy';
event.dataTransfer.setData('application/x-papercrate-tag', payload);
event.dataTransfer.setData('text/papercrate-tag', payload);
} catch (
// eslint-disable-next-line no-empty
error
) {}
}}
>
{tag.label}
</button>
);
})}
</div>
</div>
<div className="sidebar-section">
<div className="sidebar-section__header">
<button
type="button"
className="sidebar-section__title"
onClick={handleManageCorrespondents}
>
<h3>Correspondents</h3>
<span className="meta">{correspondents.length}</span>
</button>
</div>
<ul className="sidebar-correspondent-list">
{sortedCorrespondents.map((correspondent) => {
const isActive = activeCorrespondentSet.has(correspondent.id);
const className = `sidebar-correspondent-item${isActive ? ' active' : ''}`;
const label = correspondent.name || 'Unnamed';
const handleSelect = () => {
const nextId = isActive ? null : correspondent.id;
onToggleCorrespondentFilter?.(nextId);
};
return (
<li key={correspondent.id}>
<span
{tags.map((tag) => {
const isActive = activeTagSet.has(tag.id);
const style = getTagColorStyle(tag.color);
const className = `sidebar-tag-pill${isActive ? ' active' : ''}`;
return (
<button
key={tag.id}
type="button"
role="listitem"
className={className}
role="button"
onClick={handleSelect}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleSelect();
}
style={style || undefined}
onClick={() => handleToggleTag(tag.id)}
aria-pressed={isActive}
draggable
onDragStart={(event) => {
try {
const payload = JSON.stringify({
id: tag.id,
label: tag.label,
color: tag.color || null,
});
event.dataTransfer.effectAllowed = 'copy';
event.dataTransfer.setData('application/x-papercrate-tag', payload);
event.dataTransfer.setData('text/papercrate-tag', payload);
} catch (
// eslint-disable-next-line no-empty
error
) {}
}}
>
{label}
</span>
</li>
);
})}
</ul>
</div>
<div className="sidebar__footer">
<button className="secondary" type="button" onClick={handleLogoutClick}>
Log out
</button>
{tag.label}
</button>
);
})}
</div>
</div>
<div className="sidebar-section">
<div className="sidebar-section__header">
<button
type="button"
className="sidebar-section__title"
onClick={handleManageCorrespondents}
>
<h3>Correspondents</h3>
<span className="meta">{correspondents.length}</span>
</button>
</div>
<ul className="sidebar-correspondent-list">
{sortedCorrespondents.map((correspondent) => {
const isActive = activeCorrespondentSet.has(correspondent.id);
const className = `sidebar-correspondent-item${isActive ? ' active' : ''}`;
const label = correspondent.name || 'Unnamed';
const handleSelect = () => {
const nextId = isActive ? null : correspondent.id;
onToggleCorrespondentFilter?.(nextId);
};
return (
<li key={correspondent.id}>
<span
className={className}
role="button"
onClick={handleSelect}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleSelect();
}
}}
>
{label}
</span>
</li>
);
})}
</ul>
</div>
<div className="sidebar__footer">
<button className="secondary" type="button" onClick={handleLogoutClick}>
Log out
</button>
</div>
</div>
</aside>
);
-49
View File
@@ -15,55 +15,6 @@
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 {
flex: 1;
position: relative;
-19
View File
@@ -547,10 +547,6 @@ const getContrastingTextColor = (hex) => getReadableTextColor(hex, { light: '#1f
const SkeuomorphicWorkspace = ({
documents = [],
searchResults = null,
breadcrumbs = [],
currentFolderName = 'Folder',
onExit,
onRefresh,
onDocumentOpen,
resolveThumbnailUrl,
onAssignTagToDocument = null,
@@ -560,7 +556,6 @@ const SkeuomorphicWorkspace = ({
activeTagIds = [],
}) => {
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
const showingSearchResults = searchResults !== null;
const containerRef = useRef(null);
@@ -1491,20 +1486,6 @@ const SkeuomorphicWorkspace = ({
return (
<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
className="skeuo-canvas"
ref={containerRef}
+97 -37
View File
@@ -352,39 +352,34 @@ button.danger:hover:not([disabled]) {
fill: currentColor;
}
.view-toggle {
display: inline-flex;
align-items: center;
border-radius: 999px;
background: var(--surface-subtle);
margin-right: 0.5rem;
overflow: hidden;
gap: 0.25rem;
}
.view-toggle__button {
width: 2.5rem;
height: 2.2rem;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
padding: 0.3rem 0.6rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
padding: 0;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.view-toggle__button:hover,
.view-toggle__button:focus-visible {
background: transparent;
background: var(--surface-subtle);
color: var(--fg);
outline: none;
}
.view-toggle__button.active {
background: var(--accent-soft, rgba(63, 106, 216, 0.16));
color: var(--accent);
background: var(--accent);
color: var(--on-accent);
border-color: var(--accent);
}
.view-toggle__icon {
@@ -403,35 +398,96 @@ button.danger:hover:not([disabled]) {
.documents-main {
flex: 1;
display: grid;
display: flex;
grid-template-columns: 20rem minmax(0, 1fr);
min-height: 100vh;
height: 100%;
width: 100%;
position: relative;
}
.documents-main--sidebar-collapsed {
position: relative;
}
.main-content {
display: flex;
flex-direction: row;
flex-direction: column;
min-height: 0;
margin: 0;
border-radius: 0;
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-height: 0;
}
.documents-main .main-content--documents > *:not(.detail-panel) {
.main-content__body > *:not(.detail-panel) {
flex: 1 1 auto;
}
.documents-main .main-content--documents.main-content--has-detail {
.main-content--has-detail {
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 {
flex: 1;
display: flex;
@@ -936,25 +992,26 @@ button.danger:hover:not([disabled]) {
}
.sidebar {
gap: 0.28rem;
gap: 0;
overflow-y: auto;
padding: 1.25rem;
color: var(--sidebar-fg);
box-shadow: inset -12px 0 24px -12px var(--shadow-faint);
display: flex;
flex-direction: column;
max-width: 20em;
}
.sidebar__meta {
.sidebar__body {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.2rem;
margin-bottom: 1rem;
padding: 0.5rem 1rem;
gap: 0.75rem;
}
.sidebar__title {
margin: 0;
font-size: 1.2rem;
font-size: 1rem;
font-weight: 600;
color: var(--fg);
}
@@ -968,7 +1025,7 @@ button.danger:hover:not([disabled]) {
display: flex;
align-items: center;
gap: 0.4rem;
margin-top: 0.6rem;
margin: 0 0 0.75rem;
}
.sidebar__search input[type='search'] {
@@ -994,10 +1051,6 @@ button.danger:hover:not([disabled]) {
background: var(--sidebar-hover-bg);
}
.sidebar__status {
margin-top: 0.75rem;
}
.sidebar__footer {
margin-top: auto;
padding-top: 1rem;
@@ -1181,7 +1234,7 @@ button.danger:hover:not([disabled]) {
.documents-panel {
padding: 1.25rem 1.25rem 0 1.25rem;
padding: 0 0.75rem 0.75rem 0.75rem;
display: flex;
flex-direction: column;
min-height: 0;
@@ -1653,9 +1706,12 @@ button.danger:hover:not([disabled]) {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0.25rem;
gap: 0.5rem;
padding: 0.25rem;
padding: 0.5rem;
}
.panel-body {
padding: 0.5rem 1rem;
}
.panel-actions {
@@ -1664,6 +1720,11 @@ button.danger:hover:not([disabled]) {
flex-grow: 1;
}
.panel-actions > h1:first-child,
.panel-actions > h2:first-child {
padding-left: 0.5rem;
}
.panel-actions .spacer {
flex-grow: 1;
}
@@ -2033,7 +2094,6 @@ form.inline {
padding: 0.45rem 0.8rem;
border-radius: 2px;
font-size: 0.85rem;
margin-bottom: 0.8rem;
}
.status-banner.info {
+55
View File
@@ -9,10 +9,15 @@ import {
IconLayoutGrid,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconChevronsRight,
IconChevronsLeft,
IconAnalyze,
IconWindowMaximize,
IconTextScan2,
IconFolderPlus,
IconRefresh,
IconMinusVertical,
} from '@tabler/icons-react';
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 }) => (
<IconChevronsRight
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 }) => (
<IconAnalyze
className={composeClassName('icon', className)}
@@ -161,9 +211,14 @@ export default {
DownloadIcon,
ViewListIcon,
ViewGridIcon,
ChevronsLeftIcon,
ChevronsRightIcon,
AnalyzeIcon,
WindowMaximizeIcon,
FolderPlusIcon,
RefreshIcon,
ArrowUpIcon,
MinusVerticalIcon,
};
export const TextScanIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (