继续补齐个人中心弹窗底部插槽
扩展 PlatformProfileModalShell 透传共享 footer 插槽能力 将昵称修改弹窗改为使用标准 profile modal footer 补充 modal shell 与昵称弹窗的 footer 接法回归测试 更新 PlatformUiKit 收口计划与共享决策记录
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
- 2026-06-11 追加:`PlatformNavigableListItem` 继续扩展到 profile 设置行;`src/components/platform-entry/PlatformProfilePrimitives.tsx` 的 `ProfileSettingsRow` 已改成委托共享 `button + leading + trailing` 骨架,继续保留本地 `platform-profile-settings-row` class 承接分隔线、icon 胶囊和字号微调。后续 profile / 账户中心里的同类轻量导航行优先直接复用共享行骨架,不再回退成原生 `<button>` 手写布局。
|
||||
- 2026-06-11 追加:`PlatformAsyncStatePanel` 继续补齐 RPG 首页分类分支;移动端“发现 -> 分类”、桌面发现页“分类”和桌面首页“作品分类”模块现在都统一委托共享状态壳切换外层 `loading / empty / content`,分类控制条与排序按钮继续留在内容 slot 中。筛选后无结果的“当前筛选下没有作品。”也统一改成内层 `PlatformAsyncStatePanel` 切换,不再在三处 JSX 中各自维护嵌套 ternary。
|
||||
- 2026-06-11 追加:`PlatformDarkModalFooter` 不只收动作按钮区,也继续覆盖纯内容 footer;`CompanionCampModal.tsx` 底部“营地气氛”区域已改成 `layout="content"` + `padding="roomy"` 的共享 footer frame,保留原有文案和卡片布局,不再单独手写 `border-t border-white/10 px-5 py-4`。
|
||||
- 2026-06-11 追加:`PlatformProfileModalShell` 继续补齐标准 footer 插槽,直接透传 `UnifiedModal.footer` 与 `footerClassName`;`RpgEntryHomeView.tsx` 的昵称修改弹窗已改成标准 profile footer,不再把双按钮动作区手写在 body 末尾。后续个人中心里同类“表单内容 + 底部双按钮”弹窗优先走壳层 footer 接法。
|
||||
- 2026-06-09 追加:通用输入 Composer 的上传参考图、发送和移除参考图已迁移到 `PlatformIconButton`;图标上传仍使用 `asChild="label"` 保留 label + file input 语义,公共组件会自动写入隐藏文本,确保内嵌 file input 继承可访问名称。
|
||||
- 2026-06-10 追加:creation-agent composer 的上传文档 / 上传参考图入口使用 `PlatformIconButton` 默认 `platformIcon`;工作台只保留动态 label、title、busy 状态和 picker 回调,发送按钮继续保留主题色动作布局。验证命令:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformIconButton.test.tsx`。
|
||||
- 2026-06-10 追加:作品详情顶部返回 / 分享和封面轮播上一张 / 下一张入口使用 `PlatformIconButton variant="platformIcon"`;详情页保留原 `platform-work-detail__*` 局部 class 控制位置和尺寸,点赞、复制三态等专用动作暂不迁移。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformIconButton.test.tsx`。
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
19.3.13. RPG 首页个人中心的充值 / 钱包 / 每日任务 / 邀请 / 兑换码等商业与账户控制逻辑收口到 `src/components/platform-entry/usePlatformProfileCenterController.ts`;`RpgEntryHomeView` 仅保留个人中心展示、昵称头像编辑、扫码入口和页面级编排 / 交互,不再直接承接账户动作分流、商业状态派生和面板控制。该收口默认保持现有弹层与充值链路语义不变,避免在职责迁移时顺带扩张行为面。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`。
|
||||
19.3.14. RPG 首页个人中心的“玩过 / 可继续”历史弹层抽到 `src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx`;`RpgEntryHomeView` 不再内联 `SaveArchiveCard`、`ProfilePlayedWorksModal` 和旧的 `ProfileSaveArchivesModal`。当前真实产品语义已经把存档恢复并入“玩过”弹层的“可继续”分区,因此未连通的 `saveArchives` profile popup 分支一并删除,避免继续维护没有入口的独立壳层。组件级验证新增 `src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx`,并继续复用 `RpgEntryHomeView.recharge.test.tsx` 的个人中心集成断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`。
|
||||
19.3.15. 个人中心标准头部弹窗与白底副弹层壳层统一抽到 `src/components/platform-entry/PlatformProfileModalShell.tsx`;`PlatformProfileModalShell` 负责标准账户弹窗的 overlay、header、title、description、close variant 和 `closeOnBackdrop={false} / closeOnEscape={false}` 约束,`PlatformProfileSecondaryModalShell` 负责白底副弹层的 overlay、floating close、`bodyClassName="!p-0"` 和内容外壳。`RpgEntryHomeView` 内的昵称修改、账户充值、每日任务、兑换码、泥点账单与“玩过”弹层已接到共享壳层,页面不再重复手写个人中心弹窗的基础 chrome 与关闭策略。
|
||||
19.3.15.1. `PlatformProfileModalShell` 继续补齐标准 footer 插槽:壳层现已直接透传 `UnifiedModal.footer` 与 `footerClassName`,`RpgEntryHomeView.tsx` 的昵称修改弹窗不再把双按钮动作区塞在 body 末尾,而是改成标准 profile modal footer。后续个人中心里同类“表单 body + 底部双按钮动作区”弹窗,优先走 `PlatformProfileModalShell + footer`,不要把共享按钮再手写回内容区。验证命令:`npx vitest run src/components/platform-entry/PlatformProfileModalShell.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
19.3.16. RPG 首页个人中心的邀请好友 / 填邀请码 / 玩家社区三态弹层抽到 `src/components/platform-entry/PlatformProfileReferralModal.tsx`;组件统一复用 `PlatformProfileSecondaryModalShell` 承接居中白底浮层、floatingPlain 关闭按钮和成功 / 失败提示区,`RpgEntryHomeView` 不再内联邀请码规范化、社区二维码卡片和邀请用户头像行。组件级验证新增 `src/components/platform-entry/PlatformProfileReferralModal.test.tsx`,首页继续复用 `RpgEntryHomeView.recharge.test.tsx` 的邀请链路断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders invite panel with shared profile content|submits redeem panel with the shared form shell|renders community QR panels|profile community shortcut shows reward subtitle and invited users|invite query opens redeem modal directly for logged in users|profile redeem invite query modal submits code after login"`、`npm run typecheck`。
|
||||
19.3.17. RPG 首页个人中心的账户充值弹层抽到 `src/components/platform-entry/PlatformProfileRechargeModal.tsx`;组件承接 Native 二维码生成、点数 / 会员 tab、套餐卡片、空态和错误重试,继续复用 `PlatformProfileModalShell` 与平台白底卡片 token,`RpgEntryHomeView` 不再内联 `useWechatNativeQrCode`、`RechargeProductCard` 和 `ProfileRechargeModal`。组件级验证新增 `src/components/platform-entry/PlatformProfileRechargeModal.test.tsx`,首页继续复用 `RpgEntryHomeView.recharge.test.tsx` 的充值入口与 Native 二维码断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders point products and forwards buy action|shows empty state when the selected tab has no products|profile recharge modal shows native qr code on desktop web by default|create tab wallet chip opens recharge when recharge entry is enabled"`、`npm run typecheck`。
|
||||
19.3.18. RPG 首页个人中心的泥点账单、每日任务和兑换码三类标准 profile 弹层分别抽到 `src/components/platform-entry/PlatformProfileWalletLedgerModal.tsx`、`src/components/platform-entry/PlatformProfileTaskCenterModal.tsx` 与 `src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx`;账单继续复用 `PlatformProfileSecondaryModalShell`,任务和兑换码继续复用 `PlatformProfileModalShell`,页面不再内联账单余额 badge、任务领取列表和兑换码输入提交实现。三者均新增组件级测试,并继续复用 `RpgEntryHomeView.recharge.test.tsx` 的真实入口断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders ledger entries with shared balance presentation|retries from the shared error state|renders claimable tasks and forwards claim action|keeps incomplete tasks disabled|submits on button click and enter key|disables submit when the code is blank|opens wallet ledger modal from narrative coin card|profile daily task shortcut reflects task progress and claim updates|wallet ledger modal shows empty and error states|opens reward code modal from profile action on mobile|create tab wallet chip opens reward code when recharge entry is hidden"`、`npm run typecheck`。
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformProfileModalShell } from './PlatformProfileModalShell';
|
||||
|
||||
test('PlatformProfileModalShell forwards footer content into shared modal footer chrome', () => {
|
||||
render(
|
||||
<PlatformProfileModalShell
|
||||
title="修改昵称"
|
||||
onClose={vi.fn()}
|
||||
panelClassName="platform-remap-surface !max-w-sm rounded-[1.4rem]"
|
||||
bodyClassName="px-5 py-5"
|
||||
footerClassName="grid grid-cols-2 gap-3 px-5 pb-5 pt-0 sm:px-5"
|
||||
footer={
|
||||
<>
|
||||
<PlatformActionButton tone="secondary">取消</PlatformActionButton>
|
||||
<PlatformActionButton>保存</PlatformActionButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>昵称输入区</div>
|
||||
</PlatformProfileModalShell>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '修改昵称' });
|
||||
const body = screen.getByText('昵称输入区').parentElement;
|
||||
const footerButton = screen.getByRole('button', { name: '保存' });
|
||||
const footer = footerButton.closest('div');
|
||||
|
||||
expect(dialog).toBeTruthy();
|
||||
expect(body?.className).toContain('px-5');
|
||||
expect(body?.className).toContain('py-5');
|
||||
expect(footer?.className).toContain('border-t');
|
||||
expect(footer?.className).toContain('grid');
|
||||
expect(footer?.className).toContain('pt-0');
|
||||
});
|
||||
@@ -8,6 +8,7 @@ type PlatformProfileModalShellProps = {
|
||||
description?: ReactNode;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
closeLabel?: string;
|
||||
closeVariant?: 'profile' | 'profileCompact';
|
||||
closeDisabled?: boolean;
|
||||
@@ -18,6 +19,7 @@ type PlatformProfileModalShellProps = {
|
||||
panelClassName: string;
|
||||
bodyClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
footerClassName?: string;
|
||||
};
|
||||
|
||||
type PlatformProfileSecondaryModalShellProps = {
|
||||
@@ -56,6 +58,7 @@ export function PlatformProfileModalShell({
|
||||
description,
|
||||
onClose,
|
||||
children,
|
||||
footer,
|
||||
closeLabel,
|
||||
closeVariant = 'profile',
|
||||
closeDisabled = false,
|
||||
@@ -66,6 +69,7 @@ export function PlatformProfileModalShell({
|
||||
panelClassName,
|
||||
bodyClassName = 'px-5 py-5',
|
||||
descriptionClassName = PROFILE_MODAL_DESCRIPTION_CLASS,
|
||||
footerClassName,
|
||||
}: PlatformProfileModalShellProps) {
|
||||
return (
|
||||
<UnifiedModal
|
||||
@@ -89,6 +93,8 @@ export function PlatformProfileModalShell({
|
||||
titleClassName={PROFILE_MODAL_TITLE_CLASS}
|
||||
descriptionClassName={descriptionClassName}
|
||||
bodyClassName={bodyClassName}
|
||||
footer={footer}
|
||||
footerClassName={footerClassName}
|
||||
>
|
||||
{children}
|
||||
</UnifiedModal>
|
||||
|
||||
@@ -3159,9 +3159,12 @@ test('profile nickname modal uses platform text field and submits with Enter', a
|
||||
const nicknameInput = (await screen.findByLabelText(
|
||||
'新昵称',
|
||||
)) as HTMLInputElement;
|
||||
const footer = screen.getByRole('button', { name: '保存' }).closest('div');
|
||||
expect(nicknameInput.value).toBe('测试玩家');
|
||||
expect(nicknameInput.className).toContain('platform-text-field');
|
||||
expect(nicknameInput.className).toContain('platform-text-field--editor-dark');
|
||||
expect(footer?.className).toContain('border-t');
|
||||
expect(footer?.className).toContain('pt-0');
|
||||
|
||||
await user.clear(nicknameInput);
|
||||
await user.type(nicknameInput, '新昵称{Enter}');
|
||||
|
||||
@@ -2458,6 +2458,17 @@ function ProfileNicknameModal({
|
||||
closeVariant="profileCompact"
|
||||
panelClassName="platform-remap-surface !max-w-sm rounded-[1.4rem]"
|
||||
bodyClassName="px-5 py-5"
|
||||
footerClassName="grid grid-cols-2 gap-3 px-5 pb-5 pt-0 sm:px-5"
|
||||
footer={
|
||||
<>
|
||||
<PlatformActionButton tone="secondary" onClick={onClose}>
|
||||
取消
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton onClick={onSubmit} disabled={isSaving}>
|
||||
{isSaving ? '保存中' : '保存'}
|
||||
</PlatformActionButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<label className="block">
|
||||
<span className="sr-only">新昵称</span>
|
||||
@@ -2488,14 +2499,6 @@ function ProfileNicknameModal({
|
||||
{error}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||||
<PlatformActionButton tone="secondary" onClick={onClose}>
|
||||
取消
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton onClick={onSubmit} disabled={isSaving}>
|
||||
{isSaving ? '保存中' : '保存'}
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</PlatformProfileModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user