收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
import { Check, Puzzle, SlidersHorizontal, X } from 'lucide-react';
|
||||
import { Check, Puzzle, SlidersHorizontal } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import type { PuzzleCreativeTemplateSelection } from '../../../packages/shared/src/contracts/puzzleCreativeTemplate';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformIconBadge } from '../common/PlatformIconBadge';
|
||||
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
|
||||
import { PlatformStatGrid } from '../common/PlatformStatGrid';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
|
||||
type CreativeAgentTemplateConfirmPanelProps = {
|
||||
selection: PuzzleCreativeTemplateSelection;
|
||||
@@ -12,7 +20,10 @@ type CreativeAgentTemplateConfirmPanelProps = {
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
function clampLevelCount(value: number, selection: PuzzleCreativeTemplateSelection) {
|
||||
function clampLevelCount(
|
||||
value: number,
|
||||
selection: PuzzleCreativeTemplateSelection,
|
||||
) {
|
||||
const { min, max } = resolveLevelCountBounds(selection);
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
@@ -88,93 +99,89 @@ export function CreativeAgentTemplateConfirmPanel({
|
||||
预计 {pointsText}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
<PlatformModalCloseButton
|
||||
label="取消模板"
|
||||
variant="platformIcon"
|
||||
disabled={isBusy}
|
||||
onClick={onCancel}
|
||||
className="platform-icon-button"
|
||||
aria-label="取消模板"
|
||||
title="取消"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-hidden rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/68">
|
||||
<div className="aspect-[16/9] bg-[radial-gradient(circle_at_28%_20%,rgba(255,255,255,0.92),transparent_32%),linear-gradient(135deg,rgba(255,194,123,0.86),rgba(255,93,132,0.82)_52%,rgba(92,186,255,0.78))]">
|
||||
{'previewImageSrc' in draftSelection &&
|
||||
typeof draftSelection.previewImageSrc === 'string' &&
|
||||
draftSelection.previewImageSrc.trim() ? (
|
||||
<img
|
||||
src={draftSelection.previewImageSrc}
|
||||
alt={draftSelection.title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<span className="inline-flex h-14 w-14 items-center justify-center rounded-full bg-white/84 text-[var(--platform-text-strong)] shadow-sm">
|
||||
<Puzzle className="h-6 w-6" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PlatformMediaFrame
|
||||
src={
|
||||
'previewImageSrc' in draftSelection &&
|
||||
typeof draftSelection.previewImageSrc === 'string'
|
||||
? draftSelection.previewImageSrc
|
||||
: ''
|
||||
}
|
||||
alt={draftSelection.title}
|
||||
fallbackLabel={draftSelection.title}
|
||||
fallbackContent={
|
||||
<PlatformIconBadge
|
||||
icon={<Puzzle className="h-6 w-6" />}
|
||||
size="xl"
|
||||
tone="softBright"
|
||||
/>
|
||||
}
|
||||
aspect="landscape"
|
||||
surface="soft"
|
||||
className="rounded-[1.25rem]"
|
||||
fallbackShellClassName="bg-[radial-gradient(circle_at_28%_20%,rgba(255,255,255,0.92),transparent_32%),linear-gradient(135deg,rgba(255,194,123,0.86),rgba(255,93,132,0.82)_52%,rgba(92,186,255,0.78))]"
|
||||
fallbackClassName="tracking-normal"
|
||||
/>
|
||||
|
||||
<div className="platform-subpanel rounded-[1.25rem] p-4">
|
||||
<PlatformSubpanel as="div" radius="md">
|
||||
<div className="text-sm font-semibold leading-6 text-[var(--platform-text-base)]">
|
||||
{draftSelection.reason}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="platform-subpanel rounded-[1.15rem] p-4">
|
||||
<div className="text-xs font-bold tracking-[0.16em] text-[var(--platform-text-soft)]">
|
||||
关卡模式
|
||||
</div>
|
||||
<div className="mt-2 text-base font-black text-[var(--platform-text-strong)]">
|
||||
{draftSelection.selectedLevelMode === 'single_level'
|
||||
? '单关卡'
|
||||
: '多关卡'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="platform-subpanel rounded-[1.15rem] p-4">
|
||||
<div className="text-xs font-bold tracking-[0.16em] text-[var(--platform-text-soft)]">
|
||||
计划关卡
|
||||
</div>
|
||||
<div className="mt-2 text-base font-black text-[var(--platform-text-strong)]">
|
||||
{draftSelection.plannedLevelCount} 关
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PlatformSubpanel>
|
||||
|
||||
<PlatformStatGrid
|
||||
items={[
|
||||
{
|
||||
label: '关卡模式',
|
||||
value:
|
||||
draftSelection.selectedLevelMode === 'single_level'
|
||||
? '单关卡'
|
||||
: '多关卡',
|
||||
},
|
||||
{
|
||||
label: '计划关卡',
|
||||
value: `${draftSelection.plannedLevelCount} 关`,
|
||||
},
|
||||
]}
|
||||
columns="two"
|
||||
order="labelFirst"
|
||||
surface="plain"
|
||||
textAlign="left"
|
||||
itemClassName="rounded-[1.15rem] p-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-reverse gap-3 border-t border-[var(--platform-subpanel-border)] px-5 py-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)] sm:flex-row sm:justify-end">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
tone="ghost"
|
||||
disabled={isBusy}
|
||||
onClick={() => setIsAdjustOpen((current) => !current)}
|
||||
className="platform-button platform-button--ghost"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
调整
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
disabled={isBusy}
|
||||
onClick={() => onConfirm(draftSelection)}
|
||||
className="platform-button platform-button--primary"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Check className="h-4 w-4" />
|
||||
确认
|
||||
</span>
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -190,54 +197,51 @@ export function CreativeAgentTemplateConfirmPanel({
|
||||
<div className="text-base font-black text-[var(--platform-text-strong)]">
|
||||
调整关卡
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
<PlatformModalCloseButton
|
||||
label="关闭调整"
|
||||
variant="platformIcon"
|
||||
disabled={isBusy}
|
||||
onClick={() => setIsAdjustOpen(false)}
|
||||
className="platform-icon-button"
|
||||
aria-label="关闭调整"
|
||||
title="关闭"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 px-5 py-4">
|
||||
<div className="grid grid-cols-2 gap-2 rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/62 p-1">
|
||||
{[
|
||||
{ value: 'single_level' as const, label: '单关卡' },
|
||||
{ value: 'multi_level' as const, label: '多关卡' },
|
||||
].map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
disabled={isBusy || !canUseLevelMode(draftSelection, item.value)}
|
||||
onClick={() => {
|
||||
setDraftSelection((current) => ({
|
||||
...current,
|
||||
selectedLevelMode: item.value,
|
||||
plannedLevelCount:
|
||||
item.value === 'single_level'
|
||||
? 1
|
||||
: Math.max(2, current.plannedLevelCount),
|
||||
}));
|
||||
}}
|
||||
className={`min-h-10 rounded-[0.8rem] px-3 text-sm font-bold ${
|
||||
draftSelection.selectedLevelMode === item.value
|
||||
? 'bg-white text-[var(--platform-text-strong)] shadow-sm'
|
||||
: 'text-[var(--platform-text-base)]'
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<PlatformSegmentedTabs
|
||||
items={[
|
||||
{
|
||||
id: 'single_level',
|
||||
label: '单关卡',
|
||||
disabled:
|
||||
isBusy || !canUseLevelMode(draftSelection, 'single_level'),
|
||||
},
|
||||
{
|
||||
id: 'multi_level',
|
||||
label: '多关卡',
|
||||
disabled:
|
||||
isBusy || !canUseLevelMode(draftSelection, 'multi_level'),
|
||||
},
|
||||
]}
|
||||
activeId={draftSelection.selectedLevelMode}
|
||||
onChange={(nextLevelMode) => {
|
||||
setDraftSelection((current) => ({
|
||||
...current,
|
||||
selectedLevelMode: nextLevelMode,
|
||||
plannedLevelCount:
|
||||
nextLevelMode === 'single_level'
|
||||
? 1
|
||||
: Math.max(2, current.plannedLevelCount),
|
||||
}));
|
||||
}}
|
||||
radius="md"
|
||||
size="compact"
|
||||
/>
|
||||
|
||||
<label className="flex min-h-11 items-center gap-3">
|
||||
<span className="shrink-0 text-sm font-bold text-[var(--platform-text-base)]">
|
||||
关卡数
|
||||
</span>
|
||||
<input
|
||||
<PlatformTextField
|
||||
type="number"
|
||||
min={levelCountBounds.min}
|
||||
max={levelCountBounds.max}
|
||||
@@ -258,21 +262,20 @@ export function CreativeAgentTemplateConfirmPanel({
|
||||
),
|
||||
}));
|
||||
}}
|
||||
className="min-h-11 min-w-0 flex-1 rounded-[0.9rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 text-sm font-bold text-[var(--platform-text-strong)] outline-none"
|
||||
density="compact"
|
||||
className="min-h-11 min-w-0 flex-1 font-bold"
|
||||
aria-label="计划关卡数"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end border-t border-[var(--platform-subpanel-border)] px-5 py-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
disabled={isBusy}
|
||||
onClick={() => setIsAdjustOpen(false)}
|
||||
className="platform-button platform-button--primary"
|
||||
>
|
||||
完成
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user