import { DragDropProvider, KeyboardSensor, PointerSensor } from '@dnd-kit/react' import { Box, List, Menu, MenuItem, SvgIcon } from '@mui/material' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import iconDark from '@/assets/image/icon_dark.svg?react' import iconLight from '@/assets/image/icon_light.svg?react' import LogoSvg from '@/assets/image/logo.svg?react' import { useVerge } from '@/hooks/use-verge' import { useNavMenuOrder } from '@/pages/_layout/hooks' import { navItems } from '@/pages/_navigation' import { SortableItem } from '../base' import { LayoutItem } from './layout-item' import { LayoutTraffic } from './layout-traffic' import { UpdateButton } from './update-button' type MenuContextPosition = { top: number; left: number } interface LayoutSidebarProps { isDark: boolean isCollapsed: boolean } const SENSORS = [PointerSensor, KeyboardSensor] export const LayoutSidebar = (props: LayoutSidebarProps) => { const { isDark, isCollapsed } = props const { t } = useTranslation() const { verge, mutateVerge, patchVerge } = useVerge() const [menuUnlocked, setMenuUnlocked] = useState(false) const [menuContextPosition, setMenuContextPosition] = useState(null) const handleMenuOrderOptimisticUpdate = useCallback( (order: string[]) => { mutateVerge((prev) => (prev ? { ...prev, menu_order: order } : prev)) }, [mutateVerge], ) const handleMenuOrderPersist = useCallback( (order: string[]) => patchVerge({ menu_order: order }), [patchVerge], ) const { menuOrder, navItemMap, handleMenuDragEnd, isDefaultOrder, resetMenuOrder, } = useNavMenuOrder({ enabled: menuUnlocked, items: navItems, storedOrder: verge?.menu_order, onOptimisticUpdate: handleMenuOrderOptimisticUpdate, onPersist: handleMenuOrderPersist, }) const handleMenuContextMenu = useCallback( (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() setMenuContextPosition({ top: event.clientY, left: event.clientX }) }, [], ) const handleMenuContextClose = useCallback(() => { setMenuContextPosition(null) }, []) const handleResetMenuOrder = useCallback(() => { setMenuContextPosition(null) void resetMenuOrder() }, [resetMenuOrder]) const handleUnlockMenu = useCallback(() => { setMenuUnlocked(true) setMenuContextPosition(null) }, []) const handleLockMenu = useCallback(() => { setMenuUnlocked(false) setMenuContextPosition(null) }, []) const handleToggleNavCollapsed = useCallback(() => { setMenuContextPosition(null) void patchVerge({ collapse_navbar: !isCollapsed }) }, [isCollapsed, patchVerge]) // Navigation menu items const navMenuItems = menuOrder.map((path, index) => { const item = navItemMap.get(path) if (!item) return null // A registered sortable, even a disabled one, gets the drag-and-drop ARIA // attributes, so screen readers announce every item as a disabled draggable. if (!menuUnlocked) { return ( {t(item.label)} ) } return ( {(sortable) => ( {t(item.label)} )} ) }) return (
{/* Logo */}
{/* Edit navigation menu badge */} {menuUnlocked && ( ({ px: 1.5, py: 0.75, mx: 'auto', mb: 1, maxWidth: 250, borderRadius: 1.5, fontSize: 12, fontWeight: 600, textAlign: 'center', color: theme.palette.warning.contrastText, bgcolor: theme.palette.mode === 'light' ? theme.palette.warning.main : theme.palette.warning.dark, })} > {t('layout.components.navigation.menu.reorderMode')} )} {/* Navigation menu */} {navMenuItems} {/* Context menu */} {isCollapsed ? t('layout.components.navigation.menu.expandNavBar') : t('layout.components.navigation.menu.collapseNavBar')} {menuUnlocked ? t('layout.components.navigation.menu.lock') : t('layout.components.navigation.menu.unlock')} {t('layout.components.navigation.menu.restoreDefaultOrder')} {/* Traffic */}
) }