document viewer

This commit is contained in:
2025-11-12 02:24:46 +01:00
parent 762e4ee315
commit 44c982f015
7 changed files with 546 additions and 192 deletions
+108 -23
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import DocumentSummarySection from './DocumentSummarySection'; import DocumentSummarySection from './DocumentSummarySection';
import { buildDocumentMetadataItems, extractDocumentMetadataPayload } from './documentMetadata'; import { buildDocumentMetadataItems, extractDocumentMetadataPayload } from './documentMetadata';
@@ -16,6 +16,13 @@ const DocumentInfoPanel = ({
resetKey = null, resetKey = null,
classNamePrefix = 'document-info', classNamePrefix = 'document-info',
hideTabNavWhenSingle = true, hideTabNavWhenSingle = true,
summaryPlacement = 'inline',
summaryTabLabel = 'Summary',
summaryTabId = 'summary',
leadingTabs = [],
trailingTabs = [],
tabsPlacement = 'top',
summaryLayout = 'default',
}) => { }) => {
const base = classNamePrefix; const base = classNamePrefix;
@@ -92,13 +99,16 @@ const DocumentInfoPanel = ({
}; };
}, [contentConfig, contentEnabled, showContentTab, document?.id, resetKey]); }, [contentConfig, contentEnabled, showContentTab, document?.id, resetKey]);
const visibleTabs = useMemo(() => { const renderSummarySection = useCallback(() => (
const tabsList = []; <DocumentSummarySection
document={document}
detailItems={metadataItems}
layout={summaryLayout}
{...summaryProps}
/>
), [document, summaryLayout, summaryProps, metadataItems]);
tabsList.push({ const renderDetailsSection = useCallback(() => (
id: 'details',
label: detailsTabLabel,
render: () => (
<section className={`${base}__section`}> <section className={`${base}__section`}>
{metadataItems.length ? ( {metadataItems.length ? (
<dl className={`${base}__section-list`}> <dl className={`${base}__section-list`}>
@@ -113,8 +123,66 @@ const DocumentInfoPanel = ({
<p className={`${base}__section-placeholder`}>No details available.</p> <p className={`${base}__section-placeholder`}>No details available.</p>
)} )}
</section> </section>
), [base, metadataItems]);
const summaryInline = summaryPlacement !== 'tabs';
const summaryTab = useMemo(() => {
if (summaryPlacement !== 'tabs') {
return null;
}
return {
id: summaryTabId,
label: summaryTabLabel,
render: () => (
<div className={`${base}__summary-tab-content`}>
{renderSummarySection()}
</div>
), ),
};
}, [summaryPlacement, summaryTabId, summaryTabLabel, base, renderSummarySection]);
const normalizedLeadingTabs = useMemo(
() => (Array.isArray(leadingTabs)
? leadingTabs.filter((tab) => tab && tab.id && tab.label)
: []),
[leadingTabs],
);
const normalizedTrailingTabs = useMemo(
() => (Array.isArray(trailingTabs)
? trailingTabs.filter((tab) => tab && tab.id && tab.label)
: []),
[trailingTabs],
);
const summaryNode = summaryInline
? (
<>
{renderSummarySection()}
{renderDetailsSection()}
</>
)
: null;
const visibleTabs = useMemo(() => {
const tabsList = [];
if (normalizedLeadingTabs.length) {
tabsList.push(...normalizedLeadingTabs);
}
if (summaryTab) {
tabsList.push(summaryTab);
}
if (summaryPlacement !== 'tabs') {
tabsList.push({
id: 'details',
label: detailsTabLabel,
render: () => renderDetailsSection(),
}); });
}
if (showContentTab && contentConfig) { if (showContentTab && contentConfig) {
tabsList.push({ tabsList.push({
@@ -196,17 +264,25 @@ const DocumentInfoPanel = ({
}); });
} }
if (normalizedTrailingTabs.length) {
tabsList.push(...normalizedTrailingTabs);
}
return tabsList; return tabsList;
}, [ }, [
base, base,
detailsTabLabel, detailsTabLabel,
metadataItems,
contentConfig, contentConfig,
contentEnabled, contentEnabled,
contentState, contentState,
metadataPayload, metadataPayload,
metadataTabLabel, metadataTabLabel,
showContentTab, showContentTab,
summaryTab,
normalizedLeadingTabs,
normalizedTrailingTabs,
summaryPlacement,
renderDetailsSection,
]); ]);
const fallbackTabId = useMemo(() => { const fallbackTabId = useMemo(() => {
@@ -242,7 +318,7 @@ const DocumentInfoPanel = ({
if (!isControlled) { if (!isControlled) {
setUncontrolledTab(fallbackTabId); setUncontrolledTab(fallbackTabId);
} }
}, [fallbackTabId, resetKey, isControlled]); }, [fallbackTabId, isControlled]);
useEffect(() => { useEffect(() => {
if (isControlled && controlledActiveTab && !visibleTabs.some((tab) => tab.id === controlledActiveTab)) { if (isControlled && controlledActiveTab && !visibleTabs.some((tab) => tab.id === controlledActiveTab)) {
@@ -270,20 +346,7 @@ const DocumentInfoPanel = ({
const singleTab = visibleTabs.length === 1 ? visibleTabs[0] : null; const singleTab = visibleTabs.length === 1 ? visibleTabs[0] : null;
const shouldHideNav = hideTabNavWhenSingle && singleTab; const shouldHideNav = hideTabNavWhenSingle && singleTab;
return ( const tabNav = (
<>
<DocumentSummarySection
document={document}
{...summaryProps}
/>
{shouldHideNav ? (
<div className={`${base}__tabpanes ${base}__tabpanes--single`}>
<div className={`${base}__tabpanel`}>
{renderTabContent(singleTab, { document })}
</div>
</div>
) : (
<div className={`${base}__tabs-wrapper`}>
<div className={`${base}__tabs`} role="tablist" aria-label="Document details"> <div className={`${base}__tabs`} role="tablist" aria-label="Document details">
{visibleTabs.map((tab) => ( {visibleTabs.map((tab) => (
<button <button
@@ -298,6 +361,9 @@ const DocumentInfoPanel = ({
</button> </button>
))} ))}
</div> </div>
);
const tabPanels = (
<div className={`${base}__tabpanes`}> <div className={`${base}__tabpanes`}>
{visibleTabs.map((tab) => ( {visibleTabs.map((tab) => (
tab.id === activeTabId ? ( tab.id === activeTabId ? (
@@ -307,6 +373,25 @@ const DocumentInfoPanel = ({
) : null ) : null
))} ))}
</div> </div>
);
const tabsWrapperClass = `${base}__tabs-wrapper${tabsPlacement === 'bottom' ? ` ${base}__tabs-wrapper--bottom` : ''}`;
return (
<>
{summaryNode}
{shouldHideNav ? (
<div className={`${base}__tabpanes ${base}__tabpanes--single`}>
<div className={`${base}__tabpanel`}>
{renderTabContent(singleTab, { document })}
</div>
</div>
) : (
<div className={tabsWrapperClass}>
{tabsPlacement !== 'bottom' ? tabNav : null}
{tabsPlacement === 'bottom' ? tabPanels : null}
{tabsPlacement === 'bottom' ? tabNav : null}
{tabsPlacement !== 'bottom' ? tabPanels : null}
</div> </div>
)} )}
</> </>
+205 -81
View File
@@ -348,8 +348,11 @@ const DocumentSummarySection = ({
onCorrespondentAdd, onCorrespondentAdd,
onCorrespondentRemove, onCorrespondentRemove,
onUpdateTitle, onUpdateTitle,
onUpdateIssued onUpdateIssued,
layout = 'default',
detailItems = [],
}) => { }) => {
const isCompactLayout = layout === 'compact';
const summary = useMemo(() => { const summary = useMemo(() => {
if (!document) { if (!document) {
return { return {
@@ -507,6 +510,204 @@ const DocumentSummarySection = ({
return null; return null;
} }
const TitleSection = () => (
editableTitle && isTitleEditing ? (
<form className="doc-title-edit" onSubmit={submitTitleEdit}>
<input
value={titleDraft}
onChange={(event) => {
setTitleDraft(event.target.value);
if (titleError) {
setTitleError(null);
}
}}
onKeyDown={(event) => {
if (event.key === 'Escape') {
event.preventDefault();
cancelTitleEdit();
}
}}
aria-label="Document title"
autoFocus
disabled={titleSaving}
/>
<button type="submit" disabled={titleSaving}>
Save
</button>
<button
type="button"
className="secondary"
onClick={cancelTitleEdit}
disabled={titleSaving}
>
Cancel
</button>
</form>
) : (
<>
<h3 className="doc-title-row__title">{summary.title}</h3>
{editableTitle ? (
<button
type="button"
className="icon-button"
onClick={startTitleEdit}
aria-label="Edit title"
title="Edit title"
>
<EditIcon className="icon-inline" />
</button>
) : null}
</>
)
);
const issuedDisplay = editableIssued && isIssuedEditing ? (
<form className="doc-issued-edit" onSubmit={submitIssuedEdit}>
<input
type="date"
value={issuedDraft}
onChange={(event) => {
setIssuedDraft(event.target.value);
if (issuedError) {
setIssuedError(null);
}
}}
aria-label="Issued on"
disabled={issuedSaving}
/>
<button type="submit" disabled={issuedSaving}>
Save
</button>
<button
type="button"
className="secondary"
onClick={cancelIssuedEdit}
disabled={issuedSaving}
>
Cancel
</button>
</form>
) : (
<>
<span className="detail-meta__value">{issuedDateLabel || 'Not set'}</span>
{editableIssued ? (
<button
type="button"
className="icon-button"
onClick={startIssuedEdit}
aria-label={issuedDateLabel ? 'Edit issued date' : 'Set issued date'}
title={issuedDateLabel ? 'Edit issued date' : 'Set issued date'}
>
<EditIcon className="icon-inline" />
</button>
) : null}
</>
);
const metaItems = [
{
key: 'issued',
label: 'Issued',
valueContent: issuedDisplay,
error: issuedError,
},
...metaRows.map((row) => ({
key: row.key,
label: row.label,
fallbackValue: row.value,
})),
];
const detailRows = Array.isArray(detailItems)
? detailItems.map((item, index) => ({
key: `detail-${item?.label || index}`,
label: item?.label || '—',
fallbackValue: item?.value,
}))
: [];
const compactRows = [...metaItems, ...detailRows];
const renderTags = () => (
<section className="document-summary__section document-summary__section--tags">
<TagSection
tags={resolvedTags}
onRemove={
onTagRemove
? (tag) => onTagRemove(document.id, tag.id)
: undefined
}
onAdd={
onTagAdd
? ({ value, option }) => onTagAdd(document, value, { option })
: undefined
}
datalistOptions={tagOptions}
className="document-summary__tags"
/>
</section>
);
const renderCorrespondents = () => (
<section className="document-summary__section document-summary__section--correspondents">
<CorrespondentSection
entries={resolvedCorrespondents}
onRemove={
onCorrespondentRemove
? (entry) =>
onCorrespondentRemove({
documentId: document.id,
correspondentId: entry.id,
})
: undefined
}
onAdd={
onCorrespondentAdd
? ({ name, option }) =>
onCorrespondentAdd({
document,
name,
option,
})
: undefined
}
showCount
datalistOptions={correspondentOptions}
className="document-summary__correspondents"
/>
</section>
);
if (isCompactLayout) {
return (
<div className="document-summary document-summary--compact">
<section className="document-summary__section document-summary__title-row">
<TitleSection />
</section>
{titleError ? <div className="status-inline error">{titleError}</div> : null}
{renderTags()}
{renderCorrespondents()}
{compactRows.length ? (
<section className="document-summary__section document-summary__meta document-summary__meta--compact">
<dl className="document-summary__details-list document-summary__details-list--meta">
{compactRows.map((item) => (
<div key={item.key} className="document-summary__details-row">
<dt>{item.label}</dt>
<dd>
{item.valueContent != null && item.valueContent !== ''
? item.valueContent
: item.fallbackValue || '—'}
</dd>
{item.error ? <div className="status-inline error">{item.error}</div> : null}
</div>
))}
</dl>
</section>
) : null}
</div>
);
}
return ( return (
<div className="document-summary"> <div className="document-summary">
<div className="doc-title-row"> <div className="doc-title-row">
@@ -566,48 +767,7 @@ const DocumentSummarySection = ({
<div className="detail-meta"> <div className="detail-meta">
<div className="detail-meta__row"> <div className="detail-meta__row">
<span className="detail-meta__label">Issued:</span> <span className="detail-meta__label">Issued:</span>
{editableIssued && isIssuedEditing ? ( {issuedDisplay}
<form className="doc-issued-edit" onSubmit={submitIssuedEdit}>
<input
type="date"
value={issuedDraft}
onChange={(event) => {
setIssuedDraft(event.target.value);
if (issuedError) {
setIssuedError(null);
}
}}
aria-label="Issued on"
disabled={issuedSaving}
/>
<button type="submit" disabled={issuedSaving}>
Save
</button>
<button
type="button"
className="secondary"
onClick={cancelIssuedEdit}
disabled={issuedSaving}
>
Cancel
</button>
</form>
) : (
<>
<span className="detail-meta__value">{issuedDateLabel || 'Not set'}</span>
{editableIssued ? (
<button
type="button"
className="icon-button"
onClick={startIssuedEdit}
aria-label={issuedDateLabel ? 'Edit issued date' : 'Set issued date'}
title={issuedDateLabel ? 'Edit issued date' : 'Set issued date'}
>
<EditIcon className="icon-inline" />
</button>
) : null}
</>
)}
</div> </div>
{issuedError ? <div className="status-inline error">{issuedError}</div> : null} {issuedError ? <div className="status-inline error">{issuedError}</div> : null}
@@ -619,45 +779,9 @@ const DocumentSummarySection = ({
))} ))}
</div> </div>
<TagSection {renderTags()}
tags={resolvedTags}
onRemove={
onTagRemove
? (tag) => onTagRemove(document.id, tag.id)
: undefined
}
onAdd={
onTagAdd
? ({ value, option }) => onTagAdd(document, value, { option })
: undefined
}
datalistOptions={tagOptions}
/>
<CorrespondentSection {renderCorrespondents()}
entries={resolvedCorrespondents}
onRemove={
onCorrespondentRemove
? (entry) =>
onCorrespondentRemove({
documentId: document.id,
correspondentId: entry.id,
})
: undefined
}
onAdd={
onCorrespondentAdd
? ({ name, option }) =>
onCorrespondentAdd({
document,
name,
option,
})
: undefined
}
showCount
datalistOptions={correspondentOptions}
/>
</div> </div>
); );
}; };
+56 -18
View File
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import DocumentInfoPanel from '../documents/DocumentInfoPanel'; import DocumentInfoPanel from '../documents/DocumentInfoPanel';
import { DownloadIcon } from '../ui/icons'; import { DownloadIcon } from '../ui/icons';
@@ -13,7 +13,10 @@ const DocumentViewerLayout = ({
defaultTabId = 'details', defaultTabId = 'details',
infoPanelProps = {}, infoPanelProps = {},
previewLoadingMessage = 'Preparing preview…', previewLoadingMessage = 'Preparing preview…',
layoutMode = 'split',
}) => { }) => {
const isStacked = layoutMode === 'stacked';
const previewContent = useMemo(() => { const previewContent = useMemo(() => {
if (!document || !previewEntry?.url) { if (!document || !previewEntry?.url) {
return null; return null;
@@ -74,23 +77,7 @@ const DocumentViewerLayout = ({
); );
}, [previewEntry, document]); }, [previewEntry, document]);
return ( const renderViewportPane = useCallback(() => (
<>
<div className="document-viewer__details-pane">
<div className="document-viewer__details">
<DocumentInfoPanel
document={document}
summaryProps={summaryProps}
metadataPayload={metadataPayload}
contentConfig={contentTabConfig}
defaultTabId={defaultTabId}
classNamePrefix={classNamePrefix}
hideTabNavWhenSingle={false}
resetKey={resetKey || document?.id}
{...infoPanelProps}
/>
</div>
</div>
<div className="document-viewer__viewport"> <div className="document-viewer__viewport">
{!previewEntry?.url ? ( {!previewEntry?.url ? (
<div className="document-viewer__message">{previewLoadingMessage}</div> <div className="document-viewer__message">{previewLoadingMessage}</div>
@@ -98,6 +85,57 @@ const DocumentViewerLayout = ({
previewContent previewContent
)} )}
</div> </div>
), [previewEntry?.url, previewLoadingMessage, previewContent]);
const viewportPane = renderViewportPane();
const stackedLeadingTabs = useMemo(() => (
isStacked
? [
{
id: 'preview',
label: 'Preview',
render: () => renderViewportPane(),
},
]
: []
), [isStacked, renderViewportPane]);
const resolvedDefaultTabId = isStacked ? 'preview' : defaultTabId;
const summaryPlacement = 'tabs';
const tabsPlacement = isStacked ? 'bottom' : 'top';
const summaryLayout = 'compact';
const detailsPane = (
<div className="document-viewer__details-pane">
<div className="document-viewer__details">
<DocumentInfoPanel
document={document}
summaryProps={summaryProps}
metadataPayload={metadataPayload}
contentConfig={contentTabConfig}
defaultTabId={resolvedDefaultTabId}
classNamePrefix={classNamePrefix}
hideTabNavWhenSingle={false}
resetKey={resetKey || document?.id}
summaryPlacement={summaryPlacement}
summaryLayout={summaryLayout}
leadingTabs={stackedLeadingTabs}
tabsPlacement={tabsPlacement}
{...infoPanelProps}
/>
</div>
</div>
);
if (isStacked) {
return detailsPane;
}
return (
<>
{detailsPane}
{viewportPane}
</> </>
); );
}; };
@@ -432,6 +432,7 @@ const DocumentViewerPanel = ({
metadataPayload={metadataPayload} metadataPayload={metadataPayload}
contentTabConfig={contentTabConfig} contentTabConfig={contentTabConfig}
previewLoadingMessage="Loading preview…" previewLoadingMessage="Loading preview…"
layoutMode={isStackedLayout ? 'stacked' : 'split'}
/> />
</section> </section>
</div> </div>
+1 -1
View File
@@ -100,7 +100,7 @@
font-size: 15px; font-size: 15px;
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
--detail-panel-width: 30em; --detail-panel-width: calc(100vw / 3);
--documents-grid-title-size: 0.8rem; --documents-grid-title-size: 0.8rem;
} }
+83 -2
View File
@@ -489,6 +489,88 @@
gap: 0.25rem; gap: 0.25rem;
} }
.document-summary--compact {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.document-summary__title-row {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.document-summary__meta {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.document-summary__meta-row {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.document-summary__meta-label {
color: var(--muted);
}
.document-summary__meta-value {
color: var(--fg);
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
.document-summary__meta-value .detail-meta__value {
font-size: inherit;
color: inherit;
}
.document-summary__section {
padding: 0;
}
.tag-list.document-summary__tags,
.correspondent-list.document-summary__correspondents {
margin-top: 0.25rem;
}
.document-summary__details {
}
.document-summary__details-list {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.document-summary__details-row {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.document-summary__details-row dt {
margin: 0;
font-size: 0.8rem;
color: var(--muted);
word-break: break-word;
}
.document-summary__details-row dd {
margin: 0;
font-size: 0.95rem;
word-break: break-word;
}
.detail-panel .doc-title-edit, .detail-panel .doc-title-edit,
.document-summary .doc-title-edit { .document-summary .doc-title-edit {
display: flex; display: flex;
@@ -773,7 +855,7 @@
.detail-panel dt { .detail-panel dt {
font-weight: 600; font-weight: 600;
margin-top: 0.8rem; margin-top: 0;
} }
.detail-panel dd { .detail-panel dd {
@@ -786,7 +868,6 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5rem; gap: 0.5rem;
margin: 1rem 0;
align-items: center; align-items: center;
} }
+40 -15
View File
@@ -43,7 +43,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 1rem 1rem; padding: 0.5rem 1rem 0.5rem;
overflow: auto; overflow: auto;
} }
@@ -53,7 +53,8 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
max-height: calc(var(--document-viewer-portrait-height-ratio) * 100vh); flex: 1;
min-height: 0;
order: -1; order: -1;
} }
@@ -189,6 +190,15 @@
min-height: 0; min-height: 0;
} }
.document-viewer__tabs-wrapper--bottom .document-viewer__tabpanes {
order: 1;
}
.document-viewer__tabs-wrapper--bottom .document-viewer__tabs {
order: 2;
margin-top: 0.75rem;
}
.document-viewer__section { .document-viewer__section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -268,28 +278,37 @@
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.35; line-height: 1.35;
overflow: auto; overflow: auto;
word-break: break-word;
overflow-wrap: anywhere;
white-space: pre-wrap;
} }
.document-viewer__tabs { .document-viewer__tabs {
display: inline-flex; display: flex;
align-items: center; align-items: stretch;
gap: 0.5rem; gap: 0.25rem;
border-bottom: 1px solid var(--outline-subtle); padding: 0.25rem;
border-radius: 999px;
background: var(--surface-overlay, var(--surface));
border: 1px solid var(--outline-subtle);
box-shadow: var(--panel-shadow-soft, none);
} }
.document-viewer__tab { .document-viewer__tab {
appearance: none; appearance: none;
border: none; border: none;
background: transparent; background: transparent;
padding: 0.4rem 0.75rem; padding: 0.35rem 0.9rem;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 500; font-weight: 500;
color: var(--muted); color: var(--muted);
cursor: pointer; cursor: pointer;
border-bottom: 2px solid transparent;
transition: transition:
color 120ms ease, color 120ms ease,
border-color 120ms ease; background-color 120ms ease;
border-radius: 999px;
flex: 1;
text-align: center;
} }
.document-viewer__tab:hover, .document-viewer__tab:hover,
@@ -298,8 +317,8 @@
} }
.document-viewer__tab.is-active { .document-viewer__tab.is-active {
color: var(--accent); color: var(--accent-strong, var(--accent));
border-color: var(--accent); background: var(--accent-soft);
} }
.document-viewer__tabpanes { .document-viewer__tabpanes {
@@ -308,6 +327,10 @@
display: flex; display: flex;
} }
.document-viewer--stacked .document-viewer__tabpanes {
overflow: auto;
}
.document-viewer__tabpanes--single { .document-viewer__tabpanes--single {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -345,7 +368,7 @@
display: flex; display: flex;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
align-items: flex-start; align-items: stretch;
justify-content: flex-start; justify-content: flex-start;
max-height: 100%; max-height: 100%;
grid-area: viewport; grid-area: viewport;
@@ -369,12 +392,11 @@
} }
.document-viewer--stacked .document-viewer__object:not(.document-viewer__object--image) { .document-viewer--stacked .document-viewer__object:not(.document-viewer__object--image) {
height: calc(var(--document-viewer-portrait-height-ratio) * 100vh); height: 100%;
max-height: 100%;
} }
.document-viewer--stacked .document-viewer__object--image { .document-viewer--stacked .document-viewer__object--image {
height: auto; max-height: 100%;
} }
.document-viewer__unsupported { .document-viewer__unsupported {
@@ -408,3 +430,6 @@
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
} }
.document-viewer__summary-tab-content {
padding-top: 0.5rem;
}