收口未保存离开确认弹窗

新增 PlatformUnsavedLeaveConfirmDialog 统一承接未保存离开确认壳层
迁移 RPG 创作编辑器的关闭未保存修改与退出未保存结果确认复用共享组件
补充共享组件测试并更新 PlatformUiKit 收口文档与决策记录
This commit is contained in:
2026-06-10 22:26:05 +08:00
parent 4ef805282d
commit 7005792580
5 changed files with 154 additions and 18 deletions

View File

@@ -0,0 +1,53 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import { expect, test, vi } from 'vitest';
import { PlatformUnsavedLeaveConfirmDialog } from './PlatformUnsavedLeaveConfirmDialog';
test('renders the platform variant with leave/continue actions', () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
render(
<PlatformUnsavedLeaveConfirmDialog
open
title="确认关闭"
onClose={onClose}
onConfirm={onConfirm}
>
</PlatformUnsavedLeaveConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '确认关闭' });
expect(within(dialog).getByText('当前修改尚未保存,确认关闭吗?')).toBeTruthy();
fireEvent.click(within(dialog).getByRole('button', { name: '继续编辑' }));
expect(onClose).toHaveBeenCalledTimes(1);
fireEvent.click(within(dialog).getByRole('button', { name: '确认关闭' }));
expect(onConfirm).toHaveBeenCalledTimes(1);
});
test('supports pixel variant with custom confirm label', () => {
render(
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
confirmLabel="仍然退出"
variant="pixel"
onClose={() => {}}
onConfirm={() => {}}
>
</PlatformUnsavedLeaveConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '确认退出' });
expect(dialog.className).toContain('pixel-modal-shell');
expect(within(dialog).getByRole('button', { name: '仍然退出' })).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '继续编辑' })).toBeTruthy();
});

View File

@@ -0,0 +1,92 @@
import type { ReactNode } from 'react';
import { UnifiedConfirmDialog } from './UnifiedConfirmDialog';
type PlatformUnsavedLeaveConfirmDialogProps = {
open: boolean;
title?: string;
children?: ReactNode;
onClose: () => void;
onConfirm: () => void;
confirmLabel?: string;
cancelLabel?: string;
showCloseButton?: boolean;
closeOnBackdrop?: boolean;
portal?: boolean;
size?: 'sm' | 'md';
variant?: 'platform' | 'pixel';
overlayClassName?: string;
panelClassName?: string;
};
function resolveUnsavedLeaveOverlayClassName(
variant: 'platform' | 'pixel',
overlayClassName?: string,
) {
if (overlayClassName) {
return overlayClassName;
}
return variant === 'pixel' ? 'z-[140]' : 'z-[140] !items-center';
}
function resolveUnsavedLeavePanelClassName(
variant: 'platform' | 'pixel',
panelClassName?: string,
) {
if (panelClassName) {
return panelClassName;
}
return variant === 'platform'
? 'platform-remap-surface rounded-[1.5rem]'
: undefined;
}
/**
* 平台未保存离开确认弹窗。
* 统一承接关闭 / 退出前的“继续编辑 + 确认离开”语义和默认平台壳层。
*/
export function PlatformUnsavedLeaveConfirmDialog({
open,
title = '确认退出',
children,
onClose,
onConfirm,
confirmLabel,
cancelLabel = '继续编辑',
showCloseButton = true,
closeOnBackdrop = true,
portal = true,
size = 'sm',
variant = 'platform',
overlayClassName,
panelClassName,
}: PlatformUnsavedLeaveConfirmDialogProps) {
return (
<UnifiedConfirmDialog
open={open}
title={title}
onClose={onClose}
onConfirm={onConfirm}
confirmLabel={confirmLabel ?? title}
cancelLabel={cancelLabel}
showCloseButton={showCloseButton}
closeOnBackdrop={closeOnBackdrop}
showCancel
portal={portal}
size={size}
variant={variant}
overlayClassName={resolveUnsavedLeaveOverlayClassName(
variant,
overlayClassName,
)}
panelClassName={resolveUnsavedLeavePanelClassName(
variant,
panelClassName,
)}
>
{children}
</UnifiedConfirmDialog>
);
}

View File

@@ -105,9 +105,9 @@ import { PlatformSlotBadge } from '../common/PlatformSlotBadge';
import { PlatformStatusDialog } from '../common/PlatformStatusDialog';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformUnsavedLeaveConfirmDialog } from '../common/PlatformUnsavedLeaveConfirmDialog';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
import { PlatformUploadTile } from '../common/PlatformUploadTile';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork';
import { CustomWorldNpcPortrait } from '../CustomWorldNpcVisualEditor';
import {
@@ -1471,19 +1471,15 @@ function CloseConfirmDialog({
confirmLabel?: string;
}) {
return (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认关闭"
onClose={onCancel}
confirmLabel={confirmLabel}
cancelLabel="继续编辑"
showCancel
onConfirm={onConfirm}
overlayClassName="z-[140] !items-center"
panelClassName="platform-remap-surface rounded-[1.5rem]"
>
{message}
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
);
}
@@ -3383,22 +3379,19 @@ function SceneImageGenerationModal({
</ModalShell>
{isExitConfirmOpen ? (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
onClose={() => setIsExitConfirmOpen(false)}
confirmLabel="仍然退出"
cancelLabel="继续编辑"
showCancel
onConfirm={() => {
setIsExitConfirmOpen(false);
onClose();
}}
variant="pixel"
overlayClassName="z-[140]"
>
退退
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
) : null}
</>
);
@@ -4029,22 +4022,18 @@ function CoverImageGenerationModal({
</ModalShell>
{isExitConfirmOpen ? (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
onClose={() => setIsExitConfirmOpen(false)}
overlayClassName="z-[140]"
panelClassName="platform-remap-surface rounded-[1.5rem]"
confirmLabel="确认退出"
cancelLabel="继续编辑"
showCancel
onConfirm={() => {
setIsExitConfirmOpen(false);
onClose();
}}
>
退
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
) : null}
</>
);