收口统一弹窗关闭按钮

新增 PlatformModalCloseButton pixel 变体承接像素风关闭入口

将 UnifiedModal 头部关闭按钮迁移到 PlatformModalCloseButton

补充平台态和像素态关闭按钮测试

更新 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
2026-06-10 13:19:55 +08:00
parent d5b3133c8d
commit e22cb1d06b
6 changed files with 50 additions and 15 deletions

View File

@@ -71,9 +71,20 @@ test('supports platform icon close button', () => {
const button = screen.getByRole('button', { name: '关闭素材图片' });
expect(button.className).toContain('platform-icon-button');
expect(button.className).toContain('disabled:opacity-45');
expect(button.querySelector('svg')).toBeTruthy();
});
test('supports pixel close button', () => {
render(<PlatformModalCloseButton label="关闭像素弹窗" variant="pixel" />);
const button = screen.getByRole('button', { name: '关闭像素弹窗' });
expect(button.className).toContain('bg-black/20');
expect(button.className).toContain('text-zinc-400');
expect(button.className).toContain('disabled:opacity-45');
});
test('supports editor dark close button', () => {
render(
<PlatformModalCloseButton label="关闭角色自定义" variant="editorDark" />,

View File

@@ -7,6 +7,7 @@ type PlatformModalCloseButtonVariant =
| 'floating'
| 'floatingPlain'
| 'platformIcon'
| 'pixel'
| 'editorDark';
type PlatformModalCloseButtonProps = Omit<
@@ -30,7 +31,9 @@ const PLATFORM_MODAL_CLOSE_BUTTON_CLASS_BY_VARIANT: Record<
'absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/80 text-[#ff4056] shadow-sm',
floatingPlain:
'absolute right-3 top-2 z-10 flex h-8 w-8 items-center justify-center rounded-full text-[#ff4056]',
platformIcon: 'platform-icon-button',
platformIcon: 'platform-icon-button disabled:cursor-not-allowed disabled:opacity-45',
pixel:
'rounded-full border border-white/10 bg-black/20 p-2 text-zinc-400 transition-colors hover:text-white disabled:cursor-not-allowed disabled:opacity-45',
editorDark:
'platform-modal-close-button--editor-dark rounded-full border border-white/10 bg-white/5 p-2 text-zinc-300 transition hover:bg-white/10 hover:text-white',
};

View File

@@ -14,6 +14,9 @@ test('renders an accessible platform modal', () => {
expect(screen.getByRole('dialog', { name: '统一弹窗' })).toBeTruthy();
expect(screen.getByText('窗口内容')).toBeTruthy();
expect(screen.getByRole('button', { name: '关闭' }).className).toContain(
'platform-icon-button',
);
});
test('closes through backdrop and escape', () => {
@@ -56,3 +59,25 @@ test('respects closeDisabled for every default close path', () => {
expect(onClose).not.toHaveBeenCalled();
});
test('uses shared pixel close button chrome', () => {
render(
<UnifiedModal
open
title="像素弹窗"
onClose={() => {}}
variant="pixel"
portal={false}
>
<div></div>
</UnifiedModal>,
);
const closeButton = screen.getByRole('button', { name: '关闭' });
expect(screen.getByRole('dialog', { name: '像素弹窗' }).className).toContain(
'pixel-modal-shell',
);
expect(closeButton.className).toContain('bg-black/20');
expect(closeButton.className).toContain('text-zinc-400');
});

View File

@@ -1,4 +1,3 @@
import { X } from 'lucide-react';
import {
type CSSProperties,
type ReactNode,
@@ -8,6 +7,7 @@ import {
import { createPortal } from 'react-dom';
import { getNineSliceStyle, UI_CHROME } from '../../uiAssets';
import { PlatformModalCloseButton } from './PlatformModalCloseButton';
type UnifiedModalVariant = 'platform' | 'pixel';
type UnifiedModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen';
@@ -145,10 +145,6 @@ function UnifiedModalContent({
? 'flex flex-wrap items-center justify-end gap-3 border-t border-white/10 px-4 py-3 sm:px-5 sm:py-4'
: 'flex flex-wrap items-center justify-end gap-3 border-t border-[var(--platform-subpanel-border)] px-4 py-4 sm:px-5';
const closeButtonClasses = isPixel
? 'rounded-full border border-white/10 bg-black/20 p-2 text-zinc-400 transition-colors hover:text-white disabled:cursor-not-allowed disabled:opacity-45'
: 'platform-icon-button disabled:cursor-not-allowed disabled:opacity-45';
return (
<div
className={joinClassNames(overlayClasses, zIndexClassName, overlayClassName)}
@@ -183,15 +179,12 @@ function UnifiedModalContent({
) : null}
</div>
{showCloseButton ? (
<button
type="button"
aria-label={closeLabel}
<PlatformModalCloseButton
label={closeLabel}
onClick={onClose}
disabled={closeDisabled}
className={closeButtonClasses}
>
<X className="h-4 w-4" />
</button>
variant={isPixel ? 'pixel' : 'platformIcon'}
/>
) : null}
</div>
<div className={joinClassNames(bodyClasses, bodyClassName)}>