继续收口方洞图片槽弹窗壳层
方洞结果页图片槽弹窗复用 PlatformToolModalShell 保留上传、AI生成和历史素材选择业务语义 补充组件测试断言共享白底工具弹窗壳层 同步 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
@@ -297,6 +297,9 @@ test('square hole image dialog reuses platform media frame', async () => {
|
||||
const mediaFrame = image.closest('div.relative');
|
||||
|
||||
expect(dialog).toBeTruthy();
|
||||
expect(dialog.className).toContain('platform-remap-surface');
|
||||
expect(dialog.className).toContain('shadow-[0_24px_80px_rgba(0,0,0,0.55)]');
|
||||
expect(screen.getByText('历史生成')).toBeTruthy();
|
||||
expect(mediaFrame?.className).toContain('aspect-[4/3]');
|
||||
expect(mediaFrame?.className).toContain('rounded-[1.35rem]');
|
||||
expect(mediaFrame?.className).toContain(
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { type ChangeEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import type { SquareHoleResultDraft } from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||
import type {
|
||||
@@ -27,18 +26,17 @@ import {
|
||||
type SquareHoleImageAssetKind,
|
||||
updateSquareHoleWork,
|
||||
} from '../../services/square-hole-works';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
|
||||
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformStatGrid } from '../common/PlatformStatGrid';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
|
||||
import {
|
||||
PlatformSelectField,
|
||||
PlatformTextField,
|
||||
@@ -501,7 +499,6 @@ function SquareHoleImageSlotDialog({
|
||||
event: ChangeEvent<HTMLInputElement>,
|
||||
) => void;
|
||||
}) {
|
||||
const platformTheme = useAuthUi()?.platformTheme ?? 'light';
|
||||
const [assets, setAssets] = useState<SquareHoleHistoryAsset[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -537,115 +534,91 @@ function SquareHoleImageSlotDialog({
|
||||
};
|
||||
}, [slot.assetKind]);
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[145] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<PlatformToolModalShell
|
||||
open
|
||||
title={slot.title}
|
||||
ariaLabel={`${slot.title}查看`}
|
||||
onClose={onClose}
|
||||
closeLabel="关闭"
|
||||
zIndexClassName="z-[145]"
|
||||
size="xl"
|
||||
panelClassName="!max-h-[min(92vh,46rem)]"
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`${slot.title}查看`}
|
||||
className="platform-modal-shell platform-remap-surface flex max-h-[min(92vh,46rem)] w-full max-w-5xl flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
|
||||
<div className="text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
{slot.title}
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(16rem,0.78fr)]">
|
||||
<div className="space-y-3">
|
||||
<PlatformMediaFrame
|
||||
src={currentImageSrc}
|
||||
alt={slot.title}
|
||||
fallbackLabel={`${slot.title}占位图`}
|
||||
fallbackContent={<Images className="h-10 w-10" />}
|
||||
aspect="standard"
|
||||
surface="plain"
|
||||
className="rounded-[1.35rem]"
|
||||
fallbackClassName="tracking-normal text-[var(--platform-text-soft)]"
|
||||
/>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<PlatformActionButton
|
||||
asChild="label"
|
||||
tone="ghost"
|
||||
size="md"
|
||||
aria-disabled={isBusy}
|
||||
className={`min-h-11 cursor-pointer gap-2 px-4 ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
上传图片
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="sr-only"
|
||||
disabled={isBusy}
|
||||
onChange={(event) => onUpload(slot, event)}
|
||||
/>
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
onClick={onRegenerateImages}
|
||||
disabled={!canRegenerateImages || isBusy}
|
||||
tone="secondary"
|
||||
size="md"
|
||||
className="min-h-11 gap-2 px-4"
|
||||
>
|
||||
{isRegeneratingImages ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
)}
|
||||
AI生成图片
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
<PlatformModalCloseButton
|
||||
onClick={onClose}
|
||||
label="关闭"
|
||||
variant="platformIcon"
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 space-y-3">
|
||||
<PlatformFieldLabel
|
||||
variant="section"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Images className="h-3.5 w-3.5" />
|
||||
历史生成
|
||||
</PlatformFieldLabel>
|
||||
|
||||
<PlatformAssetPickerGrid
|
||||
items={assets}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
loadingLabel="读取中..."
|
||||
emptyLabel="暂无历史图片"
|
||||
disabled={isBusy}
|
||||
getKey={(asset) => asset.assetObjectId}
|
||||
getImageSrc={(asset) => asset.imageSrc}
|
||||
getImageAlt={(asset) => asset.ownerLabel || '历史图片'}
|
||||
getTitle={(asset) => asset.ownerLabel || '未记录账号'}
|
||||
getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)}
|
||||
onSelect={onSelectHistory}
|
||||
gridClassName="grid max-h-[24rem] grid-cols-2 gap-3 overflow-y-auto pr-1 sm:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(16rem,0.78fr)]">
|
||||
<div className="space-y-3">
|
||||
<PlatformMediaFrame
|
||||
src={currentImageSrc}
|
||||
alt={slot.title}
|
||||
fallbackLabel={`${slot.title}占位图`}
|
||||
fallbackContent={<Images className="h-10 w-10" />}
|
||||
aspect="standard"
|
||||
surface="plain"
|
||||
className="rounded-[1.35rem]"
|
||||
fallbackClassName="tracking-normal text-[var(--platform-text-soft)]"
|
||||
/>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<PlatformActionButton
|
||||
asChild="label"
|
||||
tone="ghost"
|
||||
size="md"
|
||||
aria-disabled={isBusy}
|
||||
className={`min-h-11 cursor-pointer gap-2 px-4 ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
上传图片
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="sr-only"
|
||||
disabled={isBusy}
|
||||
onChange={(event) => onUpload(slot, event)}
|
||||
/>
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
onClick={onRegenerateImages}
|
||||
disabled={!canRegenerateImages || isBusy}
|
||||
tone="secondary"
|
||||
size="md"
|
||||
className="min-h-11 gap-2 px-4"
|
||||
>
|
||||
{isRegeneratingImages ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
)}
|
||||
AI生成图片
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 space-y-3">
|
||||
<PlatformFieldLabel
|
||||
variant="section"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Images className="h-3.5 w-3.5" />
|
||||
历史生成
|
||||
</PlatformFieldLabel>
|
||||
|
||||
<PlatformAssetPickerGrid
|
||||
items={assets}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
loadingLabel="读取中..."
|
||||
emptyLabel="暂无历史图片"
|
||||
disabled={isBusy}
|
||||
getKey={(asset) => asset.assetObjectId}
|
||||
getImageSrc={(asset) => asset.imageSrc}
|
||||
getImageAlt={(asset) => asset.ownerLabel || '历史图片'}
|
||||
getTitle={(asset) => asset.ownerLabel || '未记录账号'}
|
||||
getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)}
|
||||
onSelect={onSelectHistory}
|
||||
gridClassName="grid max-h-[24rem] grid-cols-2 gap-3 overflow-y-auto pr-1 sm:grid-cols-3 lg:grid-cols-2 xl:grid-cols-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
</PlatformToolModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user