unify Semi UI workspace command hierarchy

This commit is contained in:
lingniu
2026-07-18 03:36:18 +08:00
parent e39140c408
commit 3cebfe1982
12 changed files with 372 additions and 79 deletions

View File

@@ -1,33 +0,0 @@
import { Tag, Typography } from '@douyinfe/semi-ui';
import type { ReactNode } from 'react';
const { Title, Text } = Typography;
export function PageHeader({
title,
description,
status,
statusColor = 'blue',
meta,
actions
}: {
title: string;
description: string;
status?: string;
statusColor?: React.ComponentProps<typeof Tag>['color'];
meta?: ReactNode;
actions?: ReactNode;
}) {
return (
<header className="v2-page-heading">
<div className="v2-page-heading-copy">
<div>
<Title heading={2}>{title}</Title>
{status ? <Tag color={statusColor} size="small">{status}</Tag> : null}
</div>
<Text type="secondary">{description}</Text>
</div>
{meta || actions ? <div className="v2-page-heading-aside">{meta}{actions}</div> : null}
</header>
);
}

View File

@@ -0,0 +1,22 @@
import { render, screen } from '@testing-library/react';
import { Button, Typography } from '@douyinfe/semi-ui';
import { expect, test } from 'vitest';
import { WorkspaceCommandBar } from './WorkspaceCommandBar';
test('renders a compact Semi workspace command hierarchy', () => {
const view = render(<WorkspaceCommandBar
className="custom-command"
ariaLabel="测试工作区操作"
title="测试工作区"
description="说明当前任务和数据范围"
status="12 条"
meta={<Typography.Text type="tertiary"></Typography.Text>}
actions={<Button theme="solid"></Button>}
/>);
expect(screen.getByRole('heading', { name: '测试工作区', level: 5 })).toBeInTheDocument();
expect(screen.getByLabelText('测试工作区操作')).toHaveClass('v2-workspace-command-bar', 'custom-command');
expect(screen.getByText('12 条').closest('.semi-tag')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '执行' })).toHaveClass('semi-button');
expect(view.container.querySelector('.v2-workspace-command-copy')).toHaveTextContent('说明当前任务和数据范围');
});

View File

@@ -0,0 +1,40 @@
import { Tag, Typography } from '@douyinfe/semi-ui';
import type { ReactNode } from 'react';
const { Text, Title } = Typography;
export function WorkspaceCommandBar({
title,
description,
status,
statusColor = 'blue',
meta,
actions,
className = '',
ariaLabel
}: {
title: ReactNode;
description: ReactNode;
status?: string;
statusColor?: React.ComponentProps<typeof Tag>['color'];
meta?: ReactNode;
actions?: ReactNode;
className?: string;
ariaLabel?: string;
}) {
const rootClassName = ['v2-workspace-command-bar', className].filter(Boolean).join(' ');
return (
<header className={rootClassName} aria-label={ariaLabel}>
<div className="v2-workspace-command-copy">
<Title heading={5}>{title}</Title>
<Text type="tertiary">{description}</Text>
</div>
{status || meta || actions ? <div className="v2-workspace-command-actions">
{status ? <Tag color={statusColor} type="light" size="small">{status}</Tag> : null}
{meta ? <span className="v2-workspace-command-meta">{meta}</span> : null}
{actions}
</div> : null}
</header>
);
}