fix(pageHeader): 使用 MUI 主题色替代硬编码颜色,支持暗色模式
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { alpha, Box, Stack, SxProps, Theme, Typography } from '@mui/material';
|
import { alpha, Box, Stack, SxProps, Theme, Typography, useTheme } from '@mui/material';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,7 +7,7 @@ import { ReactNode } from 'react';
|
|||||||
export interface PageHeaderProps {
|
export interface PageHeaderProps {
|
||||||
/** 要显示的图标组件 */
|
/** 要显示的图标组件 */
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
/** 图标的颜色,默认为 '#1976d2'(蓝色) */
|
/** 图标的颜色,默认使用主题 primary.main 色 */
|
||||||
iconColor?: string;
|
iconColor?: string;
|
||||||
/** 主标题文本 */
|
/** 主标题文本 */
|
||||||
title: string;
|
title: string;
|
||||||
@@ -53,7 +53,7 @@ export interface PageHeaderProps {
|
|||||||
*/
|
*/
|
||||||
export default function PageHeader({
|
export default function PageHeader({
|
||||||
icon,
|
icon,
|
||||||
iconColor = '#1976d2',
|
iconColor,
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
badge,
|
badge,
|
||||||
@@ -62,6 +62,9 @@ export default function PageHeader({
|
|||||||
subtitleSx,
|
subtitleSx,
|
||||||
sx,
|
sx,
|
||||||
}: PageHeaderProps) {
|
}: PageHeaderProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const resolvedIconColor = iconColor ?? theme.palette.primary.main;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" spacing={1.5} alignItems="center" sx={{ mb: 2.5, ...sx }}>
|
<Stack direction="row" spacing={1.5} alignItems="center" sx={{ mb: 2.5, ...sx }}>
|
||||||
{/* 图标容器 */}
|
{/* 图标容器 */}
|
||||||
@@ -69,8 +72,8 @@ export default function PageHeader({
|
|||||||
sx={{
|
sx={{
|
||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 2.5,
|
borderRadius: 2.5,
|
||||||
bgcolor: alpha(iconColor, 0.1),
|
bgcolor: alpha(resolvedIconColor, 0.1),
|
||||||
color: iconColor,
|
color: resolvedIconColor,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
...iconSx,
|
...iconSx,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import PageHeader, { type PageHeaderProps } from '@/components/PageHeader';
|
import PageHeader, { type PageHeaderProps } from '@/components/PageHeader';
|
||||||
|
|
||||||
|
const theme = createTheme();
|
||||||
|
|
||||||
|
function renderWithTheme(ui: React.ReactElement) {
|
||||||
|
return render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>);
|
||||||
|
}
|
||||||
|
|
||||||
describe('PageHeader 组件系统', () => {
|
describe('PageHeader 组件系统', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -22,32 +29,37 @@ describe('PageHeader 组件系统', () => {
|
|||||||
|
|
||||||
describe('PageHeader UI 渲染', () => {
|
describe('PageHeader UI 渲染', () => {
|
||||||
it('应渲染页面标题栏&副标题', () => {
|
it('应渲染页面标题栏&副标题', () => {
|
||||||
render(<PageHeader {...defaultProps} />);
|
renderWithTheme(<PageHeader {...defaultProps} />);
|
||||||
expect(screen.getByText('时间戳转换')).toBeInTheDocument();
|
expect(screen.getByText('时间戳转换')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Unix 毫秒数转换与格式化')).toBeInTheDocument();
|
expect(screen.getByText('Unix 毫秒数转换与格式化')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应渲染图标', () => {
|
it('应渲染图标', () => {
|
||||||
render(<PageHeader {...defaultProps} />);
|
renderWithTheme(<PageHeader {...defaultProps} />);
|
||||||
expect(screen.getByTestId('AccessTimeIcon')).toBeInTheDocument();
|
expect(screen.getByTestId('AccessTimeIcon')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应渲染自定义图标&图标颜色', () => {
|
it('应渲染自定义图标&图标颜色', () => {
|
||||||
render(<PageHeader {...defaultProps} icon={<CloseIcon />} iconColor="#FF0000" />);
|
renderWithTheme(<PageHeader {...defaultProps} icon={<CloseIcon />} iconColor="#FF0000" />);
|
||||||
expect(screen.getByTestId('CloseIcon')).toBeInTheDocument();
|
expect(screen.getByTestId('CloseIcon')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('CloseIcon')).toHaveStyle('color: #FF0000;');
|
expect(screen.getByTestId('CloseIcon')).toHaveStyle('color: #FF0000;');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('应默认使用主题 primary 色', () => {
|
||||||
|
renderWithTheme(<PageHeader {...defaultProps} icon={<CloseIcon />} />);
|
||||||
|
expect(screen.getByTestId('CloseIcon')).toHaveStyle(`color: ${theme.palette.primary.main};`);
|
||||||
|
});
|
||||||
|
|
||||||
it('应渲染 badge 组件', () => {
|
it('应渲染 badge 组件', () => {
|
||||||
const badge = <span data-testid="test-badge">New</span>;
|
const badge = <span data-testid="test-badge">New</span>;
|
||||||
render(<PageHeader {...defaultProps} badge={badge} />);
|
renderWithTheme(<PageHeader {...defaultProps} badge={badge} />);
|
||||||
expect(screen.getByTestId('test-badge')).toBeInTheDocument();
|
expect(screen.getByTestId('test-badge')).toBeInTheDocument();
|
||||||
expect(screen.getByText('New')).toBeInTheDocument();
|
expect(screen.getByText('New')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('应渲染 badge 与 title 并排布局', () => {
|
it('应渲染 badge 与 title 并排布局', () => {
|
||||||
const badge = <span data-testid="side-badge">v1.0</span>;
|
const badge = <span data-testid="side-badge">v1.0</span>;
|
||||||
render(<PageHeader {...defaultProps} badge={badge} />);
|
renderWithTheme(<PageHeader {...defaultProps} badge={badge} />);
|
||||||
const title = screen.getByText('时间戳转换');
|
const title = screen.getByText('时间戳转换');
|
||||||
const badgeEl = screen.getByTestId('side-badge');
|
const badgeEl = screen.getByTestId('side-badge');
|
||||||
expect(title).toBeInTheDocument();
|
expect(title).toBeInTheDocument();
|
||||||
@@ -57,13 +69,15 @@ describe('PageHeader 组件系统', () => {
|
|||||||
|
|
||||||
describe('PageHeader 条件渲染', () => {
|
describe('PageHeader 条件渲染', () => {
|
||||||
it('subtitle 为 undefined 时不应渲染副标题', () => {
|
it('subtitle 为 undefined 时不应渲染副标题', () => {
|
||||||
const { container } = render(<PageHeader icon={<AccessTimeIcon />} title="仅标题" />);
|
const { container } = renderWithTheme(
|
||||||
|
<PageHeader icon={<AccessTimeIcon />} title="仅标题" />,
|
||||||
|
);
|
||||||
const captionElements = container.querySelectorAll('p');
|
const captionElements = container.querySelectorAll('p');
|
||||||
expect(captionElements.length).toBe(0);
|
expect(captionElements.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subtitle 为空字符串时不应渲染副标题', () => {
|
it('subtitle 为空字符串时不应渲染副标题', () => {
|
||||||
const { container } = render(
|
const { container } = renderWithTheme(
|
||||||
<PageHeader icon={<AccessTimeIcon />} title="标题" subtitle="" />,
|
<PageHeader icon={<AccessTimeIcon />} title="标题" subtitle="" />,
|
||||||
);
|
);
|
||||||
const captionElements = container.querySelectorAll('p');
|
const captionElements = container.querySelectorAll('p');
|
||||||
@@ -71,14 +85,14 @@ describe('PageHeader 组件系统', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('badge 为 undefined 时不应渲染 badge 区域', () => {
|
it('badge 为 undefined 时不应渲染 badge 区域', () => {
|
||||||
render(<PageHeader {...defaultProps} />);
|
renderWithTheme(<PageHeader {...defaultProps} />);
|
||||||
expect(screen.queryByText('v1.0')).not.toBeInTheDocument();
|
expect(screen.queryByText('v1.0')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PageHeader 样式扩展', () => {
|
describe('PageHeader 样式扩展', () => {
|
||||||
it('iconSx 应作为属性传递给图标容器', () => {
|
it('iconSx 应作为属性传递给图标容器', () => {
|
||||||
const { container } = render(
|
const { container } = renderWithTheme(
|
||||||
<PageHeader {...defaultProps} iconSx={{ border: '2px solid red' }} />,
|
<PageHeader {...defaultProps} iconSx={{ border: '2px solid red' }} />,
|
||||||
);
|
);
|
||||||
const iconContainer = container.querySelector('div');
|
const iconContainer = container.querySelector('div');
|
||||||
@@ -86,19 +100,19 @@ describe('PageHeader 组件系统', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('titleSx 应作为属性传递给标题', () => {
|
it('titleSx 应作为属性传递给标题', () => {
|
||||||
render(<PageHeader {...defaultProps} titleSx={{ fontWeight: 'bold' }} />);
|
renderWithTheme(<PageHeader {...defaultProps} titleSx={{ fontWeight: 'bold' }} />);
|
||||||
const titleEl = screen.getByText('时间戳转换');
|
const titleEl = screen.getByText('时间戳转换');
|
||||||
expect(titleEl).toBeInTheDocument();
|
expect(titleEl).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subtitleSx 应作为属性传递给副标题', () => {
|
it('subtitleSx 应作为属性传递给副标题', () => {
|
||||||
render(<PageHeader {...defaultProps} subtitleSx={{ color: 'red' }} />);
|
renderWithTheme(<PageHeader {...defaultProps} subtitleSx={{ color: 'red' }} />);
|
||||||
const subtitleEl = screen.getByText('Unix 毫秒数转换与格式化');
|
const subtitleEl = screen.getByText('Unix 毫秒数转换与格式化');
|
||||||
expect(subtitleEl).toBeInTheDocument();
|
expect(subtitleEl).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sx 应作为属性传递给外层容器', () => {
|
it('sx 应作为属性传递给外层容器', () => {
|
||||||
const { container } = render(<PageHeader {...defaultProps} sx={{ mt: 3 }} />);
|
const { container } = renderWithTheme(<PageHeader {...defaultProps} sx={{ mt: 3 }} />);
|
||||||
const outerElement = container.firstChild;
|
const outerElement = container.firstChild;
|
||||||
expect(outerElement).toBeTruthy();
|
expect(outerElement).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user