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 {
|
||||
background: #78787854 !important;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.printer-alerts-display-popover .ant-popover-inner {
|
||||
@ -1612,23 +1613,23 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
top: 0;
|
||||
right: -11px;
|
||||
bottom: 0;
|
||||
width: 11px;
|
||||
width: 6px;
|
||||
z-index: 3;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
pointer-events: auto;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.objectKanbanColumnResizeHandle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
bottom: 11px;
|
||||
top: 11.15px;
|
||||
bottom: 10.85px;
|
||||
right: 1px;
|
||||
width: 2px;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
@ -1640,16 +1641,9 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.objectKanbanColumnResizeHandle.active::before,
|
||||
.objectKanbanColumnResizeHandle.active::after {
|
||||
background: #0e3a5b;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dark-mode .objectKanbanColumnResizeHandle::after {
|
||||
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);
|
||||
}
|
||||
|
||||
.dark-mode .objectKanbanColumnResizeHandle.active::before,
|
||||
.dark-mode .objectKanbanColumnResizeHandle.active::after {
|
||||
background: #0b4c7e;
|
||||
opacity: 1;
|
||||
.light-mode .objectKanbanColumnResizeHandle::after {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.objectKanbanColumnResizeHandle:hover::before {
|
||||
.light-mode .objectKanbanColumnResizeHandle::before {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dark-mode .objectKanbanColumnResizeHandle:hover::before {
|
||||
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 * {
|
||||
cursor: col-resize !important;
|
||||
@ -2313,6 +2321,15 @@ body.objectKanbanColumnResizing * {
|
||||
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
|
||||
.elipsis-text-wrapper.ant-typography
|
||||
code.elipsis-text-measure,
|
||||
|
||||
@ -69,6 +69,9 @@ import {
|
||||
import { areValuesEqual } from '../utils/Utils'
|
||||
import MissingPlaceholder from './MissingPlaceholder'
|
||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||
import useSidebarWidth from '../hooks/useSidebarWidth'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
const logger = loglevel.getLogger('DasboardTable')
|
||||
logger.setLevel(config.logLevel)
|
||||
@ -412,6 +415,10 @@ const ObjectTable = forwardRef(
|
||||
const [sidebarFilter, setSidebarFilter] = useState({})
|
||||
const [tableSorter, setTableSorter] = useState({})
|
||||
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
|
||||
// the same tick; props remain the source of truth after commit.
|
||||
@ -1929,7 +1936,11 @@ const ObjectTable = forwardRef(
|
||||
const cardsContainerNodeRef = useRef(null)
|
||||
const cardsResizeObserverRef = useRef(null)
|
||||
|
||||
const measureCardColSpan = useCallback(() => {
|
||||
const measureCardColSpan = useCallback((sizes) => {
|
||||
if (sizes?.[1]) {
|
||||
setSidebarWidth(sizes[1])
|
||||
}
|
||||
|
||||
const node = cardsContainerNodeRef.current
|
||||
if (!node) return
|
||||
const panel = node.closest('.ant-splitter-panel')
|
||||
@ -2107,17 +2118,53 @@ const ObjectTable = forwardRef(
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('loading', loading)
|
||||
}, [loading])
|
||||
const container = tableContentRef.current
|
||||
|
||||
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 = (
|
||||
<Flex
|
||||
gap={'middle'}
|
||||
vertical
|
||||
style={{ flex: 1, minWidth: 0, minHeight: 0 }}
|
||||
ref={tableContentRef}
|
||||
>
|
||||
<Splitter
|
||||
className={'farmcontrol-splitter large'}
|
||||
className={classNames('farmcontrol-splitter large', {
|
||||
'vertical-scroll-visible': verticalScrollVisible
|
||||
})}
|
||||
onResize={measureCardColSpan}
|
||||
onResizeEnd={measureCardColSpan}
|
||||
>
|
||||
@ -2214,8 +2261,8 @@ const ObjectTable = forwardRef(
|
||||
</Spin>
|
||||
)}
|
||||
</Splitter.Panel>
|
||||
{(showSortSidebar || showFilterSidebar) && (
|
||||
<Splitter.Panel defaultSize={'25%'}>
|
||||
{(showSortSidebar || showFilterSidebar) && initialized && (
|
||||
<Splitter.Panel size={sidebarWidth}>
|
||||
<Flex
|
||||
vertical
|
||||
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