Enhance Dashboard Sidebar Resizing and Transition Behavior
- Updated CSS for the DashboardSidebarSplitter to improve sidebar resizing transitions, adding new properties for smoother animations. - Introduced state management for resize locking and pointer events to enhance user interaction during sidebar adjustments. - Refactored resizing logic to support dynamic sidebar size adjustments, improving responsiveness and user experience.
This commit is contained in:
parent
fdb3045053
commit
c4b4a88434
@ -1021,7 +1021,18 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
|
||||
.farmcontrol-splitter.ant-splitter-horizontal.is-size-transitioning
|
||||
.ant-splitter-panel {
|
||||
transition: flex-basis 0.15s ease-in-out;
|
||||
transition:
|
||||
--dashboard-splitter-sidebar-size 0.15s ease-in-out,
|
||||
flex-basis 0.15s ease-in-out,
|
||||
width 0.15s ease-in-out,
|
||||
min-width 0.15s ease-in-out,
|
||||
max-width 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
@property --dashboard-splitter-sidebar-size {
|
||||
syntax: '<length>';
|
||||
inherits: true;
|
||||
initial-value: 80px;
|
||||
}
|
||||
|
||||
.farmcontrol-splitter
|
||||
@ -1563,3 +1574,13 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
.ant-splitter-panel.dashboard-splitter-content-panel {
|
||||
flex-grow: 1 !important;
|
||||
}
|
||||
|
||||
.dashboard-splitter.farmcontrol-splitter
|
||||
.ant-splitter-panel.dashboard-splitter-sidebar-panel.is-collapsed {
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
flex-basis: var(--dashboard-splitter-sidebar-size) !important;
|
||||
width: var(--dashboard-splitter-sidebar-size) !important;
|
||||
min-width: var(--dashboard-splitter-sidebar-size) !important;
|
||||
max-width: var(--dashboard-splitter-sidebar-size) !important;
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ const DEFAULT_SIDEBAR_PX = 300
|
||||
const MAX_SIDEBAR_PX = 400
|
||||
const COLLAPSED_WIDTH_PX = 80
|
||||
const ELECTRON_COLLAPSED_WIDTH_PX = 55
|
||||
const EXPAND_DRAG_PX = 10
|
||||
const SIZE_TRANSITION_MS = 150
|
||||
const COLLAPSE_STORAGE_KEY = 'DashboardSidebar_collapseState'
|
||||
const SIZE_STORAGE_KEY = 'DashboardSidebar_width'
|
||||
|
||||
@ -66,8 +68,45 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
const lastExpandedSizeRef = useRef(
|
||||
clampExpandedSize(readStoredNumber(SIZE_STORAGE_KEY, DEFAULT_SIDEBAR_PX))
|
||||
)
|
||||
const sidebarSizeRef = useRef(sidebarSize)
|
||||
sidebarSizeRef.current = sidebarSize
|
||||
const collapseSizeRafRef = useRef([])
|
||||
const [sizeTransitioning, setSizeTransitioning] = useState(false)
|
||||
const sizeTransitionTimeoutRef = useRef(null)
|
||||
const [resizeLocked, setResizeLocked] = useState(false)
|
||||
const resizeLockedRef = useRef(false)
|
||||
const suppressGestureRef = useRef(false)
|
||||
const lockUntilPointerUpRef = useRef(false)
|
||||
const lockTimeoutRef = useRef(null)
|
||||
const pointerUnlockRef = useRef(null)
|
||||
const ignoreResizeEndRef = useRef(false)
|
||||
|
||||
const setResizeLockedState = useCallback((locked) => {
|
||||
resizeLockedRef.current = locked
|
||||
setResizeLocked(locked)
|
||||
}, [])
|
||||
|
||||
const clearPointerUnlock = useCallback(() => {
|
||||
if (!pointerUnlockRef.current) return
|
||||
window.removeEventListener('pointerup', pointerUnlockRef.current)
|
||||
window.removeEventListener('pointercancel', pointerUnlockRef.current)
|
||||
pointerUnlockRef.current = null
|
||||
}, [])
|
||||
|
||||
const attachPointerUnlock = useCallback(() => {
|
||||
if (pointerUnlockRef.current) return
|
||||
const handler = () => {
|
||||
clearPointerUnlock()
|
||||
suppressGestureRef.current = false
|
||||
if (lockUntilPointerUpRef.current) {
|
||||
lockUntilPointerUpRef.current = false
|
||||
setResizeLockedState(false)
|
||||
}
|
||||
}
|
||||
pointerUnlockRef.current = handler
|
||||
window.addEventListener('pointerup', handler)
|
||||
window.addEventListener('pointercancel', handler)
|
||||
}, [clearPointerUnlock, setResizeLockedState])
|
||||
|
||||
const startSizeTransition = useCallback(() => {
|
||||
setSizeTransitioning(true)
|
||||
@ -77,16 +116,69 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
sizeTransitionTimeoutRef.current = setTimeout(() => {
|
||||
setSizeTransitioning(false)
|
||||
sizeTransitionTimeoutRef.current = null
|
||||
}, 150)
|
||||
}, SIZE_TRANSITION_MS)
|
||||
}, [])
|
||||
|
||||
const cancelCollapseSizeAnimation = useCallback(() => {
|
||||
collapseSizeRafRef.current.forEach((id) => cancelAnimationFrame(id))
|
||||
collapseSizeRafRef.current = []
|
||||
}, [])
|
||||
|
||||
const animateCollapsedSize = useCallback(() => {
|
||||
cancelCollapseSizeAnimation()
|
||||
const ids = []
|
||||
ids.push(
|
||||
requestAnimationFrame(() => {
|
||||
ids.push(
|
||||
requestAnimationFrame(() => {
|
||||
collapseSizeRafRef.current = []
|
||||
setSidebarSize(collapsedWidth)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
collapseSizeRafRef.current = ids
|
||||
}, [cancelCollapseSizeAnimation, collapsedWidth])
|
||||
|
||||
const lockResizeUntilPointerUp = useCallback(() => {
|
||||
ignoreResizeEndRef.current = true
|
||||
suppressGestureRef.current = true
|
||||
lockUntilPointerUpRef.current = true
|
||||
setResizeLockedState(true)
|
||||
attachPointerUnlock()
|
||||
}, [attachPointerUnlock, setResizeLockedState])
|
||||
|
||||
const lockResizeFor = useCallback(
|
||||
(ms) => {
|
||||
ignoreResizeEndRef.current = true
|
||||
suppressGestureRef.current = true
|
||||
setResizeLockedState(true)
|
||||
attachPointerUnlock()
|
||||
if (lockTimeoutRef.current) {
|
||||
clearTimeout(lockTimeoutRef.current)
|
||||
}
|
||||
lockTimeoutRef.current = setTimeout(() => {
|
||||
lockTimeoutRef.current = null
|
||||
if (!lockUntilPointerUpRef.current) {
|
||||
setResizeLockedState(false)
|
||||
}
|
||||
}, ms)
|
||||
},
|
||||
[attachPointerUnlock, setResizeLockedState]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (sizeTransitionTimeoutRef.current) {
|
||||
clearTimeout(sizeTransitionTimeoutRef.current)
|
||||
}
|
||||
if (lockTimeoutRef.current) {
|
||||
clearTimeout(lockTimeoutRef.current)
|
||||
}
|
||||
cancelCollapseSizeAnimation()
|
||||
clearPointerUnlock()
|
||||
},
|
||||
[]
|
||||
[cancelCollapseSizeAnimation, clearPointerUnlock]
|
||||
)
|
||||
|
||||
const persistCollapsed = (nextCollapsed) => {
|
||||
@ -104,15 +196,17 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
persistSize(expandedSize)
|
||||
}
|
||||
collapsedRef.current = true
|
||||
setCollapsed(true)
|
||||
persistCollapsed(true)
|
||||
startSizeTransition()
|
||||
setSidebarSize(collapsedWidth)
|
||||
setSidebarSize(sidebarSizeRef.current)
|
||||
setCollapsed(true)
|
||||
animateCollapsedSize()
|
||||
},
|
||||
[collapsedWidth, startSizeTransition]
|
||||
[animateCollapsedSize, startSizeTransition]
|
||||
)
|
||||
|
||||
const expandSidebar = useCallback(() => {
|
||||
cancelCollapseSizeAnimation()
|
||||
collapsedRef.current = false
|
||||
setCollapsed(false)
|
||||
persistCollapsed(false)
|
||||
@ -120,7 +214,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
setSidebarSize(DEFAULT_SIDEBAR_PX)
|
||||
persistSize(DEFAULT_SIDEBAR_PX)
|
||||
lastExpandedSizeRef.current = DEFAULT_SIDEBAR_PX
|
||||
}, [startSizeTransition])
|
||||
}, [cancelCollapseSizeAnimation, startSizeTransition])
|
||||
|
||||
const handleSidebarCollapse = useCallback(
|
||||
(nextCollapsed) => {
|
||||
@ -129,33 +223,79 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
} else {
|
||||
expandSidebar()
|
||||
}
|
||||
lockResizeFor(SIZE_TRANSITION_MS)
|
||||
},
|
||||
[collapseSidebar, expandSidebar, sidebarSize]
|
||||
[collapseSidebar, expandSidebar, lockResizeFor, sidebarSize]
|
||||
)
|
||||
|
||||
const handleResizeStart = useCallback(() => {
|
||||
if (!resizeLockedRef.current) {
|
||||
ignoreResizeEndRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleResize = useCallback(
|
||||
(sizes) => {
|
||||
if (resizeLockedRef.current || suppressGestureRef.current) {
|
||||
if (collapsedRef.current) {
|
||||
setSidebarSize(collapsedWidth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const size = sizes[0]
|
||||
|
||||
if (collapsedRef.current) {
|
||||
if (size - collapsedWidth > EXPAND_DRAG_PX) {
|
||||
expandSidebar()
|
||||
lockResizeFor(SIZE_TRANSITION_MS)
|
||||
return
|
||||
}
|
||||
setSidebarSize(Math.max(collapsedWidth, size))
|
||||
return
|
||||
}
|
||||
|
||||
if (size < COLLAPSE_BELOW_PX) {
|
||||
collapseSidebar(lastExpandedSizeRef.current)
|
||||
lockResizeUntilPointerUp()
|
||||
return
|
||||
}
|
||||
|
||||
setSidebarSize(size)
|
||||
lastExpandedSizeRef.current = clampExpandedSize(size)
|
||||
},
|
||||
[collapseSidebar, collapsedWidth]
|
||||
[
|
||||
collapseSidebar,
|
||||
collapsedWidth,
|
||||
expandSidebar,
|
||||
lockResizeFor,
|
||||
lockResizeUntilPointerUp
|
||||
]
|
||||
)
|
||||
|
||||
const handleResizeEnd = useCallback(
|
||||
(sizes) => {
|
||||
if (
|
||||
resizeLockedRef.current ||
|
||||
suppressGestureRef.current ||
|
||||
ignoreResizeEndRef.current
|
||||
) {
|
||||
ignoreResizeEndRef.current = false
|
||||
if (collapsedRef.current) {
|
||||
startSizeTransition()
|
||||
setSidebarSize(collapsedWidth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (collapsedRef.current) {
|
||||
if (sizes[0] - collapsedWidth > EXPAND_DRAG_PX) {
|
||||
expandSidebar()
|
||||
lockResizeFor(SIZE_TRANSITION_MS)
|
||||
} else {
|
||||
startSizeTransition()
|
||||
setSidebarSize(collapsedWidth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -164,7 +304,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
setSidebarSize(next)
|
||||
persistSize(next)
|
||||
},
|
||||
[collapsedWidth]
|
||||
[collapsedWidth, expandSidebar, lockResizeFor, startSizeTransition]
|
||||
)
|
||||
|
||||
const sidebarNode = isValidElement(sidebar)
|
||||
@ -189,21 +329,27 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
<Splitter
|
||||
className={`farmcontrol-splitter dashboard-splitter${
|
||||
sizeTransitioning ? ' is-size-transitioning' : ''
|
||||
}`}
|
||||
}${collapsed ? ' is-sidebar-collapsed' : ''}`}
|
||||
style={{ height: '100%' }}
|
||||
onResizeStart={handleResizeStart}
|
||||
onResize={handleResize}
|
||||
onResizeEnd={handleResizeEnd}
|
||||
>
|
||||
<Splitter.Panel
|
||||
className={`dashboard-splitter-sidebar-panel${
|
||||
collapsed ? ' is-collapsed' : ''
|
||||
}`}
|
||||
size={sidebarSize}
|
||||
min={collapsedWidth}
|
||||
max={collapsed ? collapsedWidth : MAX_SIDEBAR_PX}
|
||||
resizable={!collapsed}
|
||||
max={
|
||||
collapsed && resizeLocked && !sizeTransitioning
|
||||
? collapsedWidth
|
||||
: MAX_SIDEBAR_PX
|
||||
}
|
||||
resizable={!resizeLocked}
|
||||
style={{
|
||||
overflow: 'visible',
|
||||
minWidth: collapsedWidth,
|
||||
maxWidth:
|
||||
collapsed && !sizeTransitioning ? collapsedWidth : undefined
|
||||
'--dashboard-splitter-sidebar-size': `${sidebarSize}px`
|
||||
}}
|
||||
>
|
||||
{sidebarNode}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user