panelheader
This commit is contained in:
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useAppShell } from '../appShellContext';
|
import { useAppShell } from '../appShellContext';
|
||||||
import DocumentsLayout from './DocumentsLayout';
|
import DocumentsLayout from './DocumentsLayout';
|
||||||
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
||||||
|
import PanelHeader from '../ui/PanelHeader';
|
||||||
|
|
||||||
const DocumentsRoute = () => {
|
const DocumentsRoute = () => {
|
||||||
const {
|
const {
|
||||||
@@ -103,6 +104,17 @@ const DocumentsRoute = () => {
|
|||||||
|
|
||||||
const header = surface.header || null;
|
const header = surface.header || null;
|
||||||
|
|
||||||
|
const headerTitle = header
|
||||||
|
? header.subtitle
|
||||||
|
? (
|
||||||
|
<h2 className="main-content__title">
|
||||||
|
{header.title}
|
||||||
|
<span className="main-content__subtitle">{header.subtitle}</span>
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
: header.title
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentsLayout
|
<DocumentsLayout
|
||||||
sidebarProps={sidebarPropsWithActions}
|
sidebarProps={sidebarPropsWithActions}
|
||||||
@@ -110,17 +122,13 @@ const DocumentsRoute = () => {
|
|||||||
>
|
>
|
||||||
<div className={mainContentClass}>
|
<div className={mainContentClass}>
|
||||||
{header ? (
|
{header ? (
|
||||||
<div className="panel-header main-content__header">
|
<PanelHeader
|
||||||
{header.leading}
|
className="main-content__header"
|
||||||
<h2 className="main-content__title">
|
leading={header.leading}
|
||||||
{header.title}
|
title={headerTitle}
|
||||||
{header.subtitle ? (
|
titleTag="h2"
|
||||||
<span className="main-content__subtitle">{header.subtitle}</span>
|
actions={header.actions}
|
||||||
) : null}
|
/>
|
||||||
</h2>
|
|
||||||
<div className="panel-actions__spacer" />
|
|
||||||
{header.actions}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
<div className={bodyClass}>{surface.content}</div>
|
<div className={bodyClass}>{surface.content}</div>
|
||||||
{surface.detail || null}
|
{surface.detail || null}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
WindowMaximizeIcon,
|
WindowMaximizeIcon,
|
||||||
TextScanIcon,
|
TextScanIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
|
import PanelHeader from '../ui/PanelHeader';
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
import { formatFileSize } from '../utils/format';
|
import { formatFileSize } from '../utils/format';
|
||||||
import { resolveDocumentAssetUrl, createAssetView } from '../asset_manager';
|
import { resolveDocumentAssetUrl, createAssetView } from '../asset_manager';
|
||||||
@@ -1239,11 +1240,10 @@ const DetailPanel = ({
|
|||||||
const isBulkSelection = selectedCount > 1;
|
const isBulkSelection = selectedCount > 1;
|
||||||
const showOcrAction = Boolean(singleDoc && singleHasOcr);
|
const showOcrAction = Boolean(singleDoc && singleHasOcr);
|
||||||
|
|
||||||
return (
|
const headerLeading = [
|
||||||
<>
|
(
|
||||||
<aside className="detail-panel panel">
|
|
||||||
<div className="panel-header">
|
|
||||||
<button
|
<button
|
||||||
|
key="close"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -1252,8 +1252,13 @@ const DetailPanel = ({
|
|||||||
>
|
>
|
||||||
<ChevronsRightIcon />
|
<ChevronsRightIcon />
|
||||||
</button>
|
</button>
|
||||||
{singleDoc ? (
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (singleDoc) {
|
||||||
|
headerLeading.push(
|
||||||
<button
|
<button
|
||||||
|
key="preview"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
@@ -1265,12 +1270,16 @@ const DetailPanel = ({
|
|||||||
disabled={!singleHasPreview}
|
disabled={!singleHasPreview}
|
||||||
>
|
>
|
||||||
<WindowMaximizeIcon />
|
<WindowMaximizeIcon />
|
||||||
</button>
|
</button>,
|
||||||
) : null}
|
);
|
||||||
<h3 className="panel-header__title">{headerTitle}</h3>
|
}
|
||||||
<div className="panel-actions__spacer" />
|
|
||||||
{isBulkSelection && onBulkReanalyze ? (
|
const headerActions = [];
|
||||||
|
|
||||||
|
if (isBulkSelection && onBulkReanalyze) {
|
||||||
|
headerActions.push(
|
||||||
<button
|
<button
|
||||||
|
key="bulk-reanalyze"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
@@ -1281,10 +1290,14 @@ const DetailPanel = ({
|
|||||||
title="Re-run analysis for selection"
|
title="Re-run analysis for selection"
|
||||||
>
|
>
|
||||||
<AnalyzeIcon />
|
<AnalyzeIcon />
|
||||||
</button>
|
</button>,
|
||||||
) : null}
|
);
|
||||||
{singleDoc && singleDownloadHref ? (
|
}
|
||||||
|
|
||||||
|
if (singleDoc && singleDownloadHref) {
|
||||||
|
headerActions.push(
|
||||||
<a
|
<a
|
||||||
|
key="download"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
href={singleDownloadHref}
|
href={singleDownloadHref}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -1294,10 +1307,14 @@ const DetailPanel = ({
|
|||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
</a>
|
</a>,
|
||||||
) : null}
|
);
|
||||||
{showOcrAction ? (
|
}
|
||||||
|
|
||||||
|
if (showOcrAction) {
|
||||||
|
headerActions.push(
|
||||||
<button
|
<button
|
||||||
|
key="ocr"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
@@ -1308,10 +1325,14 @@ const DetailPanel = ({
|
|||||||
title="View OCR text"
|
title="View OCR text"
|
||||||
>
|
>
|
||||||
<TextScanIcon />
|
<TextScanIcon />
|
||||||
</button>
|
</button>,
|
||||||
) : null}
|
);
|
||||||
{singleDoc ? (
|
}
|
||||||
|
|
||||||
|
if (singleDoc) {
|
||||||
|
headerActions.push(
|
||||||
<button
|
<button
|
||||||
|
key="reanalyze"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
@@ -1322,9 +1343,19 @@ const DetailPanel = ({
|
|||||||
title="Re-run analysis"
|
title="Re-run analysis"
|
||||||
>
|
>
|
||||||
<AnalyzeIcon />
|
<AnalyzeIcon />
|
||||||
</button>
|
</button>,
|
||||||
) : null}
|
);
|
||||||
</div>
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<aside className="detail-panel panel">
|
||||||
|
<PanelHeader
|
||||||
|
leading={headerLeading}
|
||||||
|
title={headerTitle}
|
||||||
|
titleTag="h3"
|
||||||
|
actions={headerActions.length ? headerActions : null}
|
||||||
|
/>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
{selectedCount <= 1 ? renderSingle() : renderBulk()}
|
{selectedCount <= 1 ? renderSingle() : renderBulk()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FolderPlusIcon,
|
FolderPlusIcon,
|
||||||
RestoreIcon,
|
RestoreIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
|
import PanelHeader from '../ui/PanelHeader';
|
||||||
|
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
|
|
||||||
@@ -351,7 +352,10 @@ const Sidebar = ({
|
|||||||
const rootNode = folderNodes.get('root');
|
const rootNode = folderNodes.get('root');
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<div className="panel-header sidebar__header">
|
<PanelHeader
|
||||||
|
className="sidebar__header"
|
||||||
|
leading={(
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`sidebar__title-button${tenantMenuOpen ? ' is-open' : ''}`}
|
className={`sidebar__title-button${tenantMenuOpen ? ' is-open' : ''}`}
|
||||||
@@ -422,9 +426,14 @@ const Sidebar = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="panel-actions__spacer" />
|
</>
|
||||||
{onCollapse ? (
|
)}
|
||||||
|
actions={
|
||||||
|
onCollapse
|
||||||
|
? [
|
||||||
|
(
|
||||||
<button
|
<button
|
||||||
|
key="collapse"
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
@@ -433,8 +442,11 @@ const Sidebar = ({
|
|||||||
>
|
>
|
||||||
<ChevronsLeftIcon />
|
<ChevronsLeftIcon />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
),
|
||||||
</div>
|
]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
<div className="panel-body sidebar__body">
|
<div className="panel-body sidebar__body">
|
||||||
{status && (
|
{status && (
|
||||||
<div className="sidebar__status">
|
<div className="sidebar__status">
|
||||||
|
|||||||
+16
-17
@@ -1223,6 +1223,7 @@ button.danger:hover:not([disabled]) {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
|
padding-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__tenant {
|
.sidebar__tenant {
|
||||||
@@ -2198,11 +2199,17 @@ button.danger:hover:not([disabled]) {
|
|||||||
.panel-header {
|
.panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-header__leading,
|
||||||
|
.panel-header__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-header__title {
|
.panel-header__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -2215,26 +2222,18 @@ button.danger:hover:not([disabled]) {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-header > .panel-header__title:first-child {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header__actions {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-actions__spacer {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-actions > h1:first-child,
|
|
||||||
.panel-actions > h2:first-child {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-panel .panel-body {
|
.detail-panel .panel-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const composeClassName = (base, extra) => (extra ? `${base} ${extra}` : base);
|
||||||
|
|
||||||
|
const PanelHeader = ({
|
||||||
|
className = '',
|
||||||
|
leading = null,
|
||||||
|
title = null,
|
||||||
|
titleTag = 'h2',
|
||||||
|
titleProps = {},
|
||||||
|
actions = null,
|
||||||
|
}) => {
|
||||||
|
const headerClassName = composeClassName('panel-header', className);
|
||||||
|
|
||||||
|
const renderTitle = () => {
|
||||||
|
if (title == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (React.isValidElement(title)) {
|
||||||
|
if (title.type === React.Fragment) {
|
||||||
|
return (
|
||||||
|
<span className="panel-header__title" {...titleProps}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedClassName = composeClassName('panel-header__title', title.props.className || '');
|
||||||
|
return React.cloneElement(title, {
|
||||||
|
...titleProps,
|
||||||
|
className: mergedClassName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeadingTag = titleTag;
|
||||||
|
return (
|
||||||
|
<HeadingTag className="panel-header__title" {...titleProps}>
|
||||||
|
{title}
|
||||||
|
</HeadingTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={headerClassName}>
|
||||||
|
{leading ? <div className="panel-header__leading">{leading}</div> : null}
|
||||||
|
{renderTitle()}
|
||||||
|
{actions ? <div className="panel-header__actions">{actions}</div> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PanelHeader;
|
||||||
Reference in New Issue
Block a user