继续收口轻量共享按钮

将 PlatformTagEditor 标签删除入口改为复用共享图标按钮

将角色选择页重复返回按钮收口到共享暗色动作按钮壳

补充 PlatformUiKit 收口计划与共享决策记录
This commit is contained in:
2026-06-11 04:25:59 +08:00
parent a8012109ae
commit 1b89611c9a
6 changed files with 58 additions and 26 deletions

View File

@@ -23,7 +23,18 @@ test('renders tags and removes a tag', () => {
/>,
);
fireEvent.click(screen.getByRole('button', { name: '删除标签 山海' }));
const removeButton = screen.getByRole('button', { name: '删除标签 山海' });
expect(removeButton.className).toContain('platform-icon-button');
expect(removeButton.className).toContain('h-3.5');
expect(removeButton.className).toContain('w-3.5');
expect(removeButton.className).toContain('border-0');
expect(removeButton.className).toContain('bg-transparent');
expect(removeButton.className).toContain('p-0');
expect(removeButton.className).toContain('opacity-70');
expect(removeButton.getAttribute('title')).toBe('删除标签');
fireEvent.click(removeButton);
expect(onChange).toHaveBeenCalledWith(['机关']);
});

View File

@@ -131,20 +131,18 @@ export function PlatformTagEditor({
].join(' ')}
>
{tag}
<button
type="button"
<PlatformIconButton
disabled={disabled}
label={`删除标签 ${tag}`}
title="删除标签"
onClick={() =>
onChange(
normalizedTags.filter((currentTag) => currentTag !== tag),
)
}
className="rounded-full opacity-70 transition hover:opacity-100 disabled:opacity-45"
aria-label={`删除标签 ${tag}`}
title="删除标签"
>
<X className="h-3.5 w-3.5" />
</button>
className="h-3.5 w-3.5 border-0 bg-transparent p-0 opacity-70 shadow-none transition hover:translate-y-0 hover:bg-transparent disabled:opacity-45"
icon={<X className="h-3.5 w-3.5" />}
/>
</span>
))}
{normalizedTags.length <= 0 ? (

View File

@@ -103,6 +103,7 @@ afterEach(() => {
test('custom world character selection stays stable when character ids are empty', async () => {
const user = userEvent.setup();
const handleBack = vi.fn();
const handleConfirm = vi.fn();
const consoleErrorSpy = vi
.spyOn(console, 'error')
@@ -201,11 +202,21 @@ test('custom world character selection stays stable when character ids are empty
],
},
} as unknown as CustomWorldProfile}
onBack={() => {}}
onBack={handleBack}
onConfirm={handleConfirm}
/>,
);
const backButton = screen.getByRole('button', {name: '返回'});
expect(backButton.className).toContain('platform-action-button--editor-dark');
expect(backButton.className).toContain('rounded-full');
expect(backButton.className).toContain('px-3');
expect(backButton.className).toContain('py-1.5');
expect(backButton.className).toContain('text-[11px]');
await user.click(backButton);
expect(handleBack).toHaveBeenCalledTimes(1);
expect(screen.getByText(/:/u)).toBeTruthy();
expect(screen.queryByText(/:/u)).toBeNull();
@@ -235,7 +246,9 @@ test('custom world character selection stays stable when character ids are empty
expect(duplicateKeyCalls).toHaveLength(0);
});
test('custom world character selection falls back instead of rendering a blank screen when profile characters are malformed', () => {
test('custom world character selection falls back instead of rendering a blank screen when profile characters are malformed', async () => {
const user = userEvent.setup();
const handleBack = vi.fn();
vi.spyOn(console, 'warn').mockImplementation(() => undefined);
vi.mocked(buildCustomWorldPlayableCharacters).mockImplementation(() => {
throw new TypeError('profile.playableNpcs is not iterable');
@@ -268,7 +281,7 @@ test('custom world character selection falls back instead of rendering a blank s
],
},
} as unknown as CustomWorldProfile}
onBack={() => {}}
onBack={handleBack}
onConfirm={() => {}}
/>,
);
@@ -276,4 +289,7 @@ test('custom world character selection falls back instead of rendering a blank s
expect(screen.getByText('选择你的角色')).toBeTruthy();
expect(screen.getAllByText('兜底侠').length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
await user.click(screen.getByRole('button', {name: '返回'}));
expect(handleBack).toHaveBeenCalledTimes(1);
});

View File

@@ -20,6 +20,7 @@ import {
import { getNineSliceStyle, UI_CHROME } from '../../uiAssets';
import { CharacterAnimator } from '../CharacterAnimator';
import { CharacterDetailModal } from '../CharacterDetailModal';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { CharacterDraftModal } from '../SelectionCustomizationModals';
@@ -215,6 +216,21 @@ function getCharacterCardStyle(index: number, progress: number) {
};
}
function CharacterSelectBackButton({onBack}: {onBack: () => void}) {
return (
<PlatformActionButton
surface="editorDark"
tone="ghost"
size="xxs"
shape="pill"
onClick={onBack}
className="px-3 py-1.5 text-[11px]"
>
</PlatformActionButton>
);
}
export function RpgEntryCharacterSelectView({
worldType,
customWorldProfile,
@@ -344,13 +360,7 @@ export function RpgEntryCharacterSelectView({
if (!selectedCharacter || !selectedCharacterMeta) {
return (
<div className="flex h-full min-h-0 flex-col items-center justify-center gap-4 text-center">
<button
type="button"
onClick={onBack}
className="rounded-full border border-white/10 bg-black/18 px-3 py-1.5 text-[11px] text-zinc-300 transition-colors hover:text-white"
>
</button>
<CharacterSelectBackButton onBack={onBack} />
<div className="text-sm text-zinc-300"></div>
</div>
);
@@ -360,13 +370,7 @@ export function RpgEntryCharacterSelectView({
<>
<div className="flex h-full min-h-0 flex-col">
<div className="mb-3 flex justify-start">
<button
type="button"
onClick={onBack}
className="rounded-full border border-white/10 bg-black/18 px-3 py-1.5 text-[11px] text-zinc-300 transition-colors hover:text-white"
>
</button>
<CharacterSelectBackButton onBack={onBack} />
</div>
<div className="mb-4 text-center">
<div className="text-2xl font-black text-white sm:text-[2rem]"></div>