panel
This commit is contained in:
@@ -26,88 +26,8 @@ import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||
import { useAssetNavigator } from '../hooks/useAssetNavigator';
|
||||
import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
import useViewerLayoutMode from './useViewerLayoutMode';
|
||||
import { useSidebarContext } from '../sidebar/SidebarContext';
|
||||
import { usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
|
||||
const DETAIL_PANEL_WIDTH_STORAGE_KEY = 'detailPanelWidth';
|
||||
const MIN_DETAIL_PANEL_WIDTH = 320;
|
||||
const MAX_DETAIL_PANEL_WIDTH = 960;
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
const isDocumentAvailable = typeof document !== 'undefined';
|
||||
|
||||
const getDetailPanelBounds = () => {
|
||||
if (!isBrowser) {
|
||||
return {
|
||||
min: MIN_DETAIL_PANEL_WIDTH,
|
||||
max: MAX_DETAIL_PANEL_WIDTH,
|
||||
};
|
||||
}
|
||||
const viewportWidth = Math.max(window.innerWidth, 1);
|
||||
const minFractionWidth = viewportWidth / 5;
|
||||
const maxFractionWidth = viewportWidth * 0.75;
|
||||
const rawMin = Math.max(MIN_DETAIL_PANEL_WIDTH, minFractionWidth);
|
||||
const rawMax = Math.min(MAX_DETAIL_PANEL_WIDTH, maxFractionWidth);
|
||||
if (rawMin >= rawMax) {
|
||||
const fallback = Math.min(Math.max(rawMin, viewportWidth * 0.5), MAX_DETAIL_PANEL_WIDTH);
|
||||
return { min: fallback, max: fallback };
|
||||
}
|
||||
return {
|
||||
min: rawMin,
|
||||
max: rawMax,
|
||||
};
|
||||
};
|
||||
|
||||
const clampDetailPanelWidth = (value) => {
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
return null;
|
||||
}
|
||||
const { min, max } = getDetailPanelBounds();
|
||||
return Math.min(Math.max(value, min), max);
|
||||
};
|
||||
|
||||
const loadStoredDetailPanelWidth = () => {
|
||||
if (!isBrowser) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const raw = window.localStorage?.getItem(DETAIL_PANEL_WIDTH_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = parseInt(raw, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
} catch (error) {
|
||||
console.warn('Failed to read detail panel width', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const applyDetailPanelWidth = (width) => {
|
||||
if (!isDocumentAvailable || width == null) {
|
||||
return;
|
||||
}
|
||||
document.documentElement.style.setProperty('--detail-panel-width', `${width}px`);
|
||||
};
|
||||
|
||||
const getSidebarWidthFromRoot = () => {
|
||||
if (!isBrowser || !isDocumentAvailable) {
|
||||
return null;
|
||||
}
|
||||
const computed = window.getComputedStyle(document.documentElement);
|
||||
const parsed = parseFloat(computed.getPropertyValue('--sidebar-width'));
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const shouldSuppressSidebarForDetailWidth = (detailWidth) => {
|
||||
if (!isBrowser || !Number.isFinite(detailWidth)) {
|
||||
return false;
|
||||
}
|
||||
const sidebarWidth = getSidebarWidthFromRoot() || 0;
|
||||
const minMainContentWidth = window.innerWidth / 3;
|
||||
const occupiedWidth = detailWidth + sidebarWidth;
|
||||
const availableWidth = window.innerWidth - occupiedWidth;
|
||||
return availableWidth < minMainContentWidth;
|
||||
};
|
||||
|
||||
export const createDocumentViewerHeaderActions = ({
|
||||
document,
|
||||
@@ -181,7 +101,6 @@ const DocumentViewerPanel = ({
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const isSidebarVariant = variant === 'sidebar';
|
||||
const { setSidebarSuppressed } = useSidebarContext();
|
||||
const sortedCorrespondents = useMemo(
|
||||
() => sortCorrespondents(document?.correspondents || []),
|
||||
[document],
|
||||
@@ -322,166 +241,14 @@ const DocumentViewerPanel = ({
|
||||
|
||||
const panelRef = useRef(null);
|
||||
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
|
||||
const pendingWidthRef = useRef(null);
|
||||
const [isResizingPanel, setIsResizingPanel] = useState(false);
|
||||
const [detailPanelWidth, setDetailPanelWidth] = useState(() => {
|
||||
if (!isBrowser) {
|
||||
return null;
|
||||
}
|
||||
const stored = loadStoredDetailPanelWidth();
|
||||
return stored != null ? clampDetailPanelWidth(stored) : null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (detailPanelWidth != null) {
|
||||
const clamped = clampDetailPanelWidth(detailPanelWidth);
|
||||
if (clamped != null) {
|
||||
applyDetailPanelWidth(clamped);
|
||||
}
|
||||
}
|
||||
}, [detailPanelWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSidebarVariant) {
|
||||
setSidebarSuppressed(false);
|
||||
return undefined;
|
||||
}
|
||||
const updateSuppression = () => {
|
||||
if (!isBrowser) {
|
||||
setSidebarSuppressed(false);
|
||||
return;
|
||||
}
|
||||
const panelWidth = pendingWidthRef.current
|
||||
?? detailPanelWidth
|
||||
?? panelRef.current?.getBoundingClientRect().width;
|
||||
setSidebarSuppressed(shouldSuppressSidebarForDetailWidth(panelWidth));
|
||||
};
|
||||
|
||||
updateSuppression();
|
||||
const handleWindowResize = () => updateSuppression();
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
};
|
||||
}, [isSidebarVariant, detailPanelWidth, setSidebarSuppressed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSidebarVariant) {
|
||||
setSidebarSuppressed(false);
|
||||
}
|
||||
}, [isSidebarVariant, setSidebarSuppressed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
return undefined;
|
||||
}
|
||||
const handleResize = () => {
|
||||
setDetailPanelWidth((prev) => {
|
||||
if (prev == null) {
|
||||
return prev;
|
||||
}
|
||||
const clamped = clampDetailPanelWidth(prev);
|
||||
if (clamped != null && clamped !== prev) {
|
||||
applyDetailPanelWidth(clamped);
|
||||
try {
|
||||
window.localStorage?.setItem(DETAIL_PANEL_WIDTH_STORAGE_KEY, String(Math.round(clamped)));
|
||||
} catch (error) {
|
||||
console.warn('Failed to persist detail panel width', error);
|
||||
}
|
||||
return clamped;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const handleResizePointerDown = useCallback((event) => {
|
||||
if (!isSidebarVariant || !panelRef.current || !isBrowser) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const pointerId = event.pointerId;
|
||||
const target = event.currentTarget;
|
||||
target.setPointerCapture?.(pointerId);
|
||||
setIsResizingPanel(true);
|
||||
|
||||
const rect = panelRef.current.getBoundingClientRect();
|
||||
const startWidth = rect.width;
|
||||
const startX = event.clientX;
|
||||
|
||||
const updateWidth = (nextWidth) => {
|
||||
const clamped = clampDetailPanelWidth(nextWidth);
|
||||
if (clamped != null) {
|
||||
pendingWidthRef.current = clamped;
|
||||
applyDetailPanelWidth(clamped);
|
||||
if (isSidebarVariant) {
|
||||
setSidebarSuppressed(shouldSuppressSidebarForDetailWidth(clamped));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointerMove = (moveEvent) => {
|
||||
if (moveEvent.pointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
const delta = startX - moveEvent.clientX;
|
||||
updateWidth(startWidth + delta);
|
||||
};
|
||||
|
||||
const handlePointerUp = (upEvent) => {
|
||||
if (upEvent.pointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
target.releasePointerCapture?.(pointerId);
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
setIsResizingPanel(false);
|
||||
if (pendingWidthRef.current != null) {
|
||||
const finalizedWidth = pendingWidthRef.current;
|
||||
pendingWidthRef.current = null;
|
||||
setDetailPanelWidth(finalizedWidth);
|
||||
try {
|
||||
window.localStorage?.setItem(DETAIL_PANEL_WIDTH_STORAGE_KEY, String(Math.round(finalizedWidth)));
|
||||
} catch (error) {
|
||||
console.warn('Failed to persist detail panel width', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}, [isSidebarVariant, setSidebarSuppressed]);
|
||||
|
||||
const handleResizeKeyDown = useCallback((event) => {
|
||||
if (!isSidebarVariant || !isBrowser) {
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
|
||||
return;
|
||||
}
|
||||
const baseWidth = pendingWidthRef.current
|
||||
?? detailPanelWidth
|
||||
?? panelRef.current?.getBoundingClientRect().width;
|
||||
if (!Number.isFinite(baseWidth)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const step = event.shiftKey ? 40 : 20;
|
||||
const delta = event.key === 'ArrowLeft' ? step : -step;
|
||||
const nextWidth = clampDetailPanelWidth(baseWidth + delta);
|
||||
if (nextWidth == null) {
|
||||
return;
|
||||
}
|
||||
setDetailPanelWidth(nextWidth);
|
||||
try {
|
||||
window.localStorage?.setItem(DETAIL_PANEL_WIDTH_STORAGE_KEY, String(Math.round(nextWidth)));
|
||||
} catch (error) {
|
||||
console.warn('Failed to persist detail panel width', error);
|
||||
}
|
||||
}, [detailPanelWidth, isSidebarVariant]);
|
||||
const {
|
||||
panelStyle: managedDetailPanelStyle,
|
||||
handleProps: managedResizeHandleProps,
|
||||
isPanelResizing,
|
||||
} = usePanelResizeBindings('detail', { enabled: isSidebarVariant, panelRef });
|
||||
const detailPanelStyle = isSidebarVariant ? managedDetailPanelStyle : undefined;
|
||||
const resizeHandleProps = isSidebarVariant ? managedResizeHandleProps : {};
|
||||
|
||||
const viewerClassName = isStackedLayout
|
||||
? 'document-viewer document-viewer--stacked'
|
||||
@@ -649,10 +416,9 @@ const DocumentViewerPanel = ({
|
||||
const resizeHandle = isSidebarVariant ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`detail-panel__resize-handle${isResizingPanel ? ' is-active' : ''}`}
|
||||
className={`detail-panel__resize-handle${isPanelResizing ? ' is-active' : ''}`}
|
||||
aria-label="Resize detail panel"
|
||||
onPointerDown={handleResizePointerDown}
|
||||
onKeyDown={handleResizeKeyDown}
|
||||
{...resizeHandleProps}
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
</button>
|
||||
@@ -715,7 +481,11 @@ const DocumentViewerPanel = ({
|
||||
if (isSidebarVariant) {
|
||||
return (
|
||||
<>
|
||||
<aside className={`detail-panel panel${isResizingPanel ? ' detail-panel--resizing' : ''}`} ref={panelRef}>
|
||||
<aside
|
||||
className={`detail-panel panel${isPanelResizing ? ' detail-panel--resizing' : ''}`}
|
||||
ref={panelRef}
|
||||
style={detailPanelStyle}
|
||||
>
|
||||
{resizeHandle}
|
||||
<PanelHeader
|
||||
leading={headerLeadingContent}
|
||||
|
||||
Reference in New Issue
Block a user