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
|
.farmcontrol-splitter.ant-splitter-horizontal.is-size-transitioning
|
||||||
.ant-splitter-panel {
|
.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
|
.farmcontrol-splitter
|
||||||
@ -1563,3 +1574,13 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
.ant-splitter-panel.dashboard-splitter-content-panel {
|
.ant-splitter-panel.dashboard-splitter-content-panel {
|
||||||
flex-grow: 1 !important;
|
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 MAX_SIDEBAR_PX = 400
|
||||||
const COLLAPSED_WIDTH_PX = 80
|
const COLLAPSED_WIDTH_PX = 80
|
||||||
const ELECTRON_COLLAPSED_WIDTH_PX = 55
|
const ELECTRON_COLLAPSED_WIDTH_PX = 55
|
||||||
|
const EXPAND_DRAG_PX = 10
|
||||||
|
const SIZE_TRANSITION_MS = 150
|
||||||
const COLLAPSE_STORAGE_KEY = 'DashboardSidebar_collapseState'
|
const COLLAPSE_STORAGE_KEY = 'DashboardSidebar_collapseState'
|
||||||
const SIZE_STORAGE_KEY = 'DashboardSidebar_width'
|
const SIZE_STORAGE_KEY = 'DashboardSidebar_width'
|
||||||
|
|
||||||
@ -66,8 +68,45 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
const lastExpandedSizeRef = useRef(
|
const lastExpandedSizeRef = useRef(
|
||||||
clampExpandedSize(readStoredNumber(SIZE_STORAGE_KEY, DEFAULT_SIDEBAR_PX))
|
clampExpandedSize(readStoredNumber(SIZE_STORAGE_KEY, DEFAULT_SIDEBAR_PX))
|
||||||
)
|
)
|
||||||
|
const sidebarSizeRef = useRef(sidebarSize)
|
||||||
|
sidebarSizeRef.current = sidebarSize
|
||||||
|
const collapseSizeRafRef = useRef([])
|
||||||
const [sizeTransitioning, setSizeTransitioning] = useState(false)
|
const [sizeTransitioning, setSizeTransitioning] = useState(false)
|
||||||
const sizeTransitionTimeoutRef = useRef(null)
|
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(() => {
|
const startSizeTransition = useCallback(() => {
|
||||||
setSizeTransitioning(true)
|
setSizeTransitioning(true)
|
||||||
@ -77,16 +116,69 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
sizeTransitionTimeoutRef.current = setTimeout(() => {
|
sizeTransitionTimeoutRef.current = setTimeout(() => {
|
||||||
setSizeTransitioning(false)
|
setSizeTransitioning(false)
|
||||||
sizeTransitionTimeoutRef.current = null
|
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(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
if (sizeTransitionTimeoutRef.current) {
|
if (sizeTransitionTimeoutRef.current) {
|
||||||
clearTimeout(sizeTransitionTimeoutRef.current)
|
clearTimeout(sizeTransitionTimeoutRef.current)
|
||||||
}
|
}
|
||||||
|
if (lockTimeoutRef.current) {
|
||||||
|
clearTimeout(lockTimeoutRef.current)
|
||||||
|
}
|
||||||
|
cancelCollapseSizeAnimation()
|
||||||
|
clearPointerUnlock()
|
||||||
},
|
},
|
||||||
[]
|
[cancelCollapseSizeAnimation, clearPointerUnlock]
|
||||||
)
|
)
|
||||||
|
|
||||||
const persistCollapsed = (nextCollapsed) => {
|
const persistCollapsed = (nextCollapsed) => {
|
||||||
@ -104,15 +196,17 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
persistSize(expandedSize)
|
persistSize(expandedSize)
|
||||||
}
|
}
|
||||||
collapsedRef.current = true
|
collapsedRef.current = true
|
||||||
setCollapsed(true)
|
|
||||||
persistCollapsed(true)
|
persistCollapsed(true)
|
||||||
startSizeTransition()
|
startSizeTransition()
|
||||||
setSidebarSize(collapsedWidth)
|
setSidebarSize(sidebarSizeRef.current)
|
||||||
|
setCollapsed(true)
|
||||||
|
animateCollapsedSize()
|
||||||
},
|
},
|
||||||
[collapsedWidth, startSizeTransition]
|
[animateCollapsedSize, startSizeTransition]
|
||||||
)
|
)
|
||||||
|
|
||||||
const expandSidebar = useCallback(() => {
|
const expandSidebar = useCallback(() => {
|
||||||
|
cancelCollapseSizeAnimation()
|
||||||
collapsedRef.current = false
|
collapsedRef.current = false
|
||||||
setCollapsed(false)
|
setCollapsed(false)
|
||||||
persistCollapsed(false)
|
persistCollapsed(false)
|
||||||
@ -120,7 +214,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
setSidebarSize(DEFAULT_SIDEBAR_PX)
|
setSidebarSize(DEFAULT_SIDEBAR_PX)
|
||||||
persistSize(DEFAULT_SIDEBAR_PX)
|
persistSize(DEFAULT_SIDEBAR_PX)
|
||||||
lastExpandedSizeRef.current = DEFAULT_SIDEBAR_PX
|
lastExpandedSizeRef.current = DEFAULT_SIDEBAR_PX
|
||||||
}, [startSizeTransition])
|
}, [cancelCollapseSizeAnimation, startSizeTransition])
|
||||||
|
|
||||||
const handleSidebarCollapse = useCallback(
|
const handleSidebarCollapse = useCallback(
|
||||||
(nextCollapsed) => {
|
(nextCollapsed) => {
|
||||||
@ -129,33 +223,79 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
} else {
|
} else {
|
||||||
expandSidebar()
|
expandSidebar()
|
||||||
}
|
}
|
||||||
|
lockResizeFor(SIZE_TRANSITION_MS)
|
||||||
},
|
},
|
||||||
[collapseSidebar, expandSidebar, sidebarSize]
|
[collapseSidebar, expandSidebar, lockResizeFor, sidebarSize]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleResizeStart = useCallback(() => {
|
||||||
|
if (!resizeLockedRef.current) {
|
||||||
|
ignoreResizeEndRef.current = false
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleResize = useCallback(
|
const handleResize = useCallback(
|
||||||
(sizes) => {
|
(sizes) => {
|
||||||
if (collapsedRef.current) {
|
if (resizeLockedRef.current || suppressGestureRef.current) {
|
||||||
setSidebarSize(collapsedWidth)
|
if (collapsedRef.current) {
|
||||||
|
setSidebarSize(collapsedWidth)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = sizes[0]
|
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) {
|
if (size < COLLAPSE_BELOW_PX) {
|
||||||
collapseSidebar(lastExpandedSizeRef.current)
|
collapseSidebar(lastExpandedSizeRef.current)
|
||||||
|
lockResizeUntilPointerUp()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSidebarSize(size)
|
setSidebarSize(size)
|
||||||
lastExpandedSizeRef.current = clampExpandedSize(size)
|
lastExpandedSizeRef.current = clampExpandedSize(size)
|
||||||
},
|
},
|
||||||
[collapseSidebar, collapsedWidth]
|
[
|
||||||
|
collapseSidebar,
|
||||||
|
collapsedWidth,
|
||||||
|
expandSidebar,
|
||||||
|
lockResizeFor,
|
||||||
|
lockResizeUntilPointerUp
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleResizeEnd = useCallback(
|
const handleResizeEnd = useCallback(
|
||||||
(sizes) => {
|
(sizes) => {
|
||||||
|
if (
|
||||||
|
resizeLockedRef.current ||
|
||||||
|
suppressGestureRef.current ||
|
||||||
|
ignoreResizeEndRef.current
|
||||||
|
) {
|
||||||
|
ignoreResizeEndRef.current = false
|
||||||
|
if (collapsedRef.current) {
|
||||||
|
startSizeTransition()
|
||||||
|
setSidebarSize(collapsedWidth)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (collapsedRef.current) {
|
if (collapsedRef.current) {
|
||||||
setSidebarSize(collapsedWidth)
|
if (sizes[0] - collapsedWidth > EXPAND_DRAG_PX) {
|
||||||
|
expandSidebar()
|
||||||
|
lockResizeFor(SIZE_TRANSITION_MS)
|
||||||
|
} else {
|
||||||
|
startSizeTransition()
|
||||||
|
setSidebarSize(collapsedWidth)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +304,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
setSidebarSize(next)
|
setSidebarSize(next)
|
||||||
persistSize(next)
|
persistSize(next)
|
||||||
},
|
},
|
||||||
[collapsedWidth]
|
[collapsedWidth, expandSidebar, lockResizeFor, startSizeTransition]
|
||||||
)
|
)
|
||||||
|
|
||||||
const sidebarNode = isValidElement(sidebar)
|
const sidebarNode = isValidElement(sidebar)
|
||||||
@ -189,21 +329,27 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
<Splitter
|
<Splitter
|
||||||
className={`farmcontrol-splitter dashboard-splitter${
|
className={`farmcontrol-splitter dashboard-splitter${
|
||||||
sizeTransitioning ? ' is-size-transitioning' : ''
|
sizeTransitioning ? ' is-size-transitioning' : ''
|
||||||
}`}
|
}${collapsed ? ' is-sidebar-collapsed' : ''}`}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
|
onResizeStart={handleResizeStart}
|
||||||
onResize={handleResize}
|
onResize={handleResize}
|
||||||
onResizeEnd={handleResizeEnd}
|
onResizeEnd={handleResizeEnd}
|
||||||
>
|
>
|
||||||
<Splitter.Panel
|
<Splitter.Panel
|
||||||
|
className={`dashboard-splitter-sidebar-panel${
|
||||||
|
collapsed ? ' is-collapsed' : ''
|
||||||
|
}`}
|
||||||
size={sidebarSize}
|
size={sidebarSize}
|
||||||
min={collapsedWidth}
|
min={collapsedWidth}
|
||||||
max={collapsed ? collapsedWidth : MAX_SIDEBAR_PX}
|
max={
|
||||||
resizable={!collapsed}
|
collapsed && resizeLocked && !sizeTransitioning
|
||||||
|
? collapsedWidth
|
||||||
|
: MAX_SIDEBAR_PX
|
||||||
|
}
|
||||||
|
resizable={!resizeLocked}
|
||||||
style={{
|
style={{
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
minWidth: collapsedWidth,
|
'--dashboard-splitter-sidebar-size': `${sidebarSize}px`
|
||||||
maxWidth:
|
|
||||||
collapsed && !sizeTransitioning ? collapsedWidth : undefined
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sidebarNode}
|
{sidebarNode}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user