Files
testing-tool/pages/Dashboard/ToolCard.tsx
T
LingandRX 373fc0496c Develop (#26)
统一简化多个页面里的CopyButton调用,删除不再需要的空回调参数

* feat: 添加clearCookies函数的单元测试并修复cookie域名处理逻辑

* feat: 增强 data URI 处理,支持带参数的前缀并更新相关测试

* fix: 修正 AGENTS.md 中 TypeScript 类型检查命令的描述

* feat: 添加 settings.local.json 文件以配置 Bash 权限

* perf: 预设背景色避免 Popup 弹窗白屏闪烁

* perf: 避免图标过早实例化,传递组件引用而非 JSX 节点

* feat: 添加 useLazyTranslation 和 preloadNamespaces 函数以支持动态加载 i18n 命名空间

* feat: 使用 useLazyTranslation 替换 useTranslation 以支持懒加载翻译

* feat: 添加 PageSkeleton 组件及其测试用例以支持页面加载骨架屏

* feat: 使用骨架屏替换加载状态指示器,优化用户体验

* feat: 优化 CopyButton 组件的复制功能,添加定时器管理复制状态

* feat: 调整 chunk 大小警告阈值以优化构建性能
2026-05-20 20:07:44 +08:00

163 lines
4.5 KiB
TypeScript

/**
* ToolCard 组件 - 工具卡片
*
* 用于在仪表盘中展示各个工具功能的卡片组件,支持图标、标题、描述、
* 快照内容展示,具备悬停动画效果。
*/
import { alpha, Box, Card, CardActionArea, Stack, Typography, useTheme } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import type { SvgIconProps } from '@mui/material/SvgIcon';
import type { ComponentType } from 'react';
import type { PaletteColorKey } from '@/config/features';
/**
* ToolCard 组件属性接口
*/
interface ToolCardProps {
/** 工具卡片标题 */
title: string;
/** 工具卡片描述文本(可选) */
description?: string;
/** 快照内容,用于在卡片底部展示额外信息(可选) */
snapshot?: React.ReactNode;
/** 主题色键,映射到 theme.palette[key].main */
colorKey: PaletteColorKey;
/** 图标组件引用 */
icon: ComponentType<SvgIconProps>;
/** 卡片点击事件处理函数 */
onClick: () => void;
}
/**
* ToolCard 组件
*
* @param props - ToolCardProps 属性对象
* @returns 工具卡片 JSX 元素
*/
export default function ToolCard({
title,
description,
snapshot,
colorKey,
icon: IconComponent,
onClick,
}: ToolCardProps) {
const theme = useTheme();
const colorCode = theme.palette[colorKey].main;
return (
<Card
elevation={0}
sx={{
position: 'relative',
borderRadius: 4,
border: '1px solid',
borderColor: 'divider',
height: '100%',
boxSizing: 'border-box',
transition:
'border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
borderColor: colorCode,
transform: 'translateY(-4px)',
boxShadow: `0 12px 24px -10px ${alpha(colorCode, 0.2)}`,
},
}}
>
<CardActionArea
onClick={onClick}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
justifyContent: 'flex-start',
p: 2.5,
gap: 1.5,
'&:hover .arrow-icon': {
transform: 'translateX(4px)',
color: colorCode,
},
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" width="100%">
<Stack direction="row" spacing={1.5} alignItems="center">
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 40,
height: 40,
borderRadius: 3,
bgcolor: alpha(colorCode, 0.07),
color: colorCode,
}}
>
<IconComponent sx={{ fontSize: 20 }} />
</Box>
<Box>
<Typography
variant="subtitle1"
sx={{
fontWeight: 700,
lineHeight: 1.2,
color: 'text.primary',
display: 'flex',
alignItems: 'center',
gap: 0.5,
}}
>
{title}
</Typography>
{description && (
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontWeight: 500,
mt: 0.5,
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
}}
>
{description}
</Typography>
)}
</Box>
</Stack>
<ArrowForwardIosIcon
className="arrow-icon"
sx={{
fontSize: 12,
color: 'text.disabled',
mt: 0.5,
transition: 'all 0.3s ease',
}}
/>
</Stack>
{snapshot != null && (
<Box
sx={{
mt: 'auto',
pt: 1.5,
borderTop: '1px dashed',
borderColor: 'divider',
width: '100%',
}}
>
{snapshot}
</Box>
)}
</CardActionArea>
</Card>
);
}
ToolCard.displayName = 'ToolCard';