继续收口图片面板工具弹窗

将 CreativeImageInputPanel 的预览与删除确认弹窗并回 UnifiedModal 体系
补充图片面板预览与遮罩关闭回归测试
更新 PlatformUiKit 收口计划与共享决策记录
This commit is contained in:
2026-06-11 05:26:14 +08:00
parent ebd958b5a0
commit ef6a2b7200
4 changed files with 165 additions and 111 deletions

View File

@@ -63,6 +63,7 @@
- 2026-06-11 追加:`PlatformDarkModalFooter` 继续从标准双按钮 footer 扩到 detail / confirm 收尾;`NpcModals.tsx` 的交易详情 footer 和 `MapModal.tsx` 的场景切换确认 footer 已改成复用同一个 dark footer frame即使只有单个“关闭”按钮也不再手写 `flex justify-end`。这条抽象继续只覆盖 dark / pixel modal 里的底部分隔线与常规动作区排布,不向白底 profile 弹窗 footer、sticky 工作台 footer 或运行态 HUD 工具条扩张。 - 2026-06-11 追加:`PlatformDarkModalFooter` 继续从标准双按钮 footer 扩到 detail / confirm 收尾;`NpcModals.tsx` 的交易详情 footer 和 `MapModal.tsx` 的场景切换确认 footer 已改成复用同一个 dark footer frame即使只有单个“关闭”按钮也不再手写 `flex justify-end`。这条抽象继续只覆盖 dark / pixel modal 里的底部分隔线与常规动作区排布,不向白底 profile 弹窗 footer、sticky 工作台 footer 或运行态 HUD 工具条扩张。
- 2026-06-11 追加:`PlatformFilterToolbar.tsx` 作为薄结构组件收口 RPG 首页分类工具条;组件只承接“筛选按钮 + tabs + 排序按钮”的排布与 `mobile / desktop` 两种布局差异,不持有筛选状态、空态或排序逻辑。后续只有在同构壳层真的复现时才继续往 `common` 扩覆盖面;如果只是单页内局部重复、接口会越抽越胖,就优先退回文件内 helper。 - 2026-06-11 追加:`PlatformFilterToolbar.tsx` 作为薄结构组件收口 RPG 首页分类工具条;组件只承接“筛选按钮 + tabs + 排序按钮”的排布与 `mobile / desktop` 两种布局差异,不持有筛选状态、空态或排序逻辑。后续只有在同构壳层真的复现时才继续往 `common` 扩覆盖面;如果只是单页内局部重复、接口会越抽越胖,就优先退回文件内 helper。
- 2026-06-11 追加:`SquareImageCropModal.tsx` 的白底弹窗壳层改为复用 `UnifiedModal.tsx`,同时给 `UnifiedModal` 薄补 `titleId``closeIcon` 透传,让裁剪弹窗继续保留自定义 close icon、无 backdrop / Escape 关闭和两列 footer而不把 `PlatformProfileModalShell` 这类带页面语义的壳层倒灌回 `common/`。这条规则适用于 `common` 级工具弹窗:先看 `UnifiedModal` 能不能承接,再决定是否需要新的薄壳。 - 2026-06-11 追加:`SquareImageCropModal.tsx` 的白底弹窗壳层改为复用 `UnifiedModal.tsx`,同时给 `UnifiedModal` 薄补 `titleId``closeIcon` 透传,让裁剪弹窗继续保留自定义 close icon、无 backdrop / Escape 关闭和两列 footer而不把 `PlatformProfileModalShell` 这类带页面语义的壳层倒灌回 `common/`。这条规则适用于 `common` 级工具弹窗:先看 `UnifiedModal` 能不能承接,再决定是否需要新的薄壳。
- 2026-06-11 追加:`CreativeImageInputPanel.tsx` 里参考图预览、主图预览和移除图片确认都继续并回 `UnifiedModal` 体系:两个预览弹窗直接复用 `UnifiedModal`,删除确认直接复用 `UnifiedConfirmDialog`,不再在图片面板里手写三段 `platform-modal-backdrop + platform-modal-shell`。当前没有新增 `PlatformImagePreviewModal`,因为这批差异还只在尺寸与文案层,继续组合已有 modal 原语的 leverage 更高。
- 2026-06-11 追加:`PlatformProfileModalShell` 继续补齐标准 footer 插槽,直接透传 `UnifiedModal.footer``footerClassName``RpgEntryHomeView.tsx` 的昵称修改弹窗已改成标准 profile footer不再把双按钮动作区手写在 body 末尾。后续个人中心里同类“表单内容 + 底部双按钮”弹窗优先走壳层 footer 接法。 - 2026-06-11 追加:`PlatformProfileModalShell` 继续补齐标准 footer 插槽,直接透传 `UnifiedModal.footer``footerClassName``RpgEntryHomeView.tsx` 的昵称修改弹窗已改成标准 profile footer不再把双按钮动作区手写在 body 末尾。后续个人中心里同类“表单内容 + 底部双按钮”弹窗优先走壳层 footer 接法。
- 2026-06-11 追加:`PlatformProfileModalShell` 的标准 footer 接法继续扩展到单 CTA 表单收尾;`PlatformProfileRewardCodeRedeemModal.tsx` 的兑换按钮已迁到壳层 footerbody 只保留输入和反馈消息。`PlatformAsyncStatePanel` 同日继续扩展到 `PlatformAssetPickerGrid``VisualNovelSavePanel.tsx``AccountModal.tsx` 的账号安全三个子区块;其中公共素材网格继续把 `error` banner 放在状态壳外层,保持错误提示可与加载态或内容并存的原语义。 - 2026-06-11 追加:`PlatformProfileModalShell` 的标准 footer 接法继续扩展到单 CTA 表单收尾;`PlatformProfileRewardCodeRedeemModal.tsx` 的兑换按钮已迁到壳层 footerbody 只保留输入和反馈消息。`PlatformAsyncStatePanel` 同日继续扩展到 `PlatformAssetPickerGrid``VisualNovelSavePanel.tsx``AccountModal.tsx` 的账号安全三个子区块;其中公共素材网格继续把 `error` banner 放在状态壳外层,保持错误提示可与加载态或内容并存的原语义。
- 2026-06-11 追加:按钮层继续补齐轻量漏网项。`PlatformTagEditor.tsx` 的标签 chip 删除入口已改成紧凑 `PlatformIconButton`,保留透明背景和原 chip 高度;`RpgEntryCharacterSelectView.tsx` 的两处“返回”按钮统一沉到局部 `CharacterSelectBackButton`,底层委托 `PlatformActionButton surface="editorDark"`。同日 `GenerationProgressHero.tsx` 新增 `GenerationHeaderBackButton``CustomWorldGenerationView.tsx``BarkBattleGeneratingView.tsx` 已开始复用这套暖色生成页返回入口骨架;后续同类轻量返回按钮与 chip 删除按钮优先继续沿共享按钮 + 薄包装的方向推进。 - 2026-06-11 追加:按钮层继续补齐轻量漏网项。`PlatformTagEditor.tsx` 的标签 chip 删除入口已改成紧凑 `PlatformIconButton`,保留透明背景和原 chip 高度;`RpgEntryCharacterSelectView.tsx` 的两处“返回”按钮统一沉到局部 `CharacterSelectBackButton`,底层委托 `PlatformActionButton surface="editorDark"`。同日 `GenerationProgressHero.tsx` 新增 `GenerationHeaderBackButton``CustomWorldGenerationView.tsx``BarkBattleGeneratingView.tsx` 已开始复用这套暖色生成页返回入口骨架;后续同类轻量返回按钮与 chip 删除按钮优先继续沿共享按钮 + 薄包装的方向推进。

View File

@@ -273,6 +273,7 @@
19.3.47. `PlatformDarkModalFooter` 继续从标准双按钮 footer 扩展到 detail / confirm 收尾:`NpcModals.tsx` 的交易详情单按钮 footer 与 `MapModal.tsx` 的场景切换确认 footer 已接入共享 dark footer frame分别保留“关闭”单 CTA 和“取消 / 确认前往”双 CTA 的业务语义、按钮 tone 与禁用态。后续 dark / pixel modal 里若只是标准底部分隔线 + 常规动作区排布,优先直接复用 `PlatformDarkModalFooter`,即使只有单个按钮也不再手写 `flex justify-end`;但像 `SquareImageCropModal.tsx` 这类白底弹窗 footer、sticky 工作台 footer 和运行态 HUD 工具条继续留在各自语义壳层,不强行混到 dark footer 抽象里。验证命令:`npx vitest run src/components/NpcModals.test.tsx src/components/MapModal.test.tsx``npm run typecheck``npm run check:encoding``git diff --check` 19.3.47. `PlatformDarkModalFooter` 继续从标准双按钮 footer 扩展到 detail / confirm 收尾:`NpcModals.tsx` 的交易详情单按钮 footer 与 `MapModal.tsx` 的场景切换确认 footer 已接入共享 dark footer frame分别保留“关闭”单 CTA 和“取消 / 确认前往”双 CTA 的业务语义、按钮 tone 与禁用态。后续 dark / pixel modal 里若只是标准底部分隔线 + 常规动作区排布,优先直接复用 `PlatformDarkModalFooter`,即使只有单个按钮也不再手写 `flex justify-end`;但像 `SquareImageCropModal.tsx` 这类白底弹窗 footer、sticky 工作台 footer 和运行态 HUD 工具条继续留在各自语义壳层,不强行混到 dark footer 抽象里。验证命令:`npx vitest run src/components/NpcModals.test.tsx src/components/MapModal.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`
19.3.48. `RpgEntryHomeView.tsx` 里的分类筛选工具条继续从页面内重复 JSX 收口到 `src/components/common/PlatformFilterToolbar.tsx`;该 Module 只承接“筛选按钮 + 横向 tabs + 排序按钮”的结构排布,暴露 `mobile / desktop` 两种 layout 以覆盖移动端 divider + 独立排序行和桌面端同排布局差异,但不持有分类列表、筛选状态、空态或排序逻辑。当前 RPG 首页分类区已接入,后续若其它白底列表页也出现同构的筛选壳层,可直接复用这套薄结构组件;若场景只是在单页内局部重复、接口会为了兼容业务差异不断膨胀,则优先退回文件内 helper不把 `common` 扩成假的“万能筛选条”。验证命令:`npx vitest run src/components/common/PlatformFilterToolbar.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding``git diff --check` 19.3.48. `RpgEntryHomeView.tsx` 里的分类筛选工具条继续从页面内重复 JSX 收口到 `src/components/common/PlatformFilterToolbar.tsx`;该 Module 只承接“筛选按钮 + 横向 tabs + 排序按钮”的结构排布,暴露 `mobile / desktop` 两种 layout 以覆盖移动端 divider + 独立排序行和桌面端同排布局差异,但不持有分类列表、筛选状态、空态或排序逻辑。当前 RPG 首页分类区已接入,后续若其它白底列表页也出现同构的筛选壳层,可直接复用这套薄结构组件;若场景只是在单页内局部重复、接口会为了兼容业务差异不断膨胀,则优先退回文件内 helper不把 `common` 扩成假的“万能筛选条”。验证命令:`npx vitest run src/components/common/PlatformFilterToolbar.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`
19.3.49. `SquareImageCropModal.tsx` 的白底 modal 壳层与 footer 已收口到 `src/components/common/UnifiedModal.tsx``UnifiedModal` 为此只薄补了 `titleId``closeIcon` 透传,继续由调用方决定 `closeOnBackdrop``closeOnEscape``portal`、header/footer 样式和按钮内容,不额外掺入 profile 业务语义,也不让 `common/` 反向依赖 `platform-entry/``SquareImageCropModal.tsx` 继续保留裁剪拖拽、pointer capture、保存禁用态与两列等宽 footer 行为,只把 header / body / footer 外壳交给共享 modal 承接。后续 `common` 级白底工具弹窗若只是标准标题栏 + 内容区 + footer 按钮排布,优先先看 `UnifiedModal` 是否够用,再决定是否需要新的薄壳;不要为了一个弹窗把 `PlatformProfileModalShell` 之类带页面语义的壳层倒灌回 `common`。验证命令:`npx vitest run src/components/common/SquareImageCropModal.test.tsx src/components/common/UnifiedModal.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding``git diff --check` 19.3.49. `SquareImageCropModal.tsx` 的白底 modal 壳层与 footer 已收口到 `src/components/common/UnifiedModal.tsx``UnifiedModal` 为此只薄补了 `titleId``closeIcon` 透传,继续由调用方决定 `closeOnBackdrop``closeOnEscape``portal`、header/footer 样式和按钮内容,不额外掺入 profile 业务语义,也不让 `common/` 反向依赖 `platform-entry/``SquareImageCropModal.tsx` 继续保留裁剪拖拽、pointer capture、保存禁用态与两列等宽 footer 行为,只把 header / body / footer 外壳交给共享 modal 承接。后续 `common` 级白底工具弹窗若只是标准标题栏 + 内容区 + footer 按钮排布,优先先看 `UnifiedModal` 是否够用,再决定是否需要新的薄壳;不要为了一个弹窗把 `PlatformProfileModalShell` 之类带页面语义的壳层倒灌回 `common`。验证命令:`npx vitest run src/components/common/SquareImageCropModal.test.tsx src/components/common/UnifiedModal.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`
19.3.50. `CreativeImageInputPanel.tsx` 里内嵌的 white tool modal 继续并回 `UnifiedModal` 体系:参考图预览与主图预览都改成直接复用 `src/components/common/UnifiedModal.tsx`,继续保留各自 `max-w` / `max-h` 节奏、点击遮罩关闭与紧凑 header移除图片确认改成复用 `src/components/common/UnifiedConfirmDialog.tsx`,不再在 panel 内手写 `platform-modal-backdrop + platform-modal-shell + 两列按钮`。这次没有新增 `PlatformImagePreviewModal`,因为当前预览弹窗差异还只在尺寸和文案层,继续直接组合 `UnifiedModal` 更深、更稳。后续 `common` 级图片面板若出现同类“预览大图 + 单标题栏 + 关闭按钮”弹窗,优先先复用 `UnifiedModal` 并把尺寸/文案留在调用方;只有当至少两到三个调用点开始重复同一套 preview body/header adapter 时,再考虑补新的薄壳。验证命令:`npx vitest run src/components/common/CreativeImageInputPanel.test.tsx src/components/common/UnifiedModal.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`
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 顶栏按钮壳。
19.4.1. 大鱼吃小鱼结果页的发布失败弹层迁移到 `src/components/common/PlatformStatusDialog.tsx``PlatformStatusDialog` 补充自定义图标、可访问标签和动作按钮样式透传后,`BigFishResultView` 不再保留 `BigFishResultErrorModal` 内联的 `UnifiedConfirmDialog + PlatformIconBadge` 组合。结果页只保留失败文案和关闭回调,发布失败的状态图标、遮罩、白底面板和“知道了”主动作统一由共享状态弹层承接。验证命令:`npm run test -- src/components/common/PlatformStatusDialog.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx``npm run typecheck` 19.4.1. 大鱼吃小鱼结果页的发布失败弹层迁移到 `src/components/common/PlatformStatusDialog.tsx``PlatformStatusDialog` 补充自定义图标、可访问标签和动作按钮样式透传后,`BigFishResultView` 不再保留 `BigFishResultErrorModal` 内联的 `UnifiedConfirmDialog + PlatformIconBadge` 组合。结果页只保留失败文案和关闭回调,发布失败的状态图标、遮罩、白底面板和“知道了”主动作统一由共享状态弹层承接。验证命令:`npm run test -- src/components/common/PlatformStatusDialog.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx``npm run typecheck`

View File

@@ -106,6 +106,7 @@ test('creative image input panel handles reference uploads and preview', () => {
expect.stringContaining('ref-1'), expect.stringContaining('ref-1'),
); );
fireEvent.click(screen.getByRole('button', { name: '关闭参考图预览' })); fireEvent.click(screen.getByRole('button', { name: '关闭参考图预览' }));
expect(screen.queryByRole('dialog', { name: '参考图 1' })).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '移除参考图 参考图 1' })); fireEvent.click(screen.getByRole('button', { name: '移除参考图 参考图 1' }));
expect(onPromptReferenceRemove).toHaveBeenCalledWith('ref-1'); expect(onPromptReferenceRemove).toHaveBeenCalledWith('ref-1');
@@ -255,6 +256,54 @@ test('creative image input panel confirms before removing uploaded image', () =>
expect(onMainImageRemove).toHaveBeenCalledTimes(1); expect(onMainImageRemove).toHaveBeenCalledTimes(1);
}); });
test('creative image input panel closes reference preview on backdrop click', () => {
render(
<CreativeImageInputPanel
uploadedImageSrc=""
uploadedImageAlt="拼图图片"
mainImageInputId="image-upload-input"
promptTextareaId="image-prompt-input"
prompt="旧街灯牌下的猫。"
promptLabel="画面描述"
aiRedraw
promptReferenceImages={[
{
id: 'ref-1',
label: '参考图 1',
imageSrc: 'data:image/png;base64,ref-1',
},
]}
imageModelPicker={null}
submitLabel="生成"
submitDisabled={false}
labels={{
imageField: '拼图画面',
uploadImage: '上传拼图图片',
replaceImage: '更换拼图图片',
emptyImageHint: '上传图片/填写画面描述',
removeImage: '移除拼图图片',
removeImageConfirmTitle: '移除拼图图片?',
removeImageConfirmBody: '移除后需要重新上传图片。',
promptReferenceUpload: '上传参考图',
promptReferencePreviewAlt: '参考图预览',
closePromptReferencePreview: '关闭参考图预览',
}}
onMainImageFileSelect={() => {}}
onMainImageRemove={() => {}}
onAiRedrawChange={() => {}}
onPromptChange={() => {}}
onPromptReferenceFilesSelect={() => {}}
onPromptReferenceRemove={() => {}}
onSubmit={() => {}}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '预览参考图 参考图 1' }));
const dialog = screen.getByRole('dialog', { name: '参考图 1' });
fireEvent.click(dialog.parentElement as HTMLElement);
expect(screen.queryByRole('dialog', { name: '参考图 1' })).toBeNull();
});
test('creative image input panel supports a preview-only main image mode', () => { test('creative image input panel supports a preview-only main image mode', () => {
const onSubmit = vi.fn(); const onSubmit = vi.fn();
@@ -356,6 +405,9 @@ test('creative image input panel can preview the main image and keep upload on a
2, 2,
); );
fireEvent.click(screen.getByRole('button', { name: '关闭关卡图片预览' })); fireEvent.click(screen.getByRole('button', { name: '关闭关卡图片预览' }));
expect(
screen.queryByRole('dialog', { name: '查看关卡图片' }),
).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '更换参考图' })); fireEvent.click(screen.getByRole('button', { name: '更换参考图' }));
expect(inputClickSpy).toHaveBeenCalledTimes(1); expect(inputClickSpy).toHaveBeenCalledTimes(1);
@@ -376,6 +428,49 @@ test('creative image input panel can preview the main image and keep upload on a
} }
}); });
test('creative image input panel closes main image preview on backdrop click', () => {
render(
<CreativeImageInputPanel
mainImageClickMode="preview"
uploadedImageSrc="/generated-puzzle-assets/session/level/image.png"
uploadedImageAlt="拼图关卡图"
mainImageInputId="level-image-upload-input"
promptTextareaId="level-prompt-input"
prompt="旧街灯牌下的猫。"
promptLabel="画面描述"
aiRedraw
promptReferenceImages={[]}
imageModelPicker={null}
submitLabel="重新生成画面"
submitDisabled={false}
labels={{
imageField: '画面图',
uploadImage: '上传参考图',
replaceImage: '更换参考图',
emptyImageHint: '上传图片/填写画面描述',
removeImage: '移除参考图',
removeImageConfirmTitle: '移除参考图?',
removeImageConfirmBody: '移除后可重新上传或选择历史图片。',
promptReferenceUpload: '上传参考图',
promptReferencePreviewAlt: '参考图预览',
closePromptReferencePreview: '关闭参考图预览',
previewMainImage: '查看关卡图片',
closeMainImagePreview: '关闭关卡图片预览',
}}
onMainImageFileSelect={() => {}}
onMainImageRemove={() => {}}
onAiRedrawChange={() => {}}
onPromptChange={() => {}}
onSubmit={() => {}}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '查看关卡图片' }));
const dialog = screen.getByRole('dialog', { name: '查看关卡图片' });
fireEvent.click(dialog.parentElement as HTMLElement);
expect(screen.queryByRole('dialog', { name: '查看关卡图片' })).toBeNull();
});
test('creative image input panel can hide upload and history controls independently', () => { test('creative image input panel can hide upload and history controls independently', () => {
render( render(
<CreativeImageInputPanel <CreativeImageInputPanel

View File

@@ -6,12 +6,13 @@ import { PlatformActionButton } from './PlatformActionButton';
import { PlatformFieldLabel } from './PlatformFieldLabel'; import { PlatformFieldLabel } from './PlatformFieldLabel';
import { PlatformIconBadge } from './PlatformIconBadge'; import { PlatformIconBadge } from './PlatformIconBadge';
import { PlatformIconButton } from './PlatformIconButton'; import { PlatformIconButton } from './PlatformIconButton';
import { PlatformModalCloseButton } from './PlatformModalCloseButton';
import { PlatformPillBadge } from './PlatformPillBadge'; import { PlatformPillBadge } from './PlatformPillBadge';
import { PlatformPillSwitch } from './PlatformPillSwitch'; import { PlatformPillSwitch } from './PlatformPillSwitch';
import { PlatformStatusMessage } from './PlatformStatusMessage'; import { PlatformStatusMessage } from './PlatformStatusMessage';
import { PlatformTextField } from './PlatformTextField'; import { PlatformTextField } from './PlatformTextField';
import { PlatformUploadPreviewCard } from './PlatformUploadPreviewCard'; import { PlatformUploadPreviewCard } from './PlatformUploadPreviewCard';
import { UnifiedConfirmDialog } from './UnifiedConfirmDialog';
import { UnifiedModal } from './UnifiedModal';
export type CreativeImageInputReferenceImage = { export type CreativeImageInputReferenceImage = {
id: string; id: string;
@@ -489,120 +490,76 @@ export function CreativeImageInputPanel({
</div> </div>
) : null} ) : null}
{previewReferenceImage ? ( <UnifiedModal
<div open={Boolean(previewReferenceImage)}
className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6" title={previewReferenceImage?.label ?? labels.promptReferencePreviewAlt}
onClick={() => setPreviewReferenceImage(null)} onClose={() => setPreviewReferenceImage(null)}
> closeLabel={labels.closePromptReferencePreview}
<div closeVariant="profileCompact"
role="dialog" size="lg"
aria-modal="true" zIndexClassName="z-[80]"
aria-labelledby="creative-image-reference-preview-title" overlayClassName="px-4 py-6"
className="platform-modal-shell platform-remap-surface w-full max-w-2xl rounded-[1.35rem] p-3 shadow-[0_24px_70px_rgba(15,23,42,0.22)]" panelClassName="platform-remap-surface rounded-[1.35rem] p-3 shadow-[0_24px_70px_rgba(15,23,42,0.22)]"
onClick={(event) => event.stopPropagation()} headerClassName="mb-3 items-center border-b-0 px-1 py-0"
> titleClassName="text-sm font-black"
<div className="mb-3 flex items-center justify-between gap-3 px-1"> bodyClassName="px-0 py-0"
<div >
id="creative-image-reference-preview-title" {previewReferenceImage ? (
className="min-w-0 truncate text-sm font-black text-[var(--platform-text-strong)]" <div className="max-h-[72vh] overflow-hidden rounded-[1rem] bg-black/5">
> <ResolvedAssetImage
{previewReferenceImage.label} src={previewReferenceImage.imageSrc}
</div> alt={labels.promptReferencePreviewAlt}
<PlatformModalCloseButton className="h-full max-h-[72vh] w-full object-contain"
label={labels.closePromptReferencePreview} />
variant="profileCompact"
onClick={() => setPreviewReferenceImage(null)}
className="shrink-0"
/>
</div>
<div className="max-h-[72vh] overflow-hidden rounded-[1rem] bg-black/5">
<ResolvedAssetImage
src={previewReferenceImage.imageSrc}
alt={labels.promptReferencePreviewAlt}
className="h-full max-h-[72vh] w-full object-contain"
/>
</div>
</div> </div>
</div> ) : null}
) : null} </UnifiedModal>
{isMainImagePreviewOpen && uploadedImageSrc ? ( <UnifiedModal
<div open={isMainImagePreviewOpen && Boolean(uploadedImageSrc)}
className="platform-modal-backdrop fixed inset-0 z-[82] flex items-center justify-center px-4 py-6" title={labels.previewMainImage ?? uploadedImageAlt}
onClick={() => setIsMainImagePreviewOpen(false)} onClose={() => setIsMainImagePreviewOpen(false)}
> closeLabel={
<div labels.closeMainImagePreview ?? labels.closePromptReferencePreview
role="dialog" }
aria-modal="true" closeVariant="profileCompact"
aria-labelledby="creative-image-main-preview-title" size="xl"
className="platform-modal-shell platform-remap-surface w-full max-w-4xl rounded-[1.35rem] p-3 shadow-[0_24px_70px_rgba(15,23,42,0.22)]" zIndexClassName="z-[82]"
onClick={(event) => event.stopPropagation()} overlayClassName="px-4 py-6"
> panelClassName="platform-remap-surface rounded-[1.35rem] p-3 shadow-[0_24px_70px_rgba(15,23,42,0.22)]"
<div className="mb-3 flex items-center justify-between gap-3 px-1"> headerClassName="mb-3 items-center border-b-0 px-1 py-0"
<div titleClassName="text-sm font-black"
id="creative-image-main-preview-title" bodyClassName="px-0 py-0"
className="min-w-0 truncate text-sm font-black text-[var(--platform-text-strong)]" >
> {uploadedImageSrc ? (
{labels.previewMainImage ?? uploadedImageAlt} <div className="max-h-[82vh] overflow-hidden rounded-[1rem] bg-black/5">
</div> <ResolvedAssetImage
<PlatformModalCloseButton src={uploadedImageSrc}
label={ refreshKey={uploadedImageRefreshKey}
labels.closeMainImagePreview ?? alt={uploadedImageAlt}
labels.closePromptReferencePreview className="h-full max-h-[82vh] w-full object-contain"
} />
variant="profileCompact"
onClick={() => setIsMainImagePreviewOpen(false)}
className="shrink-0"
/>
</div>
<div className="max-h-[82vh] overflow-hidden rounded-[1rem] bg-black/5">
<ResolvedAssetImage
src={uploadedImageSrc}
refreshKey={uploadedImageRefreshKey}
alt={uploadedImageAlt}
className="h-full max-h-[82vh] w-full object-contain"
/>
</div>
</div> </div>
</div> ) : null}
) : null} </UnifiedModal>
{isRemoveImageConfirmOpen ? ( <UnifiedConfirmDialog
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6"> open={isRemoveImageConfirmOpen}
<div title={labels.removeImageConfirmTitle}
role="dialog" description={labels.removeImageConfirmBody}
aria-modal="true" onClose={() => setIsRemoveImageConfirmOpen(false)}
aria-labelledby="creative-image-remove-confirm-title" confirmLabel="移除"
className="platform-modal-shell platform-remap-surface w-full max-w-xs rounded-[1.35rem] p-5 shadow-[0_24px_70px_rgba(15,23,42,0.22)]" cancelLabel="取消"
> showCancel
<div onConfirm={() => {
id="creative-image-remove-confirm-title" onMainImageRemove();
className="text-base font-black text-[var(--platform-text-strong)]" setIsRemoveImageConfirmOpen(false);
> }}
{labels.removeImageConfirmTitle} size="sm"
</div> zIndexClassName="z-[80]"
<div className="mt-2 text-sm leading-6 text-[var(--platform-text-base)]"> overlayClassName="px-4 py-6"
{labels.removeImageConfirmBody} panelClassName="platform-remap-surface max-w-xs rounded-[1.35rem] shadow-[0_24px_70px_rgba(15,23,42,0.22)]"
</div> />
<div className="mt-5 grid grid-cols-2 gap-3">
<PlatformActionButton
tone="secondary"
onClick={() => setIsRemoveImageConfirmOpen(false)}
>
</PlatformActionButton>
<PlatformActionButton
onClick={() => {
onMainImageRemove();
setIsRemoveImageConfirmOpen(false);
}}
>
</PlatformActionButton>
</div>
</div>
</div>
) : null}
</div> </div>
); );
} }