feat: unify workspace dialogs

This commit is contained in:
lingniu
2026-07-19 08:32:48 +08:00
parent 49ae75937e
commit c3881bf25c
7 changed files with 619 additions and 31 deletions

View File

@@ -0,0 +1,134 @@
import { IconAlertTriangle, IconClose } from '@douyinfe/semi-icons';
import { Button, Modal, Tag } from '@douyinfe/semi-ui';
import { cloneElement, type ReactElement, type ReactNode } from 'react';
export type WorkspaceDialogBadgeColor = 'blue' | 'green' | 'grey' | 'orange' | 'red';
export type WorkspaceDialogSummaryItem = {
label: string;
value: ReactNode;
detail: string;
tone?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger';
};
export type WorkspaceDialogProps = {
visible: boolean;
ariaLabel: string;
closeLabel: string;
title: string;
description: string;
icon: ReactNode;
badge?: ReactNode;
badgeColor?: WorkspaceDialogBadgeColor;
width?: string | number;
className?: string;
closeOnEsc?: boolean;
maskClosable?: boolean;
closeDisabled?: boolean;
footer?: ReactNode;
children: ReactNode;
onCancel: () => void;
};
export function WorkspaceDialog({
visible,
ariaLabel,
closeLabel,
title,
description,
icon,
badge,
badgeColor = 'blue',
width = 480,
className = '',
closeOnEsc = true,
maskClosable = true,
closeDisabled = false,
footer,
children,
onCancel
}: WorkspaceDialogProps) {
return <Modal
className={`v2-workspace-dialog${className ? ` ${className}` : ''}`}
visible={visible}
centered
width={width}
aria-label={ariaLabel}
title={<div className="v2-workspace-dialog-title">
<span className="v2-workspace-dialog-title-icon" aria-hidden="true">{icon}</span>
<span><strong>{title}</strong><small>{description}</small></span>
{badge ? <Tag color={badgeColor} type="light" size="small">{badge}</Tag> : null}
<Button className="v2-workspace-dialog-close" theme="borderless" type="tertiary" size="small" icon={<IconClose />} aria-label={closeLabel} disabled={closeDisabled} onClick={onCancel} />
</div>}
closable={false}
closeOnEsc={closeOnEsc}
maskClosable={maskClosable}
onCancel={onCancel}
modalRender={(content) => cloneElement(content as ReactElement, { 'aria-label': ariaLabel, 'aria-labelledby': undefined })}
footer={footer ? <div className="v2-workspace-dialog-footer">{footer}</div> : null}
>
<div className="v2-workspace-dialog-content">{children}</div>
</Modal>;
}
export type WorkspaceConfirmDialogProps = {
visible: boolean;
ariaLabel: string;
title: string;
description: string;
summaryItems: WorkspaceDialogSummaryItem[];
confirmLabel: string;
cancelLabel: string;
note?: string;
pending?: boolean;
className?: string;
onConfirm: () => void;
onCancel: () => void;
};
export function WorkspaceConfirmDialog({
visible,
ariaLabel,
title,
description,
summaryItems,
confirmLabel,
cancelLabel,
note = '确认前请再次核对影响范围。',
pending = false,
className = '',
onConfirm,
onCancel
}: WorkspaceConfirmDialogProps) {
return <WorkspaceDialog
visible={visible}
ariaLabel={ariaLabel}
closeLabel={`关闭${ariaLabel}`}
className={`v2-workspace-confirm-dialog${className ? ` ${className}` : ''}`}
title={title}
description={description}
icon={<IconAlertTriangle />}
badge="需要确认"
badgeColor="orange"
closeOnEsc={!pending}
maskClosable={!pending}
closeDisabled={pending}
onCancel={onCancel}
footer={<>
<span className="v2-workspace-dialog-footer-note"><IconAlertTriangle aria-hidden="true" />{note}</span>
<span className="v2-workspace-dialog-footer-actions">
<Button type="tertiary" disabled={pending} onClick={onCancel}>{cancelLabel}</Button>
<Button theme="solid" type="danger" loading={pending} disabled={pending} onClick={onConfirm}>{confirmLabel}</Button>
</span>
</>}
>
<p className="v2-workspace-confirm-copy">{description}</p>
<div className="v2-workspace-dialog-summary" role="list" aria-label="操作影响摘要">
{summaryItems.map((item) => <span className={`is-${item.tone || 'neutral'}`} role="listitem" key={item.label}>
<small>{item.label}</small>
<strong>{item.value}</strong>
<em>{item.detail}</em>
</span>)}
</div>
</WorkspaceDialog>;
}