import { cloneElement, isValidElement, useCallback, useContext, useRef, useState } from 'react' import PropTypes from 'prop-types' import { Layout } from 'antd' import { useMediaQuery } from 'react-responsive' import { ElectronContext } from '../context/ElectronContext' import AdaptiveSplitter from './AdaptiveSplitter' const COLLAPSE_BELOW_PX = 200 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 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 collapsedWidth } 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 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 = clampExpandedSize(expandedSize) persistSize(lastExpandedSizeRef.current) } collapsedRef.current = true persistCollapsed(true) setCollapsed(true) setSidebarSize(collapsedWidth) }, [collapsedWidth] ) const expandSidebar = useCallback(() => { collapsedRef.current = false setCollapsed(false) persistCollapsed(false) const nextSize = clampExpandedSize(lastExpandedSizeRef.current) lastExpandedSizeRef.current = nextSize setSidebarSize(nextSize) persistSize(nextSize) }, []) const handleSidebarCollapse = useCallback( (nextCollapsed) => { if (nextCollapsed === collapsedRef.current) return if (nextCollapsed) { collapseSidebar(lastExpandedSizeRef.current) } else { expandSidebar() } }, [collapseSidebar, expandSidebar] ) const handleSplitterCollapse = useCallback( (nextCollapsed) => { if (nextCollapsed === collapsedRef.current) return if (nextCollapsed) { collapseSidebar(lastExpandedSizeRef.current) return } collapsedRef.current = false persistCollapsed(false) setCollapsed(false) }, [collapseSidebar] ) const handleResize = useCallback((sizes) => { const size = sizes[0] setSidebarSize(size) if (!collapsedRef.current && size >= COLLAPSE_BELOW_PX) { lastExpandedSizeRef.current = clampExpandedSize(size) } }, []) 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