Enhance ObjectTable and Sidebar Functionality with Dynamic Width Management
- Introduced a new custom hook, useSidebarWidth, to manage and persist sidebar widths based on user settings, improving user experience and customization. - Updated ObjectTable to utilize the sidebar width dynamically, enhancing layout responsiveness. - Implemented visibility logic for vertical scrollbars in the ObjectTable, ensuring better usability during data interaction. - Enhanced CSS styles for scrollbar transitions and sidebar components, improving visual feedback and interaction.
This commit is contained in:
parent
becd5d5e82
commit
fe2d257653
@ -739,6 +739,7 @@ body {
|
|||||||
|
|
||||||
.simplebar-scrollbar:before {
|
.simplebar-scrollbar:before {
|
||||||
background: #78787854 !important;
|
background: #78787854 !important;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.printer-alerts-display-popover .ant-popover-inner {
|
.printer-alerts-display-popover .ant-popover-inner {
|
||||||
@ -1612,23 +1613,23 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: -11px;
|
right: -11px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 11px;
|
width: 6px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectKanbanColumnResizeHandle::before {
|
.objectKanbanColumnResizeHandle::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 11px;
|
top: 11.15px;
|
||||||
bottom: 11px;
|
bottom: 10.85px;
|
||||||
right: 1px;
|
right: 1px;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1640,16 +1641,9 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
width: 2px;
|
width: 2px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectKanbanColumnResizeHandle.active::before,
|
|
||||||
.objectKanbanColumnResizeHandle.active::after {
|
|
||||||
background: #0e3a5b;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark-mode .objectKanbanColumnResizeHandle::after {
|
.dark-mode .objectKanbanColumnResizeHandle::after {
|
||||||
background: rgba(255, 255, 255, 0.18);
|
background: rgba(255, 255, 255, 0.18);
|
||||||
}
|
}
|
||||||
@ -1658,16 +1652,30 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .objectKanbanColumnResizeHandle.active::before,
|
.light-mode .objectKanbanColumnResizeHandle::after {
|
||||||
.dark-mode .objectKanbanColumnResizeHandle.active::after {
|
background: rgba(0, 0, 0, 0.15);
|
||||||
background: #0b4c7e;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectKanbanColumnResizeHandle:hover::before {
|
.light-mode .objectKanbanColumnResizeHandle::before {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .objectKanbanColumnResizeHandle:hover::before {
|
||||||
background: #0e3a5b;
|
background: #0e3a5b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light-mode .objectKanbanColumnResizeHandle:hover::before {
|
||||||
|
background: #e6f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .objectKanbanColumnResizeHandle.active::before {
|
||||||
|
background: #0b4c7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-mode .objectKanbanColumnResizeHandle.active::before {
|
||||||
|
background: #a3e3ff;
|
||||||
|
}
|
||||||
|
|
||||||
body.objectKanbanColumnResizing,
|
body.objectKanbanColumnResizing,
|
||||||
body.objectKanbanColumnResizing * {
|
body.objectKanbanColumnResizing * {
|
||||||
cursor: col-resize !important;
|
cursor: col-resize !important;
|
||||||
@ -2313,6 +2321,15 @@ body.objectKanbanColumnResizing * {
|
|||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.farmcontrol-splitter .ant-splitter-bar-dragger {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farmcontrol-splitter.vertical-scroll-visible .ant-splitter-bar-dragger {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.object-display-tag
|
.object-display-tag
|
||||||
.elipsis-text-wrapper.ant-typography
|
.elipsis-text-wrapper.ant-typography
|
||||||
code.elipsis-text-measure,
|
code.elipsis-text-measure,
|
||||||
|
|||||||
@ -69,6 +69,9 @@ import {
|
|||||||
import { areValuesEqual } from '../utils/Utils'
|
import { areValuesEqual } from '../utils/Utils'
|
||||||
import MissingPlaceholder from './MissingPlaceholder'
|
import MissingPlaceholder from './MissingPlaceholder'
|
||||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||||
|
import useSidebarWidth from '../hooks/useSidebarWidth'
|
||||||
|
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
const logger = loglevel.getLogger('DasboardTable')
|
const logger = loglevel.getLogger('DasboardTable')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
@ -412,6 +415,10 @@ const ObjectTable = forwardRef(
|
|||||||
const [sidebarFilter, setSidebarFilter] = useState({})
|
const [sidebarFilter, setSidebarFilter] = useState({})
|
||||||
const [tableSorter, setTableSorter] = useState({})
|
const [tableSorter, setTableSorter] = useState({})
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
const [sidebarWidth, setSidebarWidth] = useSidebarWidth(type)
|
||||||
|
|
||||||
|
const [verticalScrollVisible, setVerticalScrollVisible] = useState(false)
|
||||||
|
const tableContentRef = useRef(null)
|
||||||
|
|
||||||
// Keep refs in sync with props. applyViewState may update them earlier in
|
// Keep refs in sync with props. applyViewState may update them earlier in
|
||||||
// the same tick; props remain the source of truth after commit.
|
// the same tick; props remain the source of truth after commit.
|
||||||
@ -1929,7 +1936,11 @@ const ObjectTable = forwardRef(
|
|||||||
const cardsContainerNodeRef = useRef(null)
|
const cardsContainerNodeRef = useRef(null)
|
||||||
const cardsResizeObserverRef = useRef(null)
|
const cardsResizeObserverRef = useRef(null)
|
||||||
|
|
||||||
const measureCardColSpan = useCallback(() => {
|
const measureCardColSpan = useCallback((sizes) => {
|
||||||
|
if (sizes?.[1]) {
|
||||||
|
setSidebarWidth(sizes[1])
|
||||||
|
}
|
||||||
|
|
||||||
const node = cardsContainerNodeRef.current
|
const node = cardsContainerNodeRef.current
|
||||||
if (!node) return
|
if (!node) return
|
||||||
const panel = node.closest('.ant-splitter-panel')
|
const panel = node.closest('.ant-splitter-panel')
|
||||||
@ -2107,17 +2118,53 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('loading', loading)
|
const container = tableContentRef.current
|
||||||
}, [loading])
|
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const updateVisibility = () => {
|
||||||
|
const scrollableY = container.querySelector('.simplebar-scrollable-y')
|
||||||
|
|
||||||
|
const scrollableX = container.querySelector('.simplebar-scrollable-x')
|
||||||
|
|
||||||
|
const scrollbar = container.querySelector(
|
||||||
|
'.simplebar-vertical .simplebar-scrollbar'
|
||||||
|
)
|
||||||
|
|
||||||
|
setVerticalScrollVisible(
|
||||||
|
scrollbar?.classList.contains('simplebar-visible') ||
|
||||||
|
scrollableY?.classList.contains('simplebar-mouse-entered') ||
|
||||||
|
scrollableX?.classList.contains('simplebar-mouse-entered')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
updateVisibility()
|
||||||
|
|
||||||
|
const observer = new MutationObserver(updateVisibility)
|
||||||
|
|
||||||
|
observer.observe(container, {
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const tableContent = (
|
const tableContent = (
|
||||||
<Flex
|
<Flex
|
||||||
gap={'middle'}
|
gap={'middle'}
|
||||||
vertical
|
vertical
|
||||||
style={{ flex: 1, minWidth: 0, minHeight: 0 }}
|
style={{ flex: 1, minWidth: 0, minHeight: 0 }}
|
||||||
|
ref={tableContentRef}
|
||||||
>
|
>
|
||||||
<Splitter
|
<Splitter
|
||||||
className={'farmcontrol-splitter large'}
|
className={classNames('farmcontrol-splitter large', {
|
||||||
|
'vertical-scroll-visible': verticalScrollVisible
|
||||||
|
})}
|
||||||
onResize={measureCardColSpan}
|
onResize={measureCardColSpan}
|
||||||
onResizeEnd={measureCardColSpan}
|
onResizeEnd={measureCardColSpan}
|
||||||
>
|
>
|
||||||
@ -2214,8 +2261,8 @@ const ObjectTable = forwardRef(
|
|||||||
</Spin>
|
</Spin>
|
||||||
)}
|
)}
|
||||||
</Splitter.Panel>
|
</Splitter.Panel>
|
||||||
{(showSortSidebar || showFilterSidebar) && (
|
{(showSortSidebar || showFilterSidebar) && initialized && (
|
||||||
<Splitter.Panel defaultSize={'25%'}>
|
<Splitter.Panel size={sidebarWidth}>
|
||||||
<Flex
|
<Flex
|
||||||
vertical
|
vertical
|
||||||
gap='large'
|
gap='large'
|
||||||
|
|||||||
128
src/components/Dashboard/hooks/useSidebarWidth.jsx
Normal file
128
src/components/Dashboard/hooks/useSidebarWidth.jsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
|
||||||
|
const useSidebarWidth = (componentName, defaultValue = 300) => {
|
||||||
|
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
|
||||||
|
|
||||||
|
const savedSidebarWidth =
|
||||||
|
userSettings?.sidebarWidths?.[componentName] ?? defaultValue
|
||||||
|
|
||||||
|
const [localSidebarWidth, setLocalSidebarWidth] = useState(savedSidebarWidth)
|
||||||
|
|
||||||
|
const pendingValueRef = useRef(null)
|
||||||
|
const lastUpdateRef = useRef(0)
|
||||||
|
const timeoutRef = useRef(null)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep the latest updateUserSettings arguments in refs so the
|
||||||
|
* cleanup function always has the correct component name/value.
|
||||||
|
*/
|
||||||
|
const componentNameRef = useRef(componentName)
|
||||||
|
const updateUserSettingsRef = useRef(updateUserSettings)
|
||||||
|
|
||||||
|
componentNameRef.current = componentName
|
||||||
|
updateUserSettingsRef.current = updateUserSettings
|
||||||
|
|
||||||
|
const callUpdateUserSettings = useCallback((name, value) => {
|
||||||
|
lastUpdateRef.current = Date.now()
|
||||||
|
|
||||||
|
updateUserSettingsRef.current('sidebarWidths', name, value)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const flush = useCallback(() => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = pendingValueRef.current
|
||||||
|
|
||||||
|
if (!pending) return
|
||||||
|
|
||||||
|
pendingValueRef.current = null
|
||||||
|
|
||||||
|
callUpdateUserSettings(pending.componentName, pending.value)
|
||||||
|
}, [callUpdateUserSettings])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush the previous component's value when the component is
|
||||||
|
* unmounted OR componentName changes.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const name = componentName
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const pending = pendingValueRef.current
|
||||||
|
|
||||||
|
if (pending && pending.componentName === name) {
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [componentName, flush])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load the saved width when the component name changes.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalSidebarWidth(savedSidebarWidth)
|
||||||
|
}, [componentName, savedSidebarWidth])
|
||||||
|
|
||||||
|
const setSidebarWidth = useCallback(
|
||||||
|
(nextValue) => {
|
||||||
|
const value =
|
||||||
|
typeof nextValue === 'function'
|
||||||
|
? nextValue(localSidebarWidth)
|
||||||
|
: nextValue
|
||||||
|
|
||||||
|
if (value === localSidebarWidth) return
|
||||||
|
|
||||||
|
// Always update the UI immediately.
|
||||||
|
setLocalSidebarWidth(value)
|
||||||
|
|
||||||
|
// Keep only the latest value waiting to be persisted.
|
||||||
|
pendingValueRef.current = {
|
||||||
|
componentName,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsed = Date.now() - lastUpdateRef.current
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We haven't called updateUserSettings recently, so call it
|
||||||
|
* immediately.
|
||||||
|
*/
|
||||||
|
if (elapsed >= 1000) {
|
||||||
|
const pending = pendingValueRef.current
|
||||||
|
pendingValueRef.current = null
|
||||||
|
|
||||||
|
callUpdateUserSettings(pending.componentName, pending.value)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Already rate limited. Schedule the next call for exactly
|
||||||
|
* when the 1 second window expires.
|
||||||
|
*/
|
||||||
|
if (!timeoutRef.current) {
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
timeoutRef.current = null
|
||||||
|
|
||||||
|
const pending = pendingValueRef.current
|
||||||
|
|
||||||
|
if (!pending) return
|
||||||
|
|
||||||
|
pendingValueRef.current = null
|
||||||
|
|
||||||
|
callUpdateUserSettings(pending.componentName, pending.value)
|
||||||
|
}, 1000 - elapsed)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[componentName, localSidebarWidth, callUpdateUserSettings]
|
||||||
|
)
|
||||||
|
|
||||||
|
return [localSidebarWidth, setSidebarWidth]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSidebarWidth
|
||||||
Loading…
x
Reference in New Issue
Block a user