diff --git a/src/components/datatable/BottomPanel.jsx b/src/components/datatable/BottomPanel.jsx index d28ed5769..bbdabed72 100644 --- a/src/components/datatable/BottomPanel.jsx +++ b/src/components/datatable/BottomPanel.jsx @@ -1,11 +1,13 @@ import { IconCross16 } from '@dhis2/ui' -import React, { useRef, useCallback } from 'react' +import React, { + useRef, + useCallback, + useState, + useEffect, + useLayoutEffect, +} from 'react' import { useSelector, useDispatch } from 'react-redux' import { closeDataTable, resizeDataTable } from '../../actions/dataTable.js' -import { - LAYERS_PANEL_WIDTH, - RIGHT_PANEL_WIDTH, -} from '../../constants/layout.js' import useKeyDown from '../../hooks/useKeyDown.js' import { getCssVar } from '../../util/helpers.js' import { useWindowDimensions } from '../WindowDimensionsProvider.jsx' @@ -17,26 +19,49 @@ import styles from './styles/BottomPanel.module.css' // Container for DataTable const BottomPanel = () => { const dataTableHeight = useSelector((state) => state.ui.dataTableHeight) - const layersPanelOpen = useSelector((state) => state.ui.layersPanelOpen) - const rightPanelOpen = useSelector((state) => state.ui.rightPanelOpen) const dispatch = useDispatch() - const { width, height } = useWindowDimensions() + const { height } = useWindowDimensions() const panelRef = useRef(null) - - const onResize = useCallback( - (h) => (panelRef.current.style.height = `${h}px`), - [panelRef] - ) + const [panelWidth, setPanelWidth] = useState(0) const maxHeight = height - getCssVar('--header-height') - getCssVar('--toolbar-height') const tableHeight = dataTableHeight < maxHeight ? dataTableHeight : maxHeight - const layersWidth = layersPanelOpen ? LAYERS_PANEL_WIDTH : 0 - const rightPanelWidth = rightPanelOpen ? RIGHT_PANEL_WIDTH : 0 - const tableWidth = width - layersWidth - rightPanelWidth - const dataTableControlsHeight = 20 + const onResize = useCallback((h) => { + document.documentElement.style.setProperty( + '--data-table-height', + `${h}px` + ) + }, []) + + useLayoutEffect(() => { + document.documentElement.style.setProperty( + '--data-table-height', + `${tableHeight}px` + ) + }, [tableHeight]) + + useLayoutEffect( + () => () => + document.documentElement.style.removeProperty( + '--data-table-height' + ), + [] + ) + + useEffect(() => { + const observer = new ResizeObserver(() => { + if (panelRef.current) { + setPanelWidth(panelRef.current.getBoundingClientRect().width) + } + }) + if (panelRef.current) { + observer.observe(panelRef.current) + } + return () => observer.disconnect() + }, []) useKeyDown('Escape', () => dispatch(closeDataTable()), true) @@ -44,7 +69,6 @@ const BottomPanel = () => {
@@ -60,12 +84,11 @@ const BottomPanel = () => {
- - - +
+ + + +
) } diff --git a/src/components/datatable/DataTable.jsx b/src/components/datatable/DataTable.jsx index d0090238a..26598f799 100644 --- a/src/components/datatable/DataTable.jsx +++ b/src/components/datatable/DataTable.jsx @@ -89,13 +89,14 @@ const TableComponents = { ), } -const Table = ({ availableHeight, availableWidth }) => { +const Table = ({ availableWidth }) => { const { systemSettings: { keyAnalysisDigitGroupSeparator }, } = useCachedData() const headerRowRef = useRef(null) const [columnWidths, setColumnWidths] = useState([]) + const minColumnWidthsRef = useRef([]) const { mapViews } = useSelector((state) => state.map) const activeLayerId = useSelector((state) => state.dataTable) @@ -206,12 +207,7 @@ const Table = ({ availableHeight, availableWidth }) => { }) useEffect(() => { - /* The combination of automatic table layout and virtual scrolling - * causes a content shift when scrolling and filtering because the - * cells in the DOM have a different content length which causes the - * columns to have a different width. To avoid that we measure the - * initial column widths and switch to a fixed layout based on these - * measured widths */ + // Measure column widths in auto layout, then switch to fixed to prevent content shift during virtual scrolling if (columnWidths.length === 0 && headerRowRef.current) { requestAnimationFrame(() => { const measuredColumnWidths = [] @@ -221,20 +217,41 @@ const Table = ({ availableHeight, availableWidth }) => { measuredColumnWidths.push(Math.floor(rect.width)) } + minColumnWidthsRef.current = measuredColumnWidths setColumnWidths(measuredColumnWidths) }) } }, [columnWidths]) useEffect(() => { - /* When the window is resized, the sidebar opens, or the table - * headers change, the table needs to switch back to its - * automatic layout so that the cells can subsequently can be - * measured again in the useEffect hook above */ + // Reset to auto layout for re-measurement when headers change if (!error) { + minColumnWidthsRef.current = [] setColumnWidths([]) } - }, [availableWidth, headers, error]) + }, [headers, error]) + + useEffect(() => { + // Scale column widths proportionally on resize, clamped to initial measured widths + if (!error) { + setColumnWidths((prev) => { + if (prev.length === 0) { + return prev + } + const prevTotal = prev.reduce((sum, w) => sum + w, 0) + if (prevTotal === 0 || availableWidth === 0) { + return [] + } + const minWidths = minColumnWidthsRef.current + return prev.map((w, i) => + Math.max( + minWidths[i] ?? 0, + Math.round((w / prevTotal) * availableWidth) + ) + ) + }) + } + }, [availableWidth, error]) if (error) { return

{error}

@@ -246,7 +263,7 @@ const Table = ({ availableHeight, availableWidth }) => { context={tableContext} components={TableComponents} style={{ - height: availableHeight, + height: '100%', width: '100%', }} data={rows} @@ -321,7 +338,6 @@ const Table = ({ availableHeight, availableWidth }) => { } Table.propTypes = { - availableHeight: PropTypes.number, availableWidth: PropTypes.number, } diff --git a/src/components/datatable/styles/BottomPanel.module.css b/src/components/datatable/styles/BottomPanel.module.css index 19e3c0f83..68ce43eab 100644 --- a/src/components/datatable/styles/BottomPanel.module.css +++ b/src/components/datatable/styles/BottomPanel.module.css @@ -2,8 +2,18 @@ position: absolute; left: 0; bottom: 0; + width: 100%; + height: var(--data-table-height); z-index: 1040; background: #fff; + display: flex; + flex-direction: column; +} + +.tableContainer { + flex: 1; + min-height: 0; + position: relative; } .dataTableControls { diff --git a/src/components/map/MapPosition.jsx b/src/components/map/MapPosition.jsx index cbc420aa3..74910d904 100644 --- a/src/components/map/MapPosition.jsx +++ b/src/components/map/MapPosition.jsx @@ -1,5 +1,5 @@ import cx from 'classnames' -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { useSelector } from 'react-redux' import { getSplitViewLayer } from '../../util/helpers.js' import DownloadMapInfo from '../download/DownloadMapInfo.jsx' @@ -7,9 +7,12 @@ import NorthArrow from '../download/NorthArrow.jsx' import MapContainer from '../map/MapContainer.jsx' import styles from './styles/MapPosition.module.css' +const incrementCount = (c) => c + 1 + const MapPosition = () => { const [map, setMap] = useState() const [resizeCount, setResizeCount] = useState(0) + const outerRef = useRef(null) const { showName, showDescription, @@ -34,7 +37,7 @@ const MapPosition = () => { // Trigger map resize when panels are expanded, collapsed or dragged useEffect(() => { - setResizeCount((count) => count + 1) + setResizeCount(incrementCount) }, [ dataTableOpen, dataTableHeight, @@ -43,6 +46,30 @@ const MapPosition = () => { rightPanelOpen, ]) + // Trigger map resize continuously during ResizeHandle drag (CSS variable drives height, not Redux) + useEffect(() => { + if (!outerRef.current) { + return + } + let frameId = null + const observer = new ResizeObserver(() => { + if (frameId !== null) { + return + } + frameId = requestAnimationFrame(() => { + setResizeCount(incrementCount) + frameId = null + }) + }) + observer.observe(outerRef.current) + return () => { + observer.disconnect() + if (frameId !== null) { + cancelAnimationFrame(frameId) + } + } + }, []) + // Reset bearing and pitch when new map (mapId changed) useEffect(() => { if (map) { @@ -77,17 +104,12 @@ const MapPosition = () => { return (