135 lines
4.1 KiB
TypeScript
135 lines
4.1 KiB
TypeScript
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>;
|
|
}
|