import { cloneElement, isValidElement, useCallback, useContext, useEffect, useRef, useState } from 'react' import PropTypes from 'prop-types' import { Layout, Splitter } from 'antd' import { useMediaQuery } from 'react-responsive' import { ElectronContext } from '../context/ElectronContext' const COLLAPSE_BELOW_PX = 150 const DEFAULT_SIDEBAR_PX = 300 const MAX_SIDEBAR_PX = 400 const COLLAPSED_WIDTH_PX = 80 const ELECTRON_COLLAPSED_WIDTH_PX = 55 const COLLAPSE_STORAGE_KEY = 'DashboardSidebar_collapseState' const SIZE_STORAGE_KEY = 'DashboardSidebar_width' const readStoredBoolean = (key, fallback) => { try { const saved = sessionStorage.getItem(key) return saved ? JSON.parse(saved) : fallback } catch { return fallback } } const readStoredNumber = (key, fallback) => { try { const saved = sessionStorage.getItem(key) const value = saved ? JSON.parse(saved) : fallback return typeof value === 'number' && !Number.isNaN(value) ? value : fallback } catch { return fallback } } const clampExpandedSize = (size) => Math.min(MAX_SIDEBAR_PX, Math.max(size, COLLAPSE_BELOW_PX)) const DashboardSidebarSplitter = ({ sidebar, children }) => { const isMobile = useMediaQuery({ maxWidth: 768 }) const { isElectron } = useContext(ElectronContext) const collapsedWidth = isElectron ? ELECTRON_COLLAPSED_WIDTH_PX : COLLAPSED_WIDTH_PX const [collapsed, setCollapsed] = useState(() => readStoredBoolean(COLLAPSE_STORAGE_KEY, false) ) const [sidebarSize, setSidebarSize] = useState(() => { if (readStoredBoolean(COLLAPSE_STORAGE_KEY, false)) { return isElectron ? ELECTRON_COLLAPSED_WIDTH_PX : COLLAPSED_WIDTH_PX } return clampExpandedSize( readStoredNumber(SIZE_STORAGE_KEY, DEFAULT_SIDEBAR_PX) ) }) const collapsedRef = useRef(collapsed) collapsedRef.current = collapsed const lastExpandedSizeRef = useRef( clampExpandedSize(readStoredNumber(SIZE_STORAGE_KEY, DEFAULT_SIDEBAR_PX)) ) const [sizeTransitioning, setSizeTransitioning] = useState(false) const sizeTransitionTimeoutRef = useRef(null) const startSizeTransition = useCallback(() => { setSizeTransitioning(true) if (sizeTransitionTimeoutRef.current) { clearTimeout(sizeTransitionTimeoutRef.current) } sizeTransitionTimeoutRef.current = setTimeout(() => { setSizeTransitioning(false) sizeTransitionTimeoutRef.current = null }, 150) }, []) useEffect( () => () => { if (sizeTransitionTimeoutRef.current) { clearTimeout(sizeTransitionTimeoutRef.current) } }, [] ) const persistCollapsed = (nextCollapsed) => { sessionStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify(nextCollapsed)) } const persistSize = (size) => { sessionStorage.setItem(SIZE_STORAGE_KEY, JSON.stringify(size)) } const collapseSidebar = useCallback( (expandedSize) => { if (expandedSize >= COLLAPSE_BELOW_PX) { lastExpandedSizeRef.current = expandedSize persistSize(expandedSize) } collapsedRef.current = true setCollapsed(true) persistCollapsed(true) startSizeTransition() setSidebarSize(collapsedWidth) }, [collapsedWidth, startSizeTransition] ) const expandSidebar = useCallback(() => { collapsedRef.current = false setCollapsed(false) persistCollapsed(false) startSizeTransition() setSidebarSize(DEFAULT_SIDEBAR_PX) persistSize(DEFAULT_SIDEBAR_PX) lastExpandedSizeRef.current = DEFAULT_SIDEBAR_PX }, [startSizeTransition]) const handleSidebarCollapse = useCallback( (nextCollapsed) => { if (nextCollapsed) { collapseSidebar(sidebarSize) } else { expandSidebar() } }, [collapseSidebar, expandSidebar, sidebarSize] ) const handleResize = useCallback( (sizes) => { if (collapsedRef.current) { setSidebarSize(collapsedWidth) return } const size = sizes[0] if (size < COLLAPSE_BELOW_PX) { collapseSidebar(lastExpandedSizeRef.current) return } setSidebarSize(size) lastExpandedSizeRef.current = clampExpandedSize(size) }, [collapseSidebar, collapsedWidth] ) const handleResizeEnd = useCallback( (sizes) => { if (collapsedRef.current) { setSidebarSize(collapsedWidth) return } const next = clampExpandedSize(sizes[0]) lastExpandedSizeRef.current = next setSidebarSize(next) persistSize(next) }, [collapsedWidth] ) const sidebarNode = isValidElement(sidebar) ? cloneElement(sidebar, { collapsed, collapsedWidth, onCollapse: handleSidebarCollapse }) : sidebar if (isMobile) { return ( {sidebarNode} {children} ) } return ( {sidebarNode} {children} ) } DashboardSidebarSplitter.propTypes = { sidebar: PropTypes.node.isRequired, children: PropTypes.node.isRequired } export default DashboardSidebarSplitter