Files
clash-verge-rev/src/components/layout/layout-item.tsx
T
Ryovik 6960daf05e fix(ui): center sidebar item labels (#7289)
Remove the negative text offset so labels remain centered within the list item text area.
2026-08-13 17:04:31 +00:00

131 lines
3.6 KiB
TypeScript

import type {
DraggableAttributes,
DraggableSyntheticListeners,
} from '@dnd-kit/core'
import {
alpha,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
} from '@mui/material'
import type { CSSProperties, PointerEvent, ReactNode } from 'react'
import { useCallback } 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 { onPointerDown, ...otherListeners } = draggable
? (listeners ?? {})
: {}
const handlePointerDown = useCallback(
(event: PointerEvent<HTMLDivElement>) => {
onPointerDown?.(event)
},
[onPointerDown],
)
return (
<ListItem
ref={setNodeRef}
style={style}
sx={[
{ py: 0.5, maxWidth: 250, mx: 'auto', padding: '4px 0px' },
isDragging ? { opacity: 0.78 } : {},
]}
>
<ListItemButton
selected={!!match}
{...(draggable ? (attributes ?? {}) : {})}
{...(draggable ? otherListeners : {})}
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}
onPointerDown={handlePointerDown}
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',
}}
primary={children}
/>
</ListItemButton>
</ListItem>
)
}