3ece5e2e96
1. 工程配置: - 更新 tsconfig.json include 规则,解决 eslint.config.ts 未被包含的问题。 2. 组件重构 (Material UI 适配): - Navbar: 替换已弃用的 PaperProps 为 slotProps.paper。 - TimestampToDatetime / DatetimeToTimestamp: 替换已弃用的 InputProps 为 slotProps.input。 3. 类型与逻辑修复: - utils/recordUtils.tsx: 引入缺失依赖,忽略 UNSAFE_replayCanvas 等类型报错,修复构建问题。 - utils/messages.tsx: 完善事件参数类型定义。 4. 其他优化: - 统一 App.css 代码格式。 - 补充部分组件 (CopyButton, TimestampExecution) 的 React 导入和类型定义。
120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
import { NavLink, useLocation } from 'react-router-dom';
|
|
import { useState, ReactNode, MouseEvent } from 'react';
|
|
import {
|
|
AppBar,
|
|
Box,
|
|
IconButton,
|
|
Menu,
|
|
MenuItem,
|
|
Tab,
|
|
Tabs,
|
|
Toolbar,
|
|
useMediaQuery,
|
|
} from '@mui/material';
|
|
import MenuIcon from '@mui/icons-material/Menu';
|
|
|
|
interface RouteItem {
|
|
path: string;
|
|
label: string;
|
|
element: ReactNode;
|
|
}
|
|
|
|
interface NavbarProps {
|
|
items?: RouteItem[];
|
|
}
|
|
|
|
function Navbar({ items = [] }: NavbarProps) {
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
const isMenuOpen = Boolean(anchorEl);
|
|
const location = useLocation();
|
|
|
|
// Replicating the screen size logic from original component
|
|
const isLargeScreen = useMediaQuery('(min-width:768px)');
|
|
const isMediumScreen = useMediaQuery('(min-width:480px)');
|
|
|
|
let visibleItemsCount: number;
|
|
if (isLargeScreen) {
|
|
visibleItemsCount = items.length;
|
|
} else if (isMediumScreen) {
|
|
visibleItemsCount = Math.min(3, items.length);
|
|
} else {
|
|
// Small screen
|
|
visibleItemsCount = Math.min(2, items.length);
|
|
}
|
|
|
|
const visibleNavItems = items.slice(0, visibleItemsCount);
|
|
const collapsedNavItems = items.slice(visibleItemsCount);
|
|
|
|
const handleMenuOpen = (event: MouseEvent<HTMLElement>) => {
|
|
setAnchorEl(event.currentTarget);
|
|
};
|
|
|
|
const handleMenuClose = () => {
|
|
setAnchorEl(null);
|
|
};
|
|
|
|
// Find the current active tab index for the Tabs value
|
|
// Using startsWith to handle nested routes correctly.
|
|
const activeTabIndex = visibleNavItems.findIndex((item) =>
|
|
location.pathname.startsWith(item.path),
|
|
);
|
|
|
|
return (
|
|
<AppBar
|
|
position="static"
|
|
color="default"
|
|
elevation={0}
|
|
sx={{ backgroundColor: 'transparent', borderBottom: 1, borderColor: 'divider' }}
|
|
>
|
|
<Toolbar sx={{ justifyContent: 'center', position: 'relative' }}>
|
|
<Tabs
|
|
value={activeTabIndex === -1 ? false : activeTabIndex}
|
|
variant="scrollable"
|
|
scrollButtons="auto"
|
|
allowScrollButtonsMobile
|
|
aria-label="navigation tabs"
|
|
>
|
|
{visibleNavItems.map((item) => (
|
|
<Tab key={item.path} label={item.label} component={NavLink} to={item.path} />
|
|
))}
|
|
</Tabs>
|
|
|
|
{collapsedNavItems.length > 0 && (
|
|
<Box sx={{ position: 'absolute', right: 8 }}>
|
|
<IconButton color="inherit" aria-label="open menu" edge="end" onClick={handleMenuOpen}>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
<Menu
|
|
anchorEl={anchorEl}
|
|
open={isMenuOpen}
|
|
onClose={handleMenuClose}
|
|
slotProps={{
|
|
paper: {
|
|
style: {
|
|
maxHeight: 48 * 4.5,
|
|
width: '20ch',
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
{collapsedNavItems.map((item) => (
|
|
<MenuItem
|
|
key={item.path}
|
|
component={NavLink}
|
|
to={item.path}
|
|
onClick={handleMenuClose}
|
|
selected={location.pathname.startsWith(item.path)}
|
|
>
|
|
{item.label}
|
|
</MenuItem>
|
|
))}
|
|
</Menu>
|
|
</Box>
|
|
)}
|
|
</Toolbar>
|
|
</AppBar>
|
|
);
|
|
}
|
|
|
|
export default Navbar;
|