收口拼图首访按钮

为 PlatformActionButton 增加琥珀实心 accent tone

将拼图首访生成按钮迁移到 PlatformActionButton

将拼图首访登录按钮迁移到 PlatformActionButton

将拼图首访跳过按钮迁移到 PlatformActionButton

补充按钮组件和首访按钮断言

更新 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
2026-06-10 14:23:51 +08:00
parent e019ece907
commit 9b36903021
7 changed files with 81 additions and 15 deletions

View File

@@ -74,3 +74,19 @@ test('supports editor dark action surface', () => {
expect(button.className).toContain('bg-amber-500/20');
expect(button.className).toContain('text-[10px]');
});
test('supports accent action tone', () => {
render(
<PlatformActionButton surface="editorDark" tone="accent" size="lg" fullWidth>
</PlatformActionButton>,
);
const button = screen.getByRole('button', { name: '生成' });
expect(button.className).toContain('platform-action-button--accent');
expect(button.className).toContain('bg-amber-200');
expect(button.className).toContain('text-slate-950');
expect(button.className).toContain('h-12');
expect(button.className).toContain('w-full');
});

View File

@@ -60,3 +60,18 @@ test('builds editor dark action button class names', () => {
expect(className).toContain('text-[10px]');
expect(className).toContain('rounded-full');
});
test('builds accent action button class names', () => {
const className = getPlatformActionButtonClassName({
surface: 'editorDark',
tone: 'accent',
size: 'lg',
fullWidth: true,
});
expect(className).toContain('platform-action-button--accent');
expect(className).toContain('bg-amber-200');
expect(className).toContain('text-slate-950');
expect(className).toContain('h-12');
expect(className).toContain('w-full');
});

View File

@@ -4,7 +4,8 @@ export type PlatformActionButtonTone =
| 'ghost'
| 'danger'
| 'success'
| 'warning';
| 'warning'
| 'accent';
export type PlatformActionButtonSurface = 'platform' | 'profile' | 'editorDark';
export type PlatformActionButtonSize = 'xxs' | 'xs' | 'sm' | 'md' | 'lg';
export type PlatformActionButtonShape = 'default' | 'pill';
@@ -20,6 +21,8 @@ const PLATFORM_ACTION_BUTTON_PLATFORM_TONE_CLASS: Record<
danger: 'platform-button platform-button--danger',
success: 'platform-button platform-button--primary',
warning: 'platform-button platform-button--secondary',
accent:
'platform-action-button platform-action-button--accent border border-amber-200/70 bg-amber-200 text-slate-950 hover:bg-amber-100',
};
const PLATFORM_ACTION_BUTTON_PROFILE_TONE_CLASS: Record<
@@ -32,6 +35,8 @@ const PLATFORM_ACTION_BUTTON_PROFILE_TONE_CLASS: Record<
danger: 'platform-button platform-button--danger',
success: 'platform-primary-button',
warning: 'platform-button platform-button--secondary',
accent:
'platform-action-button platform-action-button--accent border border-amber-200/70 bg-amber-200 text-slate-950 hover:bg-amber-100',
};
const PLATFORM_ACTION_BUTTON_EDITOR_DARK_TONE_CLASS: Record<
@@ -50,6 +55,8 @@ const PLATFORM_ACTION_BUTTON_EDITOR_DARK_TONE_CLASS: Record<
'platform-action-button platform-action-button--editor-dark border border-emerald-300/35 bg-emerald-400 text-slate-950 hover:bg-emerald-300',
warning:
'platform-action-button platform-action-button--editor-dark border border-amber-300/30 bg-amber-500/20 text-amber-50 hover:bg-amber-500/25',
accent:
'platform-action-button platform-action-button--accent border border-amber-200/70 bg-amber-200 text-slate-950 hover:bg-amber-100',
};
const PLATFORM_ACTION_BUTTON_SIZE_CLASS: Record<

View File

@@ -61,9 +61,20 @@ test('PuzzleOnboardingView uses shared dark textarea and error status chrome', (
test('PuzzleOnboardingView preserves submit, skip, and disabled phase behavior', () => {
const { onSubmit, onSkip } = renderOnboarding();
const submitButton = screen.getByRole('button', { name: '生成' });
const skipButton = screen.getByRole('button', { name: '跳过' });
fireEvent.click(screen.getByRole('button', { name: '生成' }));
fireEvent.click(screen.getByRole('button', { name: '跳过' }));
expect(submitButton.className).toContain('platform-action-button--accent');
expect(submitButton.className).toContain('bg-amber-200');
expect(submitButton.className).toContain('w-full');
expect(skipButton.className).toContain(
'platform-action-button--editor-dark',
);
expect(skipButton.className).toContain('rounded-full');
expect(skipButton.className).toContain('absolute');
fireEvent.click(submitButton);
fireEvent.click(skipButton);
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSkip).toHaveBeenCalledTimes(1);
@@ -112,6 +123,9 @@ test('PuzzleOnboardingLoginOverlay uses shared error status and keeps login acti
fireEvent.click(screen.getByRole('button', { name: '注册账号 / 登录' }));
expect(onLogin).toHaveBeenCalledTimes(1);
expect(screen.getByRole('button', { name: '注册账号 / 登录' }).className).toContain(
'platform-action-button--accent',
);
expect(screen.getByText('保存首访拼图失败').className).toContain(
'platform-status-message',
);

View File

@@ -1,5 +1,6 @@
import { Loader2, Sparkles } from 'lucide-react';
import { PlatformActionButton } from '../../common/PlatformActionButton';
import { PlatformStatusMessage } from '../../common/PlatformStatusMessage';
import { PlatformTextField } from '../../common/PlatformTextField';
@@ -31,14 +32,18 @@ export function PuzzleOnboardingView({
return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-[radial-gradient(circle_at_30%_15%,rgba(251,191,36,0.22),transparent_30%),linear-gradient(135deg,#0f172a,#111827_46%,#1e1b4b)] px-4 py-8 text-white">
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.045)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:38px_38px] opacity-30" />
<button
<PlatformActionButton
type="button"
surface="editorDark"
tone="ghost"
size="sm"
shape="pill"
disabled={isGenerating}
onClick={onSkip}
className="absolute right-4 top-4 z-10 inline-flex min-h-10 items-center justify-center rounded-full border border-white/14 bg-black/24 px-4 text-sm font-black text-white/86 shadow-[0_12px_28px_rgba(0,0,0,0.22)] backdrop-blur transition hover:border-amber-200/45 hover:text-amber-100 disabled:cursor-not-allowed disabled:opacity-45 sm:right-6 sm:top-6"
className="absolute right-4 top-4 z-10 min-h-10 shadow-[0_12px_28px_rgba(0,0,0,0.22)] backdrop-blur sm:right-6 sm:top-6"
>
</button>
</PlatformActionButton>
<section className="relative flex w-full max-w-[34rem] flex-col items-center gap-5 text-center">
<div className="grid h-14 w-14 place-items-center rounded-[1.2rem] border border-amber-200/32 bg-amber-200/14 text-amber-100 shadow-[0_18px_48px_rgba(251,191,36,0.18)]">
{isGenerating ? (
@@ -70,10 +75,13 @@ export function PuzzleOnboardingView({
rows={4}
className="min-h-32 rounded-[1.25rem] border-white/14 bg-black/28 py-4 leading-7 shadow-[0_18px_50px_rgba(0,0,0,0.24)] backdrop-blur placeholder:text-white/42 focus:border-amber-200/70 focus:ring-2 focus:ring-amber-200/20 disabled:opacity-70"
/>
<button
<PlatformActionButton
type="submit"
surface="editorDark"
tone="accent"
size="lg"
fullWidth
disabled={!canSubmit}
className="inline-flex min-h-12 items-center justify-center gap-2 rounded-[1rem] bg-amber-200 px-5 text-sm font-black text-slate-950 transition hover:bg-amber-100 disabled:cursor-not-allowed disabled:opacity-45"
>
{isGenerating ? (
<>
@@ -83,7 +91,7 @@ export function PuzzleOnboardingView({
) : (
'生成'
)}
</button>
</PlatformActionButton>
</form>
{error ? (
<PlatformStatusMessage
@@ -124,11 +132,14 @@ export function PuzzleOnboardingLoginOverlay({
)}
</div>
<h2 className="text-2xl font-black leading-tight">{copy}</h2>
<button
<PlatformActionButton
type="button"
surface="editorDark"
tone="accent"
size="lg"
fullWidth
disabled={isSaving}
onClick={onLogin}
className="inline-flex min-h-12 w-full items-center justify-center gap-2 rounded-[1rem] bg-amber-200 px-5 text-sm font-black text-slate-950 transition hover:bg-amber-100 disabled:cursor-not-allowed disabled:opacity-45"
>
{isSaving ? (
<>
@@ -138,7 +149,7 @@ export function PuzzleOnboardingLoginOverlay({
) : (
'注册账号 / 登录'
)}
</button>
</PlatformActionButton>
{error ? (
<PlatformStatusMessage
tone="error"