收口分类筛选与扫码弹窗壳层
RpgEntryHomeView 的分类筛选弹窗和扫码面板改用 UnifiedModal 补充扫码关闭清理与分类筛选关闭路径测试 更新 PlatformUiKit 收口计划与 .hermes 决策记录
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
- 2026-06-10 追加:`PlatformModalCloseButton variant="pixel"` 承接 `UnifiedModal variant="pixel"` 头部圆形关闭入口;`UnifiedModal` 只选择 `platformIcon / pixel` 变体并保留 closeDisabled、Backdrop、Escape 和 portal 语义,不再手写 X 图标、aria 和关闭按钮 class。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformModalCloseButton.test.tsx src/components/common/UnifiedConfirmDialog.test.tsx`。
|
- 2026-06-10 追加:`PlatformModalCloseButton variant="pixel"` 承接 `UnifiedModal variant="pixel"` 头部圆形关闭入口;`UnifiedModal` 只选择 `platformIcon / pixel` 变体并保留 closeDisabled、Backdrop、Escape 和 portal 语义,不再手写 X 图标、aria 和关闭按钮 class。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformModalCloseButton.test.tsx src/components/common/UnifiedConfirmDialog.test.tsx`。
|
||||||
- 2026-06-10 追加:`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口标准平台弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、原有标题层级和“不响应 Escape / backdrop”的交互语义;RPG 首页个人中心里的昵称修改、账户充值、每日任务和兑换码弹窗已迁移到 `UnifiedModal`,支付结果 / 支付确认遮罩 / 泥点账单这类头部结构不同的弹窗继续保留专用实现。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
- 2026-06-10 追加:`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口标准平台弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、原有标题层级和“不响应 Escape / backdrop”的交互语义;RPG 首页个人中心里的昵称修改、账户充值、每日任务和兑换码弹窗已迁移到 `UnifiedModal`,支付结果 / 支付确认遮罩 / 泥点账单这类头部结构不同的弹窗继续保留专用实现。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||||
- 2026-06-10 追加:`UnifiedModal` 新增 `showHeader`,用于收口不需要标准头部但仍要保留 dialog 无障碍语义、遮罩和层级控制的轻量弹窗;RPG 首页个人中心的支付结果提示与支付确认遮罩已迁移到 `showHeader={false}` 模式,业务页只保留 icon badge、文案与按钮,不再手写 backdrop、aria 和白底壳层。个人中心移动端顶栏“扫码”“打开设置”入口统一使用 `PlatformIconButton`,并继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置与主题色。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformIconButton.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
- 2026-06-10 追加:`UnifiedModal` 新增 `showHeader`,用于收口不需要标准头部但仍要保留 dialog 无障碍语义、遮罩和层级控制的轻量弹窗;RPG 首页个人中心的支付结果提示与支付确认遮罩已迁移到 `showHeader={false}` 模式,业务页只保留 icon badge、文案与按钮,不再手写 backdrop、aria 和白底壳层。个人中心移动端顶栏“扫码”“打开设置”入口统一使用 `PlatformIconButton`,并继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置与主题色。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformIconButton.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||||
|
- 2026-06-10 追加:RPG 首页发现页分类筛选弹窗和个人中心扫码面板改用 `UnifiedModal` 承接 backdrop、dialog 语义和层级;分类筛选保留本地选项 / 动作布局,扫码面板继续使用 `showHeader={false}` 保留深色自定义头部与摄像头 viewport,并显式维持 `closeOnBackdrop={false}`、`closeOnEscape={false}`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/common/UnifiedModal.test.tsx`。
|
||||||
- 2026-06-09 追加:RPG 大编辑器暗色面板内的保存和角色槽动作继续走本地 `ActionButton`,不再混用白底平台 `platform-button` class;平台白底动作收口和编辑器暗色动作收口保持两套视觉边界。
|
- 2026-06-09 追加:RPG 大编辑器暗色面板内的保存和角色槽动作继续走本地 `ActionButton`,不再混用白底平台 `platform-button` class;平台白底动作收口和编辑器暗色动作收口保持两套视觉边界。
|
||||||
- 2026-06-10 追加:`PlatformActionButton surface="editorDark"` 承接 RPG 暗色弹窗 / 运行面板里的普通取消、确认、刷新和编组动作,支持 `size="xxs"` 与 `tone="success" | "warning"`;`tone="accent"` 承接暗色壳层内的琥珀实心 CTA,`tone="accentSoft"` 承接依赖局部 accent 变量的柔和强调按钮。角色自定义 footer、自定义世界生成 footer、地图切换确认、营地编组普通动作和角色聊天刷新动作已迁移。暗色可选项卡仍使用 `PlatformDarkOptionCard`,像素风发送 / 强品牌动作继续保留专用布局。验证命令:`npm run test -- src/components/common/platformActionButtonModel.test.ts src/components/common/PlatformActionButton.test.tsx src/components/SelectionCustomizationModals.test.tsx src/components/CompanionCampModal.test.tsx src/components/MapModal.test.tsx src/components/CharacterChatModal.test.tsx`。
|
- 2026-06-10 追加:`PlatformActionButton surface="editorDark"` 承接 RPG 暗色弹窗 / 运行面板里的普通取消、确认、刷新和编组动作,支持 `size="xxs"` 与 `tone="success" | "warning"`;`tone="accent"` 承接暗色壳层内的琥珀实心 CTA,`tone="accentSoft"` 承接依赖局部 accent 变量的柔和强调按钮。角色自定义 footer、自定义世界生成 footer、地图切换确认、营地编组普通动作和角色聊天刷新动作已迁移。暗色可选项卡仍使用 `PlatformDarkOptionCard`,像素风发送 / 强品牌动作继续保留专用布局。验证命令:`npm run test -- src/components/common/platformActionButtonModel.test.ts src/components/common/PlatformActionButton.test.tsx src/components/SelectionCustomizationModals.test.tsx src/components/CompanionCampModal.test.tsx src/components/MapModal.test.tsx src/components/CharacterChatModal.test.tsx`。
|
||||||
- 2026-06-10 追加:RPG 首页创作 / 草稿顶栏的钱包快捷入口通过同文件 `TopbarWalletShortcutButton` 复用 `PlatformActionButton tone="accentSoft" shape="pill" size="xs"` 与 `PlatformIconBadge`;移动端 / 桌面端继续保留 `.platform-mobile-create-wallet-chip`、`.platform-desktop-create-wallet-chip` 和 `.platform-desktop-search` 兼容 class,承接余额截断、桌面顶栏胶囊壳和既有测试锚点,点击语义仍统一走 `openRechargeOrRewardCodeModal`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
- 2026-06-10 追加:RPG 首页创作 / 草稿顶栏的钱包快捷入口通过同文件 `TopbarWalletShortcutButton` 复用 `PlatformActionButton tone="accentSoft" shape="pill" size="xs"` 与 `PlatformIconBadge`;移动端 / 桌面端继续保留 `.platform-mobile-create-wallet-chip`、`.platform-desktop-create-wallet-chip` 和 `.platform-desktop-search` 兼容 class,承接余额截断、桌面顶栏胶囊壳和既有测试锚点,点击语义仍统一走 `openRechargeOrRewardCodeModal`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||||
|
|||||||
@@ -226,6 +226,7 @@
|
|||||||
19.3.2. 个人中心昵称修改、账户充值、每日任务和兑换码四类标准头部弹窗迁移到 `UnifiedModal`;`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、居中浮层布局和标题层级。上述弹窗统一通过 `closeOnBackdrop={false}`、`closeOnEscape={false}` 保持原有交互语义,不把 backdrop / Escape 关闭行为悄悄带进个人中心;泥点账单这类头部结构和 footer 语义明显不同的弹窗继续保留专用实现,等出现更多同构 case 再扩充 `UnifiedModal` 能力。
|
19.3.2. 个人中心昵称修改、账户充值、每日任务和兑换码四类标准头部弹窗迁移到 `UnifiedModal`;`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、居中浮层布局和标题层级。上述弹窗统一通过 `closeOnBackdrop={false}`、`closeOnEscape={false}` 保持原有交互语义,不把 backdrop / Escape 关闭行为悄悄带进个人中心;泥点账单这类头部结构和 footer 语义明显不同的弹窗继续保留专用实现,等出现更多同构 case 再扩充 `UnifiedModal` 能力。
|
||||||
19.3.3. 个人中心支付结果提示和支付确认遮罩迁移到 `UnifiedModal` 的 headerless 模式;`UnifiedModal` 新增 `showHeader`,用于在保留 `role="dialog"`、可访问名称、遮罩和 z-index 语义的同时,允许业务页自己排版 icon badge、轻量标题和正文。支付结果提示与确认遮罩统一使用 `showHeader={false}`、`showCloseButton={false}`、`closeOnBackdrop={false}`、`closeOnEscape={false}`,继续保持阻断式确认语义;业务页只保留图标、文案和按钮,不再手写 backdrop、dialog aria 和面板壳层。
|
19.3.3. 个人中心支付结果提示和支付确认遮罩迁移到 `UnifiedModal` 的 headerless 模式;`UnifiedModal` 新增 `showHeader`,用于在保留 `role="dialog"`、可访问名称、遮罩和 z-index 语义的同时,允许业务页自己排版 icon badge、轻量标题和正文。支付结果提示与确认遮罩统一使用 `showHeader={false}`、`showCloseButton={false}`、`closeOnBackdrop={false}`、`closeOnEscape={false}`,继续保持阻断式确认语义;业务页只保留图标、文案和按钮,不再手写 backdrop、dialog aria 和面板壳层。
|
||||||
19.3.4. 个人中心移动端顶栏的“扫码”“打开设置”入口迁移到 `PlatformIconButton`;页面继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置、尺寸和主题色,交互语义与可访问名称统一由共享按钮承接,不再在 `RpgEntryHomeView` 里手写图标按钮的 `type`、`aria-label` 和基础 chrome。
|
19.3.4. 个人中心移动端顶栏的“扫码”“打开设置”入口迁移到 `PlatformIconButton`;页面继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置、尺寸和主题色,交互语义与可访问名称统一由共享按钮承接,不再在 `RpgEntryHomeView` 里手写图标按钮的 `type`、`aria-label` 和基础 chrome。
|
||||||
|
19.3.5. 发现页分类筛选弹窗与个人中心扫码面板迁移到 `UnifiedModal`;分类筛选继续复用本地选项栅格和底部动作区样式,但 backdrop、dialog 语义、头部关闭入口和 `closeOnEscape={false}` 统一收口到共享壳层。扫码面板复用 `showHeader={false}` 模式保留深色自定义头部、摄像头 viewport 和状态提示,同时显式保持 `closeOnBackdrop={false}`、`closeOnEscape={false}`,确保不会把扫码中的资源清理语义改散到页面外层。
|
||||||
19.3. creative-agent 首页的侧边栏菜单、账号入口、开启新对话、我的创作、首页激励 CTA 和 prompt suggestion 按钮迁移到 `PlatformIconButton` / `PlatformActionButton`;首页继续保留 `creative-agent-home__*` 本地 class 承接透明顶栏、抽屉和品牌化胶囊视觉,不把视觉回收和语义收口绑成一次大改。`Beta` 徽标和历史记录纯文本行暂保留本地实现,等出现更多同构轻量列表行后再评估是否抽新的共享 row primitive。
|
19.3. creative-agent 首页的侧边栏菜单、账号入口、开启新对话、我的创作、首页激励 CTA 和 prompt suggestion 按钮迁移到 `PlatformIconButton` / `PlatformActionButton`;首页继续保留 `creative-agent-home__*` 本地 class 承接透明顶栏、抽屉和品牌化胶囊视觉,不把视觉回收和语义收口绑成一次大改。`Beta` 徽标和历史记录纯文本行暂保留本地实现,等出现更多同构轻量列表行后再评估是否抽新的共享 row primitive。
|
||||||
19.4. 大鱼吃小鱼结果页 hero 的返回入口迁移到 `PlatformIconButton variant="darkMini"`,测试 / 发布动作迁移到 `PlatformActionButton surface="editorDark"`;结果页只保留测试运行、发布提交和文案状态语义,不再手写 hero 顶栏按钮壳。
|
19.4. 大鱼吃小鱼结果页 hero 的返回入口迁移到 `PlatformIconButton variant="darkMini"`,测试 / 发布动作迁移到 `PlatformActionButton surface="editorDark"`;结果页只保留测试运行、发布提交和文案状态语义,不再手写 hero 顶栏按钮壳。
|
||||||
20. 平台方形上传入口和紧凑虚线新增入口迁移到 `PlatformUploadTile`,上传后的图片预览迁移到 `PlatformUploadPreviewCard`;反馈页上传凭证入口 / 预览、敲木鱼工作台新增功德词条入口、通用创作图片面板的提示词参考图缩略图、抓大鹅封面编辑参考图缩略图、通用输入 Composer 已选参考图条、creation-agent 已选参考图条和拼图结果页关卡引用图横条已先迁移。方形缩略图使用默认 `layout="square"`,横向“已选择参考图 / 文件名 / 素材名 / 移除”条使用 `layout="inline"`;只读引用图条不传 `onRemove`,避免公共组件额外渲染删除入口。后续继续收口结果页素材上传、工作台参考图上传、紧凑虚线新增入口等上传 / 动作块时,业务页只保留文件选择、预览数组、预览回调、删除回调、校验逻辑或新增回调,上传方块外观、主副文案、缩略图壳、预览按钮、标题行、横向已选条、移除按钮和禁用态统一由 Module 承接;工具栏中的小图标上传仍继续使用 `PlatformIconButton asChild="label"`。
|
20. 平台方形上传入口和紧凑虚线新增入口迁移到 `PlatformUploadTile`,上传后的图片预览迁移到 `PlatformUploadPreviewCard`;反馈页上传凭证入口 / 预览、敲木鱼工作台新增功德词条入口、通用创作图片面板的提示词参考图缩略图、抓大鹅封面编辑参考图缩略图、通用输入 Composer 已选参考图条、creation-agent 已选参考图条和拼图结果页关卡引用图横条已先迁移。方形缩略图使用默认 `layout="square"`,横向“已选择参考图 / 文件名 / 素材名 / 移除”条使用 `layout="inline"`;只读引用图条不传 `onRemove`,避免公共组件额外渲染删除入口。后续继续收口结果页素材上传、工作台参考图上传、紧凑虚线新增入口等上传 / 动作块时,业务页只保留文件选择、预览数组、预览回调、删除回调、校验逻辑或新增回调,上传方块外观、主副文案、缩略图壳、预览按钮、标题行、横向已选条、移除按钮和禁用态统一由 Module 承接;工具栏中的小图标上传仍继续使用 `PlatformIconButton asChild="label"`。
|
||||||
|
|||||||
@@ -3086,7 +3086,12 @@ test('profile scan action opens camera scanner instead of recharge panel', async
|
|||||||
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByRole('dialog', { name: '扫码' })).toBeTruthy();
|
const qrScannerDialog = await screen.findByRole('dialog', { name: '扫码' });
|
||||||
|
|
||||||
|
expect(qrScannerDialog).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
within(qrScannerDialog).getByRole('button', { name: '关闭扫码' }),
|
||||||
|
).toBeTruthy();
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getUserMedia).toHaveBeenCalledWith({
|
expect(getUserMedia).toHaveBeenCalledWith({
|
||||||
audio: false,
|
audio: false,
|
||||||
@@ -3094,6 +3099,14 @@ test('profile scan action opens camera scanner instead of recharge panel', async
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
expect(mockGetRpgProfileRechargeCenter).not.toHaveBeenCalled();
|
expect(mockGetRpgProfileRechargeCenter).not.toHaveBeenCalled();
|
||||||
|
expect(screen.queryByText('账户充值')).toBeNull();
|
||||||
|
|
||||||
|
await user.click(within(qrScannerDialog).getByRole('button', { name: '关闭扫码' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('dialog', { name: '扫码' })).toBeNull();
|
||||||
|
});
|
||||||
|
expect(stopTrack).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('desktop account entry uses saved avatar image when available', async () => {
|
test('desktop account entry uses saved avatar image when available', async () => {
|
||||||
@@ -5294,11 +5307,21 @@ test('mobile game category filter dialog filters by play type', async () => {
|
|||||||
const filterDialog = await screen.findByRole('dialog', {
|
const filterDialog = await screen.findByRole('dialog', {
|
||||||
name: '分类筛选',
|
name: '分类筛选',
|
||||||
});
|
});
|
||||||
|
const closeButton = within(filterDialog).getByRole('button', {
|
||||||
|
name: '关闭分类筛选',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(closeButton.className).toContain('platform-icon-button');
|
||||||
|
|
||||||
await user.click(within(filterDialog).getByRole('button', { name: '抓鹅' }));
|
await user.click(within(filterDialog).getByRole('button', { name: '抓鹅' }));
|
||||||
|
|
||||||
expect(screen.queryByRole('button', { name: /奇幻拼图,试玩/u })).toBeNull();
|
expect(screen.queryByRole('button', { name: /奇幻拼图,试玩/u })).toBeNull();
|
||||||
expect(screen.getByRole('button', { name: /奇幻抓鹅,进入/u })).toBeTruthy();
|
expect(screen.getByRole('button', { name: /奇幻抓鹅,进入/u })).toBeTruthy();
|
||||||
|
|
||||||
|
await user.click(closeButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('dialog', { name: '分类筛选' })).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bottom category tab becomes ranking and switches ranking metrics', async () => {
|
test('bottom category tab becomes ranking and switches ranking metrics', async () => {
|
||||||
|
|||||||
@@ -2326,99 +2326,85 @@ function PlatformCategoryFilterDialog({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="platform-modal-backdrop fixed inset-0 z-[90] flex items-end justify-center px-3 py-4 sm:items-center">
|
<UnifiedModal
|
||||||
<button
|
open
|
||||||
type="button"
|
title="分类筛选"
|
||||||
aria-label="关闭分类筛选"
|
description={`${resultCount} 个作品`}
|
||||||
className="absolute inset-0"
|
onClose={onClose}
|
||||||
onClick={onClose}
|
closeOnBackdrop
|
||||||
/>
|
closeOnEscape={false}
|
||||||
<div
|
closeLabel="关闭分类筛选"
|
||||||
role="dialog"
|
portal={false}
|
||||||
aria-modal="true"
|
size="sm"
|
||||||
aria-label="分类筛选"
|
zIndexClassName="z-[90]"
|
||||||
className="platform-modal-shell platform-category-filter-dialog relative w-full max-w-md overflow-hidden rounded-[1.35rem]"
|
overlayClassName="platform-modal-backdrop !px-3 !py-4"
|
||||||
>
|
panelClassName="platform-category-filter-dialog relative !rounded-[1.35rem]"
|
||||||
<div className="flex min-w-0 items-center justify-between gap-3 px-4 py-4">
|
headerClassName="border-b-0 px-4 py-4"
|
||||||
<div className="min-w-0">
|
titleClassName="text-base font-black"
|
||||||
<div className="text-base font-black text-[var(--platform-text-strong)]">
|
descriptionClassName="mt-0.5 text-xs font-semibold text-[var(--platform-text-soft)]"
|
||||||
分类筛选
|
bodyClassName="grid gap-4 !px-4 !pb-4 !pt-0 sm:!px-4 sm:!pb-4 sm:!pt-0"
|
||||||
</div>
|
>
|
||||||
<div className="mt-0.5 text-xs font-semibold text-[var(--platform-text-soft)]">
|
<div className="grid gap-2">
|
||||||
{resultCount} 个作品
|
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
||||||
</div>
|
玩法
|
||||||
</div>
|
|
||||||
<PlatformModalCloseButton
|
|
||||||
label="关闭"
|
|
||||||
onClick={onClose}
|
|
||||||
icon={<XCircle className="h-5 w-5" />}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="platform-category-filter-dialog__options">
|
||||||
|
{PLATFORM_CATEGORY_KIND_FILTERS.map((option) => {
|
||||||
|
const active = option.id === kindFilter;
|
||||||
|
|
||||||
<div className="grid gap-4 px-4 pb-4">
|
return (
|
||||||
<div className="grid gap-2">
|
<button
|
||||||
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
key={option.id}
|
||||||
玩法
|
type="button"
|
||||||
</div>
|
onClick={() => onKindFilterChange(option.id)}
|
||||||
<div className="platform-category-filter-dialog__options">
|
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
||||||
{PLATFORM_CATEGORY_KIND_FILTERS.map((option) => {
|
>
|
||||||
const active = option.id === kindFilter;
|
{option.label}
|
||||||
|
</button>
|
||||||
return (
|
);
|
||||||
<button
|
})}
|
||||||
key={option.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => onKindFilterChange(option.id)}
|
|
||||||
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
|
||||||
排序
|
|
||||||
</div>
|
|
||||||
<div className="platform-category-filter-dialog__options">
|
|
||||||
{PLATFORM_CATEGORY_SORT_OPTIONS.map((option) => {
|
|
||||||
const active = option.id === sortMode;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={option.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => onSortModeChange(option.id)}
|
|
||||||
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="platform-category-filter-dialog__actions">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onReset}
|
|
||||||
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--secondary"
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--primary"
|
|
||||||
>
|
|
||||||
完成
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
||||||
|
排序
|
||||||
|
</div>
|
||||||
|
<div className="platform-category-filter-dialog__options">
|
||||||
|
{PLATFORM_CATEGORY_SORT_OPTIONS.map((option) => {
|
||||||
|
const active = option.id === sortMode;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={option.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSortModeChange(option.id)}
|
||||||
|
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="platform-category-filter-dialog__actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onReset}
|
||||||
|
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--secondary"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--primary"
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</UnifiedModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3866,53 +3852,60 @@ function ProfileQrScannerModal({
|
|||||||
}, [onError, onResult]);
|
}, [onError, onResult]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<UnifiedModal
|
||||||
className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6"
|
open
|
||||||
role="dialog"
|
title="扫码"
|
||||||
aria-modal="true"
|
onClose={onClose}
|
||||||
aria-label="扫码"
|
showHeader={false}
|
||||||
|
showCloseButton={false}
|
||||||
|
closeOnBackdrop={false}
|
||||||
|
closeOnEscape={false}
|
||||||
|
portal={false}
|
||||||
|
size="sm"
|
||||||
|
zIndexClassName="z-[80]"
|
||||||
|
overlayClassName={PROFILE_MODAL_OVERLAY_CLASS}
|
||||||
|
panelClassName="platform-qr-scanner-modal !max-w-sm rounded-[1.4rem]"
|
||||||
|
bodyClassName="!p-0"
|
||||||
>
|
>
|
||||||
<div className="platform-qr-scanner-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
|
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
|
||||||
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
|
<div className="text-base font-black">扫码</div>
|
||||||
<div className="text-base font-black">扫码</div>
|
<PlatformModalCloseButton
|
||||||
<PlatformModalCloseButton
|
label="关闭扫码"
|
||||||
label="关闭扫码"
|
onClick={onClose}
|
||||||
onClick={onClose}
|
icon="×"
|
||||||
icon="×"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3 px-5 py-5">
|
|
||||||
<div className="platform-qr-scanner-modal__viewport">
|
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
className="h-full w-full object-cover"
|
|
||||||
playsInline
|
|
||||||
muted
|
|
||||||
/>
|
|
||||||
<span className="platform-qr-scanner-modal__frame" />
|
|
||||||
</div>
|
|
||||||
{result ? (
|
|
||||||
<PlatformStatusMessage
|
|
||||||
tone="success"
|
|
||||||
surface="profile"
|
|
||||||
size="xs"
|
|
||||||
className="rounded-2xl font-semibold"
|
|
||||||
>
|
|
||||||
已识别:{result}
|
|
||||||
</PlatformStatusMessage>
|
|
||||||
) : error ? (
|
|
||||||
<PlatformStatusMessage
|
|
||||||
tone="error"
|
|
||||||
surface="profile"
|
|
||||||
size="xs"
|
|
||||||
className="rounded-2xl font-semibold"
|
|
||||||
>
|
|
||||||
{error}
|
|
||||||
</PlatformStatusMessage>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-3 px-5 py-5">
|
||||||
|
<div className="platform-qr-scanner-modal__viewport">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
playsInline
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
<span className="platform-qr-scanner-modal__frame" />
|
||||||
|
</div>
|
||||||
|
{result ? (
|
||||||
|
<PlatformStatusMessage
|
||||||
|
tone="success"
|
||||||
|
surface="profile"
|
||||||
|
size="xs"
|
||||||
|
className="rounded-2xl font-semibold"
|
||||||
|
>
|
||||||
|
已识别:{result}
|
||||||
|
</PlatformStatusMessage>
|
||||||
|
) : error ? (
|
||||||
|
<PlatformStatusMessage
|
||||||
|
tone="error"
|
||||||
|
surface="profile"
|
||||||
|
size="xs"
|
||||||
|
className="rounded-2xl font-semibold"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</PlatformStatusMessage>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</UnifiedModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user