继续收口编辑器空态与暗色动作按钮
视觉小说实体列表空态复用 PlatformEmptyState 角色素材工作室局部按钮改为委托 PlatformActionButton RPG大编辑器局部按钮改为委托 PlatformActionButton 更新 PlatformUiKit 收口文档与团队决策记录
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user