继续收口编辑器空态与暗色动作按钮

视觉小说实体列表空态复用 PlatformEmptyState
角色素材工作室局部按钮改为委托 PlatformActionButton
RPG大编辑器局部按钮改为委托 PlatformActionButton
更新 PlatformUiKit 收口文档与团队决策记录
This commit is contained in:
2026-06-11 01:52:47 +08:00
parent 06bf03a28c
commit 402b847c7f
7 changed files with 59 additions and 23 deletions
+2 -2
View File
@@ -2077,8 +2077,8 @@
- 决策:平台入口的创作前置泥点阻断提示只在 `platform-entry` 局部抽成 `src/components/platform-entry/PlatformDraftGenerationPointNoticeDialog.tsx`,并使用 `DraftGenerationPointNotice` union(`insufficient-points` / `balance-load-failed`)承接业务真相;不要在 `common/` 再抽一个泛化 `BlockingNoticeDialog`,否则会把 `PlatformAcknowledgeStatusDialog` 的样式透传再包装一层而不缩小调用面。
- 决策:`PlatformAsyncStatePanel` 从 profile modal 扩展到作品架类白底 panel;`CustomWorldCreationHub.tsx` 的作品架主体现在也统一走 `loadingState / emptyState / children` 三段 slot,但 error + 重试继续留在业务层外侧,不把共享组件扩成“banner + retry + content”全能状态机。后续白底作品架或列表 panel 若只是互斥的 `loading / empty / content`,优先直接复用这套骨架。
- 决策:`CopyFeedbackButton.tsx` 的 `actionSurface` 分支继续收口到 `PlatformActionButton`,`pill` 分支继续保留 `PlatformPillBadge` 风格;复制反馈按钮不再直接调用 `getPlatformActionButtonClassName` 手拼平台按钮基础 chrome。后续同类“复制状态机 + 平台动作按钮”组合优先直接复用 `CopyFeedbackButton`,不要在业务页重新混写图标、文案、aria 和动作按钮 class。
- 决策:白底 / 暗色面板里的轻量空态和普通 CTA 继续向共享组件收口。`PuzzleResultView.tsx` 的缺草稿提示、`RpgCreationAssetDebugPanel.tsx` 的空诊断提示改为 `PlatformEmptyState`,`Match3DResultView.tsx` 的引用素材列表直接复用 `PlatformAssetPickerGrid` 自己的空态;`AdventureEntityModal.tsx` 的私聊按钮和 `InventoryPanel.tsx` 的锻造 / 合成按钮改为 `PlatformActionButton surface="editorDark"`,业务页只贴局部 sky / emerald 皮肤。后续白底子面板里的只读空态优先使用 `PlatformEmptyState surface="subpanel" size="inline"`,暗色编辑 / 运行面板里的普通动作优先使用 `PlatformActionButton surface="editorDark"`。
- 验证方式:`npm run test -- src/components/common/PlatformAsyncStatePanel.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/common/PlatformSegmentedTabs.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/PixelCloseButton.test.tsx src/components/CharacterChatModal.test.tsx src/components/MapModal.test.tsx`、`npm run test -- src/components/common/useMudPointConfirmController.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/rpg-creation-result/RpgCreationResultActionBar.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`、`npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 决策:白底 / 暗色面板里的轻量空态和普通 CTA 继续向共享组件收口。`PuzzleResultView.tsx` 的缺草稿提示、`RpgCreationAssetDebugPanel.tsx` 的空诊断提示、`VisualNovelEntityGrid` 的空实体列表都改为 `PlatformEmptyState`,`Match3DResultView.tsx` 的引用素材列表直接复用 `PlatformAssetPickerGrid` 自己的空态;`AdventureEntityModal.tsx` 的私聊按钮、`InventoryPanel.tsx` 的锻造 / 合成按钮,以及 `RpgCreationRoleAssetStudioModalImpl.tsx`、`RpgCreationEntityEditorShared.tsx` 里的局部 `ActionButton` 包装层都改为委托 `PlatformActionButton surface="editorDark"`。后续白底子面板里的只读空态优先使用 `PlatformEmptyState surface="subpanel" size="inline"`;暗色编辑 / 运行面板里的普通动作优先使用 `PlatformActionButton surface="editorDark"`,若业务仍需 `stopPropagation`、tone 映射或局部排版,可保留薄包装层,但不要再直接写原生 `<button>` 基础 chrome。
- 验证方式:`npm run test -- src/components/common/PlatformAsyncStatePanel.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/common/PlatformSegmentedTabs.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/PixelCloseButton.test.tsx src/components/CharacterChatModal.test.tsx src/components/MapModal.test.tsx`、`npm run test -- src/components/common/useMudPointConfirmController.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/rpg-creation-result/RpgCreationResultActionBar.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`、`npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-05-26 敲木鱼发布后作品架与推荐流刷新口径
File diff suppressed because one or more lines are too long
@@ -1055,7 +1055,11 @@ test('基本设定面板只编辑六个角色维度名称', async () => {
expect(screen.queryByLabelText('正向信号')).toBeNull();
expect(screen.queryByLabelText('战斗体现')).toBeNull();
await user.click(screen.getByRole('button', { name: /保存修改/u }));
const saveButton = screen.getByRole('button', { name: /保存修改/u });
expect(saveButton.className).toContain('platform-action-button--editor-dark');
expect(saveButton.className).toContain('rounded-full');
await user.click(saveButton);
expect(savedProfileRef.current?.attributeSchema.slots[0]?.name).toBe('潮骨');
});
@@ -23,6 +23,7 @@ import {
import { buildProjectPixelStyleReferenceBoard } from '../asset-studio/projectPixelStyleReference';
import { useAuthUi } from '../auth/AuthUiContext';
import { CharacterAnimator } from '../CharacterAnimator';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformMudPointConfirmDialog } from '../common/PlatformMudPointConfirmDialog';
import {
CORE_ACTIONS,
@@ -206,16 +207,21 @@ function ActionButton({
disabled?: boolean;
tone?: 'default' | 'sky' | 'green';
}) {
const resolvedTone =
tone === 'green' ? 'success' : tone === 'sky' ? 'primary' : 'ghost';
const toneClassName =
tone === 'green'
? 'border-emerald-400/30 bg-emerald-500/10 text-emerald-100 hover:bg-emerald-500/20'
? 'border-emerald-400/30 bg-emerald-500/10 text-emerald-100 hover:border-emerald-300/40 hover:bg-emerald-500/20 hover:text-white'
: tone === 'sky'
? 'border-sky-300/22 bg-sky-500/12 text-sky-50 hover:border-sky-200/40 hover:text-white'
: 'border-white/12 bg-black/20 text-zinc-200 hover:border-white/22 hover:text-white';
? 'border-sky-300/22 bg-sky-500/12 text-sky-50 hover:border-sky-200/40 hover:bg-sky-500/15 hover:text-white'
: 'border-white/12 bg-black/20 text-zinc-200 hover:border-white/22 hover:bg-black/20 hover:text-white';
return (
<button
type="button"
<PlatformActionButton
surface="editorDark"
tone={resolvedTone}
shape="pill"
align="start"
onPointerDown={(event) => {
event.stopPropagation();
}}
@@ -227,7 +233,12 @@ function ActionButton({
onClick();
}}
disabled={disabled}
className={`inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-semibold transition-colors ${toneClassName} ${disabled ? 'cursor-not-allowed opacity-45' : ''}`}
className={[
'py-2 font-semibold leading-tight disabled:opacity-45',
toneClassName,
]
.filter(Boolean)
.join(' ')}
>
{icon ?? null}
<span className="flex flex-col items-start leading-tight">
@@ -236,7 +247,7 @@ function ActionButton({
<span className="text-[11px] font-medium opacity-70">{subLabel}</span>
) : null}
</span>
</button>
</PlatformActionButton>
);
}
@@ -95,6 +95,7 @@ import {
import { useAuthUi } from '../auth/AuthUiContext';
import { CharacterAnimator } from '../CharacterAnimator';
import { PlatformAcknowledgeStatusDialog } from '../common/PlatformAcknowledgeStatusDialog';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformIconButton } from '../common/PlatformIconButton';
@@ -1665,16 +1666,22 @@ function ActionButton({
disabled?: boolean;
className?: string;
}) {
const buttonTone =
tone === 'sky' ? 'primary' : tone === 'rose' ? 'danger' : 'ghost';
const toneClassName =
tone === 'sky'
? 'border-sky-300/22 bg-sky-500/12 text-sky-50 hover:border-sky-200/40 hover:text-white'
? 'border-sky-300/22 bg-sky-500/12 text-sky-50 hover:border-sky-200/40 hover:bg-sky-500/18 hover:text-white'
: tone === 'rose'
? 'border-rose-300/22 bg-rose-500/12 text-rose-50 hover:border-rose-200/40 hover:text-white'
? 'border-rose-300/22 bg-rose-500/12 text-rose-50 hover:border-rose-200/40 hover:bg-rose-500/16 hover:text-white'
: 'border-white/12 bg-black/20 text-zinc-200 hover:border-white/22 hover:text-white';
return (
<button
<PlatformActionButton
type="button"
surface="editorDark"
tone={buttonTone}
size="sm"
shape="pill"
onPointerDown={(event) => {
event.stopPropagation();
}}
@@ -1686,10 +1693,16 @@ function ActionButton({
onClick();
}}
disabled={disabled}
className={`rounded-full border px-4 py-2 text-sm font-semibold transition-colors ${toneClassName} ${disabled ? 'cursor-not-allowed opacity-45' : ''} ${className}`}
className={[
'py-2 font-semibold disabled:opacity-45',
toneClassName,
className,
]
.filter(Boolean)
.join(' ')}
>
{label}
</button>
</PlatformActionButton>
);
}
@@ -113,7 +113,7 @@ test('visual novel entity list items use interactive PlatformSubpanel shells', a
expect(characterCard.className).toContain('rounded-[1.25rem]');
});
test('visual novel empty entity list uses PlatformSubpanel shell', async () => {
test('visual novel empty entity list uses PlatformEmptyState shell', async () => {
const user = userEvent.setup();
const emptyCharacterDraft = {
...mockVisualNovelDraft,
@@ -126,11 +126,17 @@ test('visual novel empty entity list uses PlatformSubpanel shell', async () => {
await user.click(screen.getByRole('button', { name: '角色' }));
const emptyPanel = screen.getByText('暂无角色').closest('.platform-subpanel');
const emptyPanel = screen
.getByText('暂无角色')
.closest('.platform-empty-state');
expect(emptyPanel?.className).toContain('platform-subpanel');
expect(emptyPanel?.className).toContain('platform-empty-state');
expect(emptyPanel?.className).toContain('bg-white/74');
expect(emptyPanel?.className).toContain('rounded-[1.25rem]');
expect(emptyPanel?.className).toContain('min-h-32');
expect(emptyPanel?.className).toContain(
'text-[var(--platform-text-soft)]',
);
});
test('visual novel result opens complex editors as a dialog', async () => {
@@ -1848,12 +1848,13 @@ function VisualNovelEntityGrid({
</PlatformSubpanel>
))}
{items.length <= 0 ? (
<PlatformSubpanel
as="div"
className="flex min-h-32 items-center justify-center rounded-[1.25rem] px-4 text-sm text-[var(--platform-text-soft)]"
<PlatformEmptyState
surface="subpanel"
size="inline"
className="flex min-h-32 items-center justify-center rounded-[1.25rem] p-4 font-normal"
>
{emptyText}
</PlatformSubpanel>
</PlatformEmptyState>
) : null}
</div>
</div>