From c75876ab3cb6335297cfda565c19ea768ac6d757 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Thu, 3 Sep 2026 15:09:27 +0100 Subject: [PATCH] Enhance ObjectKanban Component with Column Resizing Overlays and Improved Skeleton Structure - Introduced ObjectKanbanColumnResizeHandle and ObjectKanbanColumnResizeOverlay components for better column resizing functionality. - Updated CSS styles to support new overlays and improve visual feedback during resizing actions. - Refactored ObjectKanbanSkeletonColumns to utilize the new skeleton structure for enhanced layout and responsiveness. - Adjusted properties and styles for better content visibility and interaction during resizing. --- assets/stylesheets/App.css | 30 ++- .../Dashboard/common/ObjectKanban.jsx | 221 +++++++++++++----- .../Dashboard/common/ObjectKanbanColumn.jsx | 20 +- 3 files changed, 187 insertions(+), 84 deletions(-) diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index d7832b06..f358d943 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -1451,11 +1451,20 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { .objectKanbanContainerSkeletons { display: flex; flex-direction: column; + position: relative; flex: 1; min-width: 0; min-height: 0; } +.objectKanbanOverlayColumns { + position: absolute; + inset: 0; + z-index: 4; + overflow: hidden; + pointer-events: none; +} + .objectKanbanKanbanBody { position: relative; flex: 1; @@ -1568,14 +1577,16 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { pointer-events: none; } -.objectKanbanSkeletonTrack { +.objectKanbanSkeletonTrack, +.objectKanbanOverlayTrack { width: max-content; min-width: 100%; height: 100%; will-change: transform; } -.objectKanbanSkeletonRow { +.objectKanbanSkeletonRow, +.objectKanbanOverlayRow { width: max-content; min-width: 100%; height: 100%; @@ -1592,16 +1603,21 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { border-radius: 0 0 12px 12px; } +.objectKanbanColumnResizeOverlay { + position: relative; +} + .objectKanbanColumnResizeHandle { position: absolute; top: 0; - right: -10px; + right: -11px; bottom: 0; width: 11px; z-index: 3; cursor: col-resize; touch-action: none; user-select: none; + pointer-events: auto; } .objectKanbanColumnResizeHandle::before { @@ -1609,7 +1625,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { position: absolute; top: 11px; bottom: 11px; - right: 0; + right: 1px; width: 2px; transform: translateX(-50%); opacity: 0.6; @@ -1620,7 +1636,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { content: ''; position: absolute; top: 50%; - right: 0; + right: 1px; width: 2px; height: 20px; transform: translate(-50%, -50%); @@ -1648,7 +1664,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { opacity: 1; } -.objectKanbanColumn:hover .objectKanbanColumnResizeHandle::before { +.objectKanbanColumnResizeHandle:hover::before { background: #0e3a5b; } @@ -1660,7 +1676,7 @@ body.objectKanbanColumnResizing * { .objectKanbanColumnSkeleton { border-radius: 0 0 12px 12px; - overflow: hidden; + overflow: visible; } .objectKanbanColumnCards { diff --git a/src/components/Dashboard/common/ObjectKanban.jsx b/src/components/Dashboard/common/ObjectKanban.jsx index 7de270e8..23d28d90 100644 --- a/src/components/Dashboard/common/ObjectKanban.jsx +++ b/src/components/Dashboard/common/ObjectKanban.jsx @@ -11,6 +11,7 @@ import { } from 'react' import { Empty, Flex } from 'antd' import PropTypes from 'prop-types' +import classNames from 'classnames' import { ApiServerContext } from '../context/ApiServerContext' import ObjectCard from './ObjectCard' import ObjectKanbanColumn from './ObjectKanbanColumn' @@ -97,6 +98,99 @@ const getKanbanSkeletonLayout = (width, height) => { return { columnCount, cardCount } } +const ObjectKanbanColumnResizeHandle = ({ active = false, onMouseDown }) => ( +
+) + +ObjectKanbanColumnResizeHandle.propTypes = { + active: PropTypes.bool, + onMouseDown: PropTypes.func +} + +const ObjectKanbanColumnResizeOverlay = ({ + columnKey, + width, + columnIndex, + isResizing, + onResizeStart +}) => { + return ( +
+ {onResizeStart && columnKey && ( + onResizeStart(event, columnKey)} + /> + )} +
+ ) +} + +const ObjectKanbanColumnSkeleton = ({ + columnKey, + width, + cardCount = 0, + model, + modelProperties, + visibleColumns, + keyPrefix = 'skeleton', + columnIndex = 0 +}) => ( +
+
+ {cardCount > 0 && ( + + {Array.from({ length: cardCount }).map((_, cardIndex) => ( + + ))} + + )} +
+
+) + +ObjectKanbanColumnSkeleton.propTypes = { + columnKey: PropTypes.string, + width: PropTypes.number.isRequired, + cardCount: PropTypes.number, + model: PropTypes.object.isRequired, + modelProperties: PropTypes.array.isRequired, + visibleColumns: PropTypes.object, + keyPrefix: PropTypes.string, + columnIndex: PropTypes.number, + isResizing: PropTypes.bool, + onResizeStart: PropTypes.func +} + const ObjectKanbanSkeletonColumns = ({ columnCount, cardCount = 0, @@ -105,7 +199,9 @@ const ObjectKanbanSkeletonColumns = ({ modelProperties, visibleColumns, keyPrefix = 'skeleton', - columns = null + columns = null, + resizingKey = null, + onColumnResizeStart }) => { const skeletonColumns = columns?.length > 0 @@ -120,38 +216,19 @@ const ObjectKanbanSkeletonColumns = ({
{skeletonColumns.map((column, columnIndex) => ( -
-
- {cardCount > 0 && ( - - {Array.from({ length: cardCount }).map((_, cardIndex) => ( - - ))} - - )} -
-
+ columnKey={column.key} + width={column.width} + cardCount={cardCount} + model={model} + modelProperties={modelProperties} + visibleColumns={visibleColumns} + keyPrefix={keyPrefix} + columnIndex={columnIndex} + isResizing={resizingKey === column.key} + onResizeStart={onColumnResizeStart} + /> ))}
@@ -159,6 +236,14 @@ const ObjectKanbanSkeletonColumns = ({ ) } +ObjectKanbanColumnResizeOverlay.propTypes = { + columnKey: PropTypes.string, + width: PropTypes.number.isRequired, + columnIndex: PropTypes.number, + isResizing: PropTypes.bool, + onResizeStart: PropTypes.func +} + ObjectKanbanSkeletonColumns.propTypes = { columnCount: PropTypes.number.isRequired, cardCount: PropTypes.number, @@ -167,7 +252,9 @@ ObjectKanbanSkeletonColumns.propTypes = { modelProperties: PropTypes.array.isRequired, visibleColumns: PropTypes.object, keyPrefix: PropTypes.string, - columns: PropTypes.array + columns: PropTypes.array, + resizingKey: PropTypes.string, + onColumnResizeStart: PropTypes.func } const ObjectKanban = forwardRef( @@ -200,6 +287,7 @@ const ObjectKanban = forwardRef( const columnRefs = useRef({}) const headerTrackRef = useRef(null) const skeletonTrackRef = useRef(null) + const overlayTrackRef = useRef(null) const kanbanBodyRef = useRef(null) const [scrollElement, setScrollElement] = useState(null) const [categoryValues, setCategoryValues] = useState([]) @@ -272,6 +360,7 @@ const ObjectKanban = forwardRef( const mainScroll = scrollElement const headerTrack = headerTrackRef.current const skeletonTrack = skeletonTrackRef.current + const overlayTrack = overlayTrackRef.current if (!mainScroll || !headerTrack) return undefined const handleMainScroll = () => { @@ -280,6 +369,9 @@ const ObjectKanban = forwardRef( if (skeletonTrack) { skeletonTrack.style.transform = offset } + if (overlayTrack) { + overlayTrack.style.transform = offset + } } handleMainScroll() @@ -429,12 +521,14 @@ const ObjectKanban = forwardRef( setReloadingColumnKeys(previousKeys) try { lastCategoryQueryKeyRef.current = null - const nextValues = await load(baseFilterRef.current, sorterRef.current, { - silent: true - }) - const nextKeySet = new Set( - (nextValues || []).map(getCategoryValueKey) + const nextValues = await load( + baseFilterRef.current, + sorterRef.current, + { + silent: true + } ) + const nextKeySet = new Set((nextValues || []).map(getCategoryValueKey)) // Only reload columns that existed before; newly created columns // mount and load themselves. const keysToReload = previousKeys.filter((key) => nextKeySet.has(key)) @@ -480,9 +574,9 @@ const ObjectKanban = forwardRef( updatedData, categoryPropertyName ) - ? getCategoryColumnKeys( - updatedData[categoryPropertyName] - ).filter((key) => previousKeys.has(key)) + ? getCategoryColumnKeys(updatedData[categoryPropertyName]).filter( + (key) => previousKeys.has(key) + ) : [] const tentativeKeys = [...new Set([...oldKeys, ...updatedKeys])] @@ -755,11 +849,7 @@ const ObjectKanban = forwardRef( setDropTargetKey(null) return } - const nextColumns = reorderColumnsByKey( - resolvedColumns, - fromKey, - toKey - ) + const nextColumns = reorderColumnsByKey(resolvedColumns, fromKey, toKey) persistColumns(nextColumns) setDraggedKey(null) setDropTargetKey(null) @@ -845,6 +935,23 @@ const ObjectKanban = forwardRef( return (
+
+
+ + {displayColumns.map(({ key: columnKey, width }, columnIndex) => ( + + ))} + +
+
+
- handleColumnResizeStart(event, columnKey) - } /> ) )} @@ -926,16 +1029,16 @@ const ObjectKanban = forwardRef( > {displayColumns.map(({ key: columnKey, width }) => ( -
-
-
+ columnKey={columnKey} + width={width} + model={model} + modelProperties={modelProperties} + visibleColumns={visibleColumns} + isResizing={resizingKey === columnKey} + onResizeStart={handleColumnResizeStart} + /> ))}
diff --git a/src/components/Dashboard/common/ObjectKanbanColumn.jsx b/src/components/Dashboard/common/ObjectKanbanColumn.jsx index d5d845c1..494a53c5 100644 --- a/src/components/Dashboard/common/ObjectKanbanColumn.jsx +++ b/src/components/Dashboard/common/ObjectKanbanColumn.jsx @@ -11,7 +11,6 @@ import { } from 'react' import { Flex } from 'antd' import PropTypes from 'prop-types' -import classNames from 'classnames' import { ApiServerContext } from '../context/ApiServerContext' import ObjectCard from './ObjectCard' import Spin from './Spin' @@ -42,9 +41,7 @@ const ObjectKanbanColumn = forwardRef( lazyLoading = false, renderActions, scrollElement = null, - width, - isResizing = false, - onResizeStart + width }, ref ) => { @@ -597,17 +594,6 @@ const ObjectKanbanColumn = forwardRef(
- {onResizeStart && ( -
onResizeStart(event)} - /> - )} ) } @@ -631,9 +617,7 @@ ObjectKanbanColumn.propTypes = { lazyLoading: PropTypes.bool, renderActions: PropTypes.func, scrollElement: PropTypes.object, - width: PropTypes.number, - isResizing: PropTypes.bool, - onResizeStart: PropTypes.func + width: PropTypes.number } export default ObjectKanbanColumn