优化SFX生成面板界面

按设计稿重排音效 composer 的底部控件,并统一循环开关的选中态配色。

- 一键优化与撤销移入底部行,与时长、循环、ElevenLabs 和生成同处一排
- SFX footer 改 flex 并作用域隔离,共享的 5 列 grid 不受影响;模型 chip
  起分组作用,它与生成按钮靠右
- option-cluster 的 width: 100% 是给 grid 单元格写的,在 flex 行里会把基准
  宽度撑成整行再收缩,循环按钮因此被拉到 355px,改为按内容取宽
- footer 直接子 anchor 改 flex,消除块级盒与 inline-block 之间的基线空隙
- 时长按钮补时钟图标,新建音效默认改为自动时长
- 循环按钮补图标,Loop 改为「循环 开 / 关」,aria-label 同步
- 选中态由实心橙底配近白文字改为设计稿的淡橙底 + 橙字 + 橙描边;实底方案在
  通用 hover 用 background 简写换掉底色后会变成白底白字,hover 变体一并写死
- 删除 sound-effect-loop 选中态的重复规则,其声明与通用规则完全相同

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 11:48:32 +00:00
parent cad861a13d
commit 97de29a58f
5 changed files with 154 additions and 51 deletions
@@ -961,13 +961,20 @@ describe('ImageCanvasGenerationComposerView', () => {
'.image-canvas-editor__generation-composer-footer',
);
expect(footer).toBeTruthy();
// 底部一行:左侧一键优化与时长、循环,右侧 ElevenLabs + 生成。
expect(footer?.children[0]?.className).toContain(
'image-canvas-editor__sound-effect-prompt-actions',
);
expect(footer?.children[1]?.className).toContain(
'image-canvas-editor__option-popover-anchor--dimensions',
);
expect(footer?.children[2]?.className).toContain(
'image-canvas-editor__option-popover-anchor--model',
'image-canvas-editor__sound-effect-loop',
);
expect(footer?.children[3]?.className).toContain(
'image-canvas-editor__option-popover-anchor--model',
);
expect(footer?.children[4]?.className).toContain(
'image-canvas-editor__generation-submit',
);
const prompt = within(panel).getByRole('textbox', { name: 'prompt' });
@@ -988,7 +995,7 @@ describe('ImageCanvasGenerationComposerView', () => {
expect(within(panel).queryByText('单次')).toBeNull();
expect(
within(panel)
.getByRole('button', { name: 'Loop' })
.getByRole('button', { name: '循环 关' })
.getAttribute('aria-pressed'),
).toBe('false');
expect(within(panel).queryByText(/BPM/u)).toBeNull();
@@ -1034,7 +1041,7 @@ describe('ImageCanvasGenerationComposerView', () => {
expect(screen.getByLabelText('当前音效时长').textContent).toBe('8');
expect(screen.getByRole('button', { name: '音效时长 8秒' })).toBeTruthy();
fireEvent.click(within(panel).getByRole('button', { name: 'Loop' }));
fireEvent.click(within(panel).getByRole('button', { name: '循环 关' }));
fireEvent.submit(panel);
expect(onSubmitImageGeneration).toHaveBeenCalledWith(
@@ -1082,7 +1089,7 @@ describe('ImageCanvasGenerationComposerView', () => {
'一键优化',
'撤销',
'音效时长 5秒',
'Loop',
'循环 关',
'生成游戏音效',
]) {
expect(
@@ -1,10 +1,12 @@
import {
Check,
ChevronDown,
Clock,
Cpu,
Film,
ImageIcon,
Music,
Repeat,
Sparkles,
Undo2,
WandSparkles,
@@ -1183,7 +1185,7 @@ function ImageCanvasAudioGenerationComposerView({
return (
<form
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--audio"
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--audio image-canvas-editor__generation-composer--sound-effect"
style={style}
role="dialog"
aria-label={dialogLabel}
@@ -1255,48 +1257,48 @@ function ImageCanvasAudioGenerationComposerView({
{soundEffectDialog.errorMessage}
</PlatformStatusMessage>
) : null}
<div className="image-canvas-editor__background-music-prompt-actions image-canvas-editor__sound-effect-prompt-actions">
<PlatformActionButton
type="button"
tone="secondary"
size="xs"
shape="pill"
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__sound-effect-prompt-action--optimize"
aria-label="一键优化"
disabled={!canOptimize}
onClick={() => {
void controller.optimizePrompt(soundEffectDialog.id);
}}
>
<span
className="image-canvas-editor__background-music-prompt-action-icon"
aria-hidden="true"
>
<WandSparkles />
</span>
<span>{isAiProcessing ? '优化中' : '一键优化'}</span>
</PlatformActionButton>
{isUndoVisible ? (
<div className="image-canvas-editor__generation-composer-footer">
<div className="image-canvas-editor__background-music-prompt-actions image-canvas-editor__sound-effect-prompt-actions">
<PlatformActionButton
type="button"
tone="ghost"
tone="secondary"
size="xs"
shape="pill"
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__background-music-prompt-action--undo"
disabled={!canUndo}
onClick={() => controller.undoPrompt(soundEffectDialog.id)}
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__sound-effect-prompt-action--optimize"
aria-label="一键优化"
disabled={!canOptimize}
onClick={() => {
void controller.optimizePrompt(soundEffectDialog.id);
}}
>
<span
className="image-canvas-editor__background-music-prompt-action-icon"
aria-hidden="true"
>
<Undo2 />
<WandSparkles />
</span>
<span></span>
<span>{isAiProcessing ? '优化中' : '一键优化'}</span>
</PlatformActionButton>
) : null}
</div>
<div className="image-canvas-editor__generation-composer-footer">
{isUndoVisible ? (
<PlatformActionButton
type="button"
tone="ghost"
size="xs"
shape="pill"
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__background-music-prompt-action--undo"
disabled={!canUndo}
onClick={() => controller.undoPrompt(soundEffectDialog.id)}
>
<span
className="image-canvas-editor__background-music-prompt-action-icon"
aria-hidden="true"
>
<Undo2 />
</span>
<span></span>
</PlatformActionButton>
) : null}
</div>
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--dimensions">
<PlatformInlineOptionButton
ref={soundOptionsButtonRef}
@@ -1307,7 +1309,13 @@ function ImageCanvasAudioGenerationComposerView({
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => setIsSoundOptionsOpen((isOpen) => !isOpen)}
>
{soundOptionLabel}
<span
className="image-canvas-editor__sound-effect-option-icon"
aria-hidden="true"
>
<Clock />
</span>
<span>{soundOptionLabel}</span>
</PlatformInlineOptionButton>
{isSoundOptionsOpen
? renderEditorPortal(
@@ -1366,12 +1374,21 @@ function ImageCanvasAudioGenerationComposerView({
</div>
<PlatformInlineOptionButton
className="image-canvas-editor__option-cluster image-canvas-editor__sound-effect-loop"
aria-label="Loop"
aria-label={`循环 ${soundLoop ? '开' : '关'}`}
aria-pressed={soundLoop}
disabled={isLocked}
onClick={() => updateAudioDialog({ soundLoop: !soundLoop })}
>
Loop
<span
className="image-canvas-editor__sound-effect-option-icon"
aria-hidden="true"
>
<Repeat />
</span>
<span></span>
<span className="image-canvas-editor__sound-effect-loop-state">
{soundLoop ? '开' : '关'}
</span>
</PlatformInlineOptionButton>
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--model image-canvas-editor__readonly-generation-option">
<PlatformInlineOptionButton
@@ -254,7 +254,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
status: 'idle',
composerOpen: true,
soundModel: 'eleven_text_to_sound_v2',
soundDurationMode: 'manual',
soundDurationMode: 'auto',
soundDurationSeconds: 5,
soundLoop: false,
placeholder: {
@@ -404,7 +404,8 @@ export function createSoundEffectGenerationDialogDraft({
status: 'idle',
composerOpen: true,
soundModel: DEFAULT_SOUND_EFFECT_MODEL,
soundDurationMode: 'manual',
// 与设计稿一致:新建音效默认自动时长,由模型按描述决定长度。
soundDurationMode: 'auto',
soundDurationSeconds: DEFAULT_SOUND_EFFECT_DURATION_SECONDS,
soundLoop: false,
placeholder: {
+88 -10
View File
@@ -7541,11 +7541,18 @@ button.image-canvas-editor__reference-chip:disabled {
box-shadow: none;
}
/*
* 开启态按设计稿取淡橙底 + 橙字 + 橙描边不用实心橙底配近白文字实底方案在通用 hover
* 把底色换成 #f8fafc 后会变成白底白字hover 变体必须一并写死否则通用 hover
* `background` 简写仍会把淡橙底洗掉阴影仍由通用规则提供悬停反馈不丢
*/
.image-canvas-editor__generation-composer
.platform-inline-option-button[aria-pressed='true'] {
.platform-inline-option-button[aria-pressed='true'],
.image-canvas-editor__generation-composer
.platform-inline-option-button[aria-pressed='true']:hover:not(:disabled) {
border-color: var(--image-canvas-brand-border-strong);
background: var(--image-canvas-brand-fill);
color: var(--image-canvas-brand-text-on-fill);
background: var(--image-canvas-brand-soft);
color: var(--image-canvas-brand-accent);
}
.image-canvas-editor__generation-submit {
@@ -16892,8 +16899,85 @@ button {
min-width: 0;
}
/*
* 助手动作与时长循环模型和生成同处底部一行共享 footer 5 grid 在这里换成
* flex作用域限定在 SFX图片 / 视频 composer 仍走原来的 grid
*/
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__generation-composer-footer {
display: flex;
align-items: center;
gap: 0.4rem;
padding-top: 0.5rem;
}
/*
* `.image-canvas-editor__option-cluster` `width: 100%` 是给 grid 单元格写的 flex
* 行里会把基准宽度撑成整行再收缩循环按钮是 footer 的直接子元素必须改回按内容取宽
* 时长和模型包在 anchor 宽度由 anchor 收敛不受影响
*/
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__generation-composer-footer
> .image-canvas-editor__option-cluster {
width: auto;
}
/* anchor 是块级盒、内部按钮是 inline-block,行盒下沿会多出基线空隙导致高度错位。 */
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__generation-composer-footer
> .image-canvas-editor__option-popover-anchor {
display: flex;
align-items: center;
}
/* 模型 chip 起分组作用:它和生成按钮被顶到右侧,左侧留给动作与参数。 */
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__option-popover-anchor--model {
margin-left: auto;
}
/* flex 布局下提交按钮硬编码的 grid-column 自动失效,无需覆盖。 */
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__generation-submit {
height: 2.25rem;
min-height: 0;
}
.image-canvas-editor__sound-effect-prompt-actions {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 0.4rem;
min-width: 0;
}
/* 共享的 label 是无 gap 的 inline-flex,加了图标和状态后必须自己撑开间距。 */
.image-canvas-editor__generation-composer--sound-effect
.image-canvas-editor__option-cluster
.platform-inline-option-button__label {
gap: 0.3rem;
}
/* 时长与循环的前置图标,尺寸对齐助手按钮里的图标。 */
.image-canvas-editor__sound-effect-option-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.image-canvas-editor__sound-effect-option-icon svg {
width: 0.85rem;
height: 0.85rem;
}
/* 开 / 关状态跟在「循环」后面,用弱化色,避免和标签抢读。 */
.image-canvas-editor__sound-effect-loop-state {
color: #94a3b8;
font-weight: 700;
}
.image-canvas-editor__sound-effect-loop[aria-pressed='true']
.image-canvas-editor__sound-effect-loop-state {
color: inherit;
}
.image-canvas-editor__sound-effect-prompt-action--optimize:not(:disabled) {
@@ -16920,12 +17004,6 @@ button {
margin: 0;
}
.image-canvas-editor__sound-effect-loop[aria-pressed='true'] {
border-color: var(--image-canvas-brand-border-strong);
background: var(--image-canvas-brand-soft);
color: var(--image-canvas-brand-accent);
}
/*
* 助手动作按钮`.platform-button` 给的是 2.9rem 高与 0.7rem/1rem 内边距比同一面板里
* 2.25rem 的生成按钮高出一截这里按两级选择器压回去避免整排按钮比主操作还重