继续收口NPC弹窗与角色详情空态
统一 NPC 弹窗底部动作按钮与交易详情空态到共享组件 统一角色构筑详情空明细到 PlatformEmptyState 补充 PlatformUiKit 收口计划、共享决策记录与对应测试护栏
This commit is contained in:
@@ -2081,6 +2081,7 @@
|
||||
- 决策:`CustomWorldNpcVisualEditor.tsx` 的本地 `ActionButton` 和 `SkillEffectPreview.tsx` 的“重新预览”按钮也继续并入这条暗色按钮收口线,统一委托 `PlatformActionButton surface="editorDark"`;局部包装层只保留 `stopPropagation`、图标排布、`tone` 映射和极少量视觉微调。后续暗色编辑器里的局部动作按钮若只是普通 CTA,不再新增原生 `<button>` 实现,优先沿用“薄包装 + 共享按钮本体”模式。
|
||||
- 决策:RPG 创作侧标准 dark header / footer 动作也继续纳入同一条按钮收口线。`RpgCreationRoleAssetStudioModalImpl.tsx` 的 header“关闭”、`RpgCreationEntityEditorShared.tsx` 的 footer“取消”以及 `RpgCreationRoleAssetStudioFooter.tsx` 的“保存到当前角色”都改为委托 `PlatformActionButton surface="editorDark"`;局部壳层只保留布局、宽度/字号贴合和少量 tone 语义,不再为标准 dark close / cancel / save CTA 单独维护原生 `<button>` 基础 chrome。
|
||||
- 决策:RPG runtime overlay 里的标准 dark CTA 和可点击 dark row 也继续纳入这条收口线。`RpgAdventurePanelOverlays.tsx` 的 goal panel“知道了”、任务详情里的“领取任务 / 返回交付”、任务完成提示里的“打开任务日志”都改为委托 `PlatformActionButton surface="editorDark"`;设置面板里的“运行统计”入口改为 `PlatformSubpanel as="button" surface="dark"`。像素风 choice button、HUD launcher、奖励物品格和输入 composer 保持 runtime 专属语义,不继续硬并到普通平台按钮。
|
||||
- 决策:NPC dark modal footer 和暗色明细空态也继续纳入同一条收口线。`NpcModals.tsx` 里的交易 / 赠礼 / 招募弹窗 footer 按钮和物品详情“关闭”按钮都改为委托 `PlatformActionButton surface="editorDark"`,交易右侧“请选择一件物品”提示改为 `PlatformEmptyState surface="editorDark"`;`CharacterInfoShared.tsx` 的 `BuildContributionDetailPanel` 空明细也改为 `PlatformEmptyState surface="editorDark"`。数量 stepper、赠礼 / 招募 option card、标签强度按钮这类带独立业务语义的控件继续保留局部实现。
|
||||
- 验证方式:`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 src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-05-26 敲木鱼发布后作品架与推荐流刷新口径
|
||||
|
||||
@@ -267,6 +267,7 @@
|
||||
22. 设置面板、结果页运行配置和工作台白底配置项中的整行开关迁移到 `PlatformToggleRow`;视觉小说结果页、runtime 设置面板和拼消消创作工作台 AI 生成底图开关已先迁移。后续整行配置项只保留字段写回和可选点击动作,不再重复开关行 chrome、checkbox class 或状态 pill。
|
||||
22.1. RPG 创作侧标准 dark header / footer 动作继续向共享按钮收口:`RpgCreationRoleAssetStudioModalImpl.tsx` 的 header“关闭”、`RpgCreationEntityEditorShared.tsx` 的 footer“取消”、`RpgCreationRoleAssetStudioFooter.tsx` 的“保存到当前角色”都改为委托 `PlatformActionButton surface="editorDark"`。局部壳层只继续保留 `stopPropagation`、tone 映射、布局和极少量字号/宽度贴合;标准暗色编辑器里的 close / cancel / save CTA 不再各自手写原生 `<button>` 基础 chrome。
|
||||
22.2. RPG runtime overlay 里的标准 dark CTA 和可点击 dark row 继续向共享原子收口:`RpgAdventurePanelOverlays.tsx` 的 goal panel“知道了”、任务详情里的“领取任务 / 返回交付”、任务完成提示里的“打开任务日志”都改为委托 `PlatformActionButton surface="editorDark"`;设置面板里的“运行统计”入口改为 `PlatformSubpanel as="button" surface="dark"`。像素风 choice button、HUD launcher、奖励物品格和输入 composer 这类 runtime 专属控件继续保留独立语义,不并回普通平台按钮。
|
||||
22.3. NPC dark modal footer 与暗色明细空态继续向共享原子收口:`NpcModals.tsx` 里的交易 / 赠礼 / 招募弹窗 footer 按钮和物品详情“关闭”按钮都改为委托 `PlatformActionButton surface="editorDark"`,交易右侧“请选择一件物品”提示改为 `PlatformEmptyState surface="editorDark"`;`CharacterInfoShared.tsx` 里的 `BuildContributionDetailPanel` 空明细也改为 `PlatformEmptyState surface="editorDark"`。数量 stepper、赠礼 / 招募 option card、标签强度按钮这类带更强业务语义的控件继续保留局部实现。
|
||||
|
||||
## 验证
|
||||
|
||||
|
||||
@@ -193,6 +193,28 @@ test('BuildContributionDetailPanel reuses dark PlatformSubpanel chrome', () => {
|
||||
expect(attributeRow?.className).toContain('bg-black/25');
|
||||
});
|
||||
|
||||
test('BuildContributionDetailPanel empty state reuses dark PlatformEmptyState chrome', () => {
|
||||
const row: BuildContributionRow = {
|
||||
label: '潮汐',
|
||||
source: 'character',
|
||||
fitScore: 0.72,
|
||||
sourceCoefficient: 1,
|
||||
bonusDelta: 0.12,
|
||||
attributeSimilarities: {},
|
||||
attributeWeights: {},
|
||||
attributeContributions: {},
|
||||
attributeModifierDeltas: {},
|
||||
};
|
||||
|
||||
render(<BuildContributionDetailPanel row={row} attributes={[]} />);
|
||||
|
||||
const emptyState = screen.getByText('当前标签还没有可展示的属性适配明细。');
|
||||
|
||||
expect(emptyState.className).toContain('platform-empty-state');
|
||||
expect(emptyState.className).toContain('border-dashed');
|
||||
expect(emptyState.className).toContain('bg-black/20');
|
||||
});
|
||||
|
||||
test('PlayerLevelProgress renders xp progress details', () => {
|
||||
render(
|
||||
<PlayerLevelProgress level={6} currentLevelXp={72} xpToNextLevel={120} />,
|
||||
|
||||
@@ -352,14 +352,14 @@ export function BuildContributionDetailPanel({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PlatformSubpanel
|
||||
surface="dark"
|
||||
radius="xs"
|
||||
padding="sm"
|
||||
className="mt-4 px-4 py-3 text-sm leading-6 text-zinc-400"
|
||||
<PlatformEmptyState
|
||||
surface="editorDark"
|
||||
size="compact"
|
||||
tone="soft"
|
||||
className="mt-4"
|
||||
>
|
||||
{emptyText}
|
||||
</PlatformSubpanel>
|
||||
</PlatformEmptyState>
|
||||
)}
|
||||
</PlatformSubpanel>
|
||||
</div>
|
||||
|
||||
@@ -260,7 +260,50 @@ test('NPC 弹窗空态复用暗色平台空态', () => {
|
||||
});
|
||||
|
||||
const recruitIntro = screen.getByText('同行名额已满,需要先让一人离队。');
|
||||
const tradeDetailEmptyState = screen.getByText(
|
||||
'请选择一件物品,右侧会显示数量、价格与详情。',
|
||||
);
|
||||
|
||||
expect(recruitIntro.className).toContain('platform-status-message');
|
||||
expect(recruitIntro.className).toContain('border-amber-300/15');
|
||||
expect(tradeDetailEmptyState.className).toContain('platform-empty-state');
|
||||
expect(tradeDetailEmptyState.className).toContain('border-dashed');
|
||||
});
|
||||
|
||||
test('NPC 弹窗标准 dark footer CTA 复用 PlatformActionButton', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const { unmount } = render(
|
||||
<NpcModals gameState={createEmptyGameState()} npcUi={createEmptyNpcUi()} />,
|
||||
);
|
||||
|
||||
const cancelButtons = screen.getAllByRole('button', { name: '取消' });
|
||||
const tradeConfirmButton = screen.getByRole('button', { name: '确认购买' });
|
||||
const giftConfirmButton = screen.getByRole('button', { name: '确认赠礼' });
|
||||
const recruitConfirmButton = screen.getByRole('button', { name: '确认招募' });
|
||||
const footerButtons = [
|
||||
cancelButtons[0],
|
||||
tradeConfirmButton,
|
||||
cancelButtons[1],
|
||||
giftConfirmButton,
|
||||
cancelButtons[2],
|
||||
recruitConfirmButton,
|
||||
].filter((button): button is HTMLElement => Boolean(button));
|
||||
|
||||
expect(footerButtons).toHaveLength(6);
|
||||
|
||||
footerButtons.forEach((button) => {
|
||||
expect(button.className).toContain('platform-action-button--editor-dark');
|
||||
expect(button.className).toContain('rounded-2xl');
|
||||
});
|
||||
|
||||
unmount();
|
||||
render(<NpcModals gameState={createGameState()} npcUi={createNpcUi()} />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /月壳/ }));
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: '关闭' });
|
||||
|
||||
expect(closeButton.className).toContain('platform-action-button--editor-dark');
|
||||
expect(closeButton.className).toContain('rounded-2xl');
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
getNineSliceStyle,
|
||||
UI_CHROME,
|
||||
} from '../uiAssets';
|
||||
import { PlatformActionButton } from './common/PlatformActionButton';
|
||||
import { PlatformDarkOptionCard } from './common/PlatformDarkOptionCard';
|
||||
import { PlatformEmptyState } from './common/PlatformEmptyState';
|
||||
import { PlatformPillBadge } from './common/PlatformPillBadge';
|
||||
@@ -412,9 +413,14 @@ export function NpcModals({ gameState, npcUi }: NpcModalsProps) {
|
||||
</PlatformSubpanel>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 py-8 text-center text-sm text-zinc-500">
|
||||
<PlatformEmptyState
|
||||
surface="editorDark"
|
||||
size="compact"
|
||||
tone="soft"
|
||||
className="px-2 py-8 text-center"
|
||||
>
|
||||
请选择一件物品,右侧会显示数量、价格与详情。
|
||||
</div>
|
||||
</PlatformEmptyState>
|
||||
)}
|
||||
</PlatformSubpanel>
|
||||
</div>
|
||||
@@ -422,29 +428,23 @@ export function NpcModals({ gameState, npcUi }: NpcModalsProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-3 border-t border-white/10 px-4 py-3 sm:px-5 sm:py-4">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
onClick={npcUi.closeTradeModal}
|
||||
className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-zinc-200"
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="primary"
|
||||
size="xs"
|
||||
disabled={!canConfirmTrade}
|
||||
onClick={npcUi.confirmTrade}
|
||||
className={`pixel-nine-slice pixel-pressable px-4 py-2 text-xs ${canConfirmTrade ? 'text-white' : 'text-zinc-600'}`}
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
{tradeMode === 'buy' ? '确认购买' : '确认出售'}
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
@@ -560,17 +560,14 @@ export function NpcModals({ gameState, npcUi }: NpcModalsProps) {
|
||||
)}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
onClick={() => setTradeDetail(null)}
|
||||
className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-zinc-200"
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -669,29 +666,23 @@ export function NpcModals({ gameState, npcUi }: NpcModalsProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 px-5 pb-5">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
onClick={npcUi.closeGiftModal}
|
||||
className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-zinc-200"
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="primary"
|
||||
size="xs"
|
||||
disabled={!activeGiftView?.canSubmit}
|
||||
onClick={npcUi.confirmGift}
|
||||
className={`pixel-nine-slice pixel-pressable px-4 py-2 text-xs ${activeGiftView?.canSubmit ? 'text-white' : 'text-zinc-600'}`}
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
确认赠礼
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
@@ -773,29 +764,23 @@ export function NpcModals({ gameState, npcUi }: NpcModalsProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 px-5 pb-5">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
onClick={npcUi.closeRecruitModal}
|
||||
className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-zinc-200"
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
surface="editorDark"
|
||||
tone="primary"
|
||||
size="xs"
|
||||
disabled={!npcUi.recruitModal.selectedReleaseNpcId}
|
||||
onClick={npcUi.confirmRecruit}
|
||||
className={`pixel-nine-slice pixel-pressable px-4 py-2 text-xs ${npcUi.recruitModal.selectedReleaseNpcId ? 'text-white' : 'text-zinc-600'}`}
|
||||
style={getNineSliceStyle(UI_CHROME.choiceButton, {
|
||||
paddingX: 14,
|
||||
paddingY: 8,
|
||||
})}
|
||||
>
|
||||
确认招募
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user