收口轻量支付弹窗与个人中心图标按钮
UnifiedModal 新增无头部模式并补齐对应可访问性测试 RpgEntryHomeView 的支付结果提示、支付确认遮罩与个人中心顶栏图标按钮改用共享组件 更新 PlatformUiKit 收口计划与 .hermes 决策记录
This commit is contained in:
@@ -63,6 +63,28 @@ test('supports disabling escape close while keeping the custom close button chro
|
||||
expect(screen.getByRole('dialog', { name: '个人中心弹窗' })).toBeTruthy();
|
||||
});
|
||||
|
||||
test('supports headerless dialogs while preserving the accessible name', () => {
|
||||
render(
|
||||
<UnifiedModal
|
||||
open
|
||||
title="支付成功"
|
||||
description="账户状态已刷新"
|
||||
onClose={() => {}}
|
||||
showHeader={false}
|
||||
showCloseButton={false}
|
||||
portal={false}
|
||||
>
|
||||
<div>窗口内容</div>
|
||||
</UnifiedModal>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '支付成功' });
|
||||
|
||||
expect(dialog).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '关闭' })).toBeNull();
|
||||
expect(screen.getByText('窗口内容')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('respects closeDisabled for every default close path', () => {
|
||||
const onClose = vi.fn();
|
||||
render(
|
||||
|
||||
@@ -25,6 +25,7 @@ type UnifiedModalProps = {
|
||||
onClose: () => void;
|
||||
variant?: UnifiedModalVariant;
|
||||
size?: UnifiedModalSize;
|
||||
showHeader?: boolean;
|
||||
closeDisabled?: boolean;
|
||||
closeOnBackdrop?: boolean;
|
||||
closeOnEscape?: boolean;
|
||||
@@ -86,6 +87,7 @@ function UnifiedModalContent({
|
||||
onClose,
|
||||
variant = 'platform',
|
||||
size = 'md',
|
||||
showHeader = true,
|
||||
closeDisabled = false,
|
||||
closeOnBackdrop = true,
|
||||
closeOnEscape = true,
|
||||
@@ -173,42 +175,51 @@ function UnifiedModalContent({
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={titleId}
|
||||
aria-labelledby={showHeader ? titleId : undefined}
|
||||
aria-label={showHeader ? undefined : title}
|
||||
aria-describedby={description ? descriptionId : undefined}
|
||||
className={joinClassNames(panelClasses, sizeClassName, panelClassName)}
|
||||
style={getPanelStyle(variant, panelStyle)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className={joinClassNames(headerClasses, headerClassName)}>
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
id={titleId}
|
||||
className={joinClassNames(titleClasses, titleClassName)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{description ? (
|
||||
{showHeader ? (
|
||||
<div className={joinClassNames(headerClasses, headerClassName)}>
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
id={descriptionId}
|
||||
className={joinClassNames(
|
||||
descriptionClasses,
|
||||
descriptionClassName,
|
||||
)}
|
||||
id={titleId}
|
||||
className={joinClassNames(titleClasses, titleClassName)}
|
||||
>
|
||||
{description}
|
||||
{title}
|
||||
</div>
|
||||
{description ? (
|
||||
<div
|
||||
id={descriptionId}
|
||||
className={joinClassNames(
|
||||
descriptionClasses,
|
||||
descriptionClassName,
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{showCloseButton ? (
|
||||
<PlatformModalCloseButton
|
||||
label={closeLabel}
|
||||
onClick={onClose}
|
||||
disabled={closeDisabled}
|
||||
variant={closeVariant ?? (isPixel ? 'pixel' : 'platformIcon')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{showCloseButton ? (
|
||||
<PlatformModalCloseButton
|
||||
label={closeLabel}
|
||||
onClick={onClose}
|
||||
disabled={closeDisabled}
|
||||
variant={closeVariant ?? (isPixel ? 'pixel' : 'platformIcon')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className={joinClassNames(bodyClasses, bodyClassName)}>
|
||||
{/* 无头部弹窗仍需要把描述挂到 aria-describedby,避免只剩可访问名称没有上下文。 */}
|
||||
{!showHeader && description ? (
|
||||
<div id={descriptionId} className="sr-only">
|
||||
{description}
|
||||
</div>
|
||||
) : null}
|
||||
{children}
|
||||
</div>
|
||||
{footer ? (
|
||||
|
||||
@@ -2903,12 +2903,20 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
|
||||
const topbar = container.querySelector('.platform-mobile-topbar');
|
||||
expect(topbar).toBeTruthy();
|
||||
expect(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '打开设置' }),
|
||||
).toBeTruthy();
|
||||
const scanButton = within(topbar as HTMLElement).getByRole('button', {
|
||||
name: '扫码',
|
||||
});
|
||||
const settingsButton = within(topbar as HTMLElement).getByRole('button', {
|
||||
name: '打开设置',
|
||||
});
|
||||
expect(scanButton).toBeTruthy();
|
||||
expect(settingsButton).toBeTruthy();
|
||||
expect(scanButton.className).toContain('platform-icon-button');
|
||||
expect(scanButton.className).toContain('platform-profile-header__icon-button');
|
||||
expect(settingsButton.className).toContain('platform-icon-button');
|
||||
expect(settingsButton.className).toContain(
|
||||
'platform-profile-header__icon-button',
|
||||
);
|
||||
expect(
|
||||
within(topbar as HTMLElement).queryByRole('button', {
|
||||
name: /充值/u,
|
||||
|
||||
@@ -3337,69 +3337,76 @@ function RechargePaymentResultModal({
|
||||
: 'text-[var(--platform-button-danger-text)]';
|
||||
|
||||
return (
|
||||
<div className="platform-modal-backdrop fixed inset-0 z-[90] flex items-center justify-center px-4 py-6">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="recharge-payment-result-title"
|
||||
className="platform-modal-shell platform-remap-surface w-full max-w-sm overflow-hidden rounded-[1.4rem]"
|
||||
>
|
||||
<div className="px-5 pb-5 pt-6 text-center">
|
||||
<PlatformIconBadge
|
||||
icon={<Icon className="h-8 w-8" aria-hidden="true" />}
|
||||
size="xl"
|
||||
tone="neutral"
|
||||
className={`mx-auto bg-white/10 ${iconClass}`}
|
||||
/>
|
||||
<div
|
||||
id="recharge-payment-result-title"
|
||||
className="mt-4 text-xl font-black text-[var(--platform-text-strong)]"
|
||||
>
|
||||
{result.title}
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-[var(--platform-text-soft)]">
|
||||
{result.message}
|
||||
</div>
|
||||
<PlatformActionButton
|
||||
surface="profile"
|
||||
fullWidth
|
||||
size="md"
|
||||
className="mt-5"
|
||||
onClick={onClose}
|
||||
>
|
||||
知道了
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
<UnifiedModal
|
||||
open
|
||||
title={result.title}
|
||||
onClose={onClose}
|
||||
showHeader={false}
|
||||
showCloseButton={false}
|
||||
closeOnBackdrop={false}
|
||||
closeOnEscape={false}
|
||||
portal={false}
|
||||
size="sm"
|
||||
zIndexClassName="z-[90]"
|
||||
overlayClassName={PROFILE_MODAL_OVERLAY_CLASS}
|
||||
panelClassName="platform-remap-surface !max-w-sm rounded-[1.4rem]"
|
||||
bodyClassName="px-5 pb-5 pt-6 text-center"
|
||||
>
|
||||
<PlatformIconBadge
|
||||
icon={<Icon className="h-8 w-8" aria-hidden="true" />}
|
||||
size="xl"
|
||||
tone="neutral"
|
||||
className={`mx-auto bg-white/10 ${iconClass}`}
|
||||
/>
|
||||
<div className="mt-4 text-xl font-black text-[var(--platform-text-strong)]">
|
||||
{result.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-[var(--platform-text-soft)]">
|
||||
{result.message}
|
||||
</div>
|
||||
<PlatformActionButton
|
||||
surface="profile"
|
||||
fullWidth
|
||||
size="md"
|
||||
className="mt-5"
|
||||
onClick={onClose}
|
||||
>
|
||||
知道了
|
||||
</PlatformActionButton>
|
||||
</UnifiedModal>
|
||||
);
|
||||
}
|
||||
|
||||
function RechargePaymentConfirmationMask({ orderId }: { orderId: string }) {
|
||||
return (
|
||||
<div className="platform-modal-backdrop fixed inset-0 z-[95] flex items-center justify-center px-4 py-6">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="正在确认支付"
|
||||
className="platform-modal-shell platform-remap-surface w-full max-w-sm overflow-hidden rounded-[1.4rem]"
|
||||
>
|
||||
<div className="px-5 pb-5 pt-6 text-center">
|
||||
<PlatformIconBadge
|
||||
icon={<Loader2 className="h-8 w-8 animate-spin" aria-hidden="true" />}
|
||||
size="xl"
|
||||
tone="neutral"
|
||||
className="mx-auto bg-white/10 text-[var(--platform-accent)]"
|
||||
/>
|
||||
<div className="mt-4 text-xl font-black text-[var(--platform-text-strong)]">
|
||||
正在确认支付
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-[var(--platform-text-soft)]">
|
||||
订单 {orderId} 正在同步到账状态,请先停留在当前页面。
|
||||
</div>
|
||||
</div>
|
||||
<UnifiedModal
|
||||
open
|
||||
title="正在确认支付"
|
||||
onClose={() => undefined}
|
||||
showHeader={false}
|
||||
showCloseButton={false}
|
||||
closeOnBackdrop={false}
|
||||
closeOnEscape={false}
|
||||
portal={false}
|
||||
size="sm"
|
||||
zIndexClassName="z-[95]"
|
||||
overlayClassName={PROFILE_MODAL_OVERLAY_CLASS}
|
||||
panelClassName="platform-remap-surface !max-w-sm rounded-[1.4rem]"
|
||||
bodyClassName="px-5 pb-5 pt-6 text-center"
|
||||
>
|
||||
<PlatformIconBadge
|
||||
icon={<Loader2 className="h-8 w-8 animate-spin" aria-hidden="true" />}
|
||||
size="xl"
|
||||
tone="neutral"
|
||||
className="mx-auto bg-white/10 text-[var(--platform-accent)]"
|
||||
/>
|
||||
<div className="mt-4 text-xl font-black text-[var(--platform-text-strong)]">
|
||||
正在确认支付
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-[var(--platform-text-soft)]">
|
||||
订单 {orderId} 正在同步到账状态,请先停留在当前页面。
|
||||
</div>
|
||||
</UnifiedModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7402,22 +7409,18 @@ export function RpgEntryHomeView({
|
||||
<RpgEntryBrandLogo />
|
||||
{isAuthenticated && activeTab === 'profile' ? (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformIconButton
|
||||
label="扫码"
|
||||
icon={<ScanLine className="h-5 w-5" />}
|
||||
onClick={openQrScannerPanel}
|
||||
className="platform-profile-header__icon-button"
|
||||
aria-label="扫码"
|
||||
>
|
||||
<ScanLine className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
/>
|
||||
<PlatformIconButton
|
||||
label="打开设置"
|
||||
icon={<Settings className="h-5 w-5" />}
|
||||
onClick={() => authUi?.openSettingsModal()}
|
||||
className="platform-profile-header__icon-button"
|
||||
aria-label="打开设置"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
) : isAuthenticated &&
|
||||
(activeTab === 'create' || activeTab === 'saves') ? (
|
||||
|
||||
Reference in New Issue
Block a user