Cactus 57b17ab8d3
feat: add navigation collapse functionality to layout (#5815)
* feat(layout): add collapsible navbar toggle in UI settings

* refactor(layout): adjust collapsed navbar styles

* docs: Changelog.md

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-19 18:15:20 +08:00

122 lines
3.5 KiB
TypeScript

import type {
DraggableAttributes,
DraggableSyntheticListeners,
} from "@dnd-kit/core";
import {
alpha,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
} from "@mui/material";
import type { CSSProperties, ReactNode } from "react";
import { useMatch, useNavigate, useResolvedPath } from "react-router";
import { useVerge } from "@/hooks/use-verge";
interface SortableProps {
setNodeRef?: (element: HTMLElement | null) => void;
attributes?: DraggableAttributes;
listeners?: DraggableSyntheticListeners;
style?: CSSProperties;
isDragging?: boolean;
disabled?: boolean;
}
interface Props {
to: string;
children: string;
icon: ReactNode[];
sortable?: SortableProps;
}
export const LayoutItem = (props: Props) => {
const { to, children, icon, sortable } = props;
const { verge } = useVerge();
const { menu_icon } = verge ?? {};
const navCollapsed = verge?.collapse_navbar ?? false;
const resolved = useResolvedPath(to);
const match = useMatch({ path: resolved.pathname, end: true });
const navigate = useNavigate();
const effectiveMenuIcon =
navCollapsed && menu_icon === "disable" ? "monochrome" : menu_icon;
const { setNodeRef, attributes, listeners, style, isDragging, disabled } =
sortable ?? {};
const draggable = Boolean(sortable) && !disabled;
const dragHandleProps = draggable
? { ...(attributes ?? {}), ...(listeners ?? {}) }
: undefined;
return (
<ListItem
ref={setNodeRef}
style={style}
sx={[
{ py: 0.5, maxWidth: 250, mx: "auto", padding: "4px 0px" },
isDragging ? { opacity: 0.78 } : {},
]}
>
<ListItemButton
selected={!!match}
{...(dragHandleProps ?? {})}
sx={[
{
borderRadius: 2,
marginLeft: 1.25,
paddingLeft: 1,
paddingRight: 1,
marginRight: 1.25,
cursor: draggable ? "grab" : "pointer",
"&:active": draggable ? { cursor: "grabbing" } : {},
"& .MuiListItemText-primary": {
color: "text.primary",
fontWeight: "700",
},
},
({ palette: { mode, primary } }) => {
const bgcolor =
mode === "light"
? alpha(primary.main, 0.15)
: alpha(primary.main, 0.35);
const color = mode === "light" ? "#1f1f1f" : "#ffffff";
return {
"&.Mui-selected": { bgcolor },
"&.Mui-selected:hover": { bgcolor },
"&.Mui-selected .MuiListItemText-primary": { color },
};
},
]}
title={navCollapsed ? children : undefined}
aria-label={navCollapsed ? children : undefined}
onClick={() => navigate(to)}
>
{(effectiveMenuIcon === "monochrome" || !effectiveMenuIcon) && (
<ListItemIcon
sx={{
color: "text.primary",
marginLeft: "6px",
cursor: draggable ? "grab" : "inherit",
}}
>
{icon[0]}
</ListItemIcon>
)}
{effectiveMenuIcon === "colorful" && (
<ListItemIcon sx={{ cursor: draggable ? "grab" : "inherit" }}>
{icon[1]}
</ListItemIcon>
)}
<ListItemText
sx={{
textAlign: "center",
marginLeft: effectiveMenuIcon === "disable" ? "" : "-35px",
}}
primary={children}
/>
</ListItemButton>
</ListItem>
);
};