移除画风菜单说明文案并按需加载预览图
删除画风菜单默认操作说明与规则解释文案 画风预览图改为悬停或键盘聚焦时挂载单张图片 补充指针与键盘预览回归测试
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from 'react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { GenerateDialogState } from './ImageCanvasEditorTypes';
|
||||
import { ImageCanvasSceneStyleControl } from './ImageCanvasSceneStyleControl';
|
||||
|
||||
function SceneStyleControlHarness() {
|
||||
const [dialog, setDialog] = useState<GenerateDialogState | null>({
|
||||
mode: 'scene',
|
||||
prompt: '',
|
||||
status: 'idle',
|
||||
sceneStylePreset: 'anime',
|
||||
});
|
||||
|
||||
return dialog ? (
|
||||
<ImageCanvasSceneStyleControl
|
||||
dialog={dialog}
|
||||
setGenerateDialog={setDialog}
|
||||
renderEditorPortal={(node) => node}
|
||||
buildPortalMenuStyle={() => ({ position: 'fixed', left: 0, top: 0 })}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
describe('ImageCanvasSceneStyleControl', () => {
|
||||
it('仅在指针停留于对应预览按钮时挂载一张预览图', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<SceneStyleControlHarness />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '画风预设 日系动画' }));
|
||||
|
||||
expect(screen.queryByRole('img')).toBeNull();
|
||||
|
||||
const animePreviewButton = screen.getByRole('button', {
|
||||
name: '预览日系动画画风',
|
||||
});
|
||||
await user.hover(animePreviewButton);
|
||||
|
||||
expect(
|
||||
screen.getByRole('img', { name: '日系动画固定画风预览' }),
|
||||
).toBeTruthy();
|
||||
expect(screen.getAllByRole('img')).toHaveLength(1);
|
||||
|
||||
await user.unhover(animePreviewButton);
|
||||
|
||||
expect(screen.queryByRole('img')).toBeNull();
|
||||
});
|
||||
|
||||
it('键盘聚焦预览按钮时挂载图片,焦点离开后卸载', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<SceneStyleControlHarness />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '画风预设 日系动画' }));
|
||||
await user.tab();
|
||||
await user.tab();
|
||||
|
||||
const animePreviewButton = screen.getByRole('button', {
|
||||
name: '预览日系动画画风',
|
||||
});
|
||||
expect(document.activeElement).toBe(animePreviewButton);
|
||||
expect(
|
||||
screen.getByRole('img', { name: '日系动画固定画风预览' }),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.tab();
|
||||
|
||||
expect(document.activeElement).toBe(
|
||||
screen.getByRole('menuitemradio', { name: /清透水彩/u }),
|
||||
);
|
||||
expect(screen.queryByRole('img')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -49,11 +49,20 @@ export function ImageCanvasSceneStyleControl({
|
||||
buildPortalMenuStyle,
|
||||
}: ImageCanvasSceneStyleControlProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [hoveredPreview, setHoveredPreview] =
|
||||
useState<EditorSceneStylePreset | null>(null);
|
||||
const [focusedPreview, setFocusedPreview] =
|
||||
useState<EditorSceneStylePreset | null>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const dismiss = useCallback(() => setIsOpen(false), []);
|
||||
const dismiss = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setHoveredPreview(null);
|
||||
setFocusedPreview(null);
|
||||
}, []);
|
||||
const selectedValue =
|
||||
dialog.sceneStylePreset ?? DEFAULT_EDITOR_SCENE_STYLE_PRESET;
|
||||
const selectedLabel = getEditorSceneStylePresetLabel(selectedValue);
|
||||
const activePreview = hoveredPreview ?? focusedPreview;
|
||||
|
||||
useImageCanvasFloatingOptionDismiss({
|
||||
isOpen,
|
||||
@@ -76,7 +85,7 @@ export function ImageCanvasSceneStyleControl({
|
||||
}
|
||||
: currentDialog,
|
||||
);
|
||||
setIsOpen(false);
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -88,7 +97,13 @@ export function ImageCanvasSceneStyleControl({
|
||||
aria-expanded={isOpen}
|
||||
disabled={dialog.status === 'generating'}
|
||||
trailingIcon={<ChevronDown className="h-3 w-3" />}
|
||||
onClick={() => setIsOpen((current) => !current)}
|
||||
onClick={() => {
|
||||
if (isOpen) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="image-canvas-editor__scene-style-trigger-label">
|
||||
<Palette className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
@@ -106,11 +121,11 @@ export function ImageCanvasSceneStyleControl({
|
||||
>
|
||||
<div className="image-canvas-editor__scene-style-menu-heading">
|
||||
<strong>画风预设</strong>
|
||||
<span>点击名称选择,悬停“预览”查看固定参考图</span>
|
||||
</div>
|
||||
{EDITOR_SCENE_STYLE_PRESET_OPTIONS.map((option) => {
|
||||
const selected = option.value === selectedValue;
|
||||
const preview = SCENE_STYLE_PREVIEWS[option.value];
|
||||
const previewActive = activePreview === option.value;
|
||||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
@@ -135,27 +150,47 @@ export function ImageCanvasSceneStyleControl({
|
||||
/>
|
||||
</button>
|
||||
{preview ? (
|
||||
<span className="image-canvas-editor__scene-style-preview-wrap">
|
||||
<span
|
||||
className="image-canvas-editor__scene-style-preview-wrap"
|
||||
onPointerEnter={() => setHoveredPreview(option.value)}
|
||||
onPointerLeave={() =>
|
||||
setHoveredPreview((current) =>
|
||||
current === option.value ? null : current,
|
||||
)
|
||||
}
|
||||
onFocus={() => setFocusedPreview(option.value)}
|
||||
onBlur={(event) => {
|
||||
if (
|
||||
!event.currentTarget.contains(event.relatedTarget)
|
||||
) {
|
||||
setFocusedPreview((current) =>
|
||||
current === option.value ? null : current,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="image-canvas-editor__scene-style-preview-button"
|
||||
aria-label={`预览${option.label}画风`}
|
||||
aria-expanded={previewActive}
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<Eye className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>预览</span>
|
||||
</button>
|
||||
<figure className="image-canvas-editor__scene-style-preview-card">
|
||||
<img
|
||||
src={preview}
|
||||
alt={`${option.label}固定画风预览`}
|
||||
/>
|
||||
<figcaption>
|
||||
<strong>{option.label}</strong>
|
||||
<span>固定预览图 · 不作为用户参考图</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
{previewActive ? (
|
||||
<figure className="image-canvas-editor__scene-style-preview-card">
|
||||
<img
|
||||
src={preview}
|
||||
alt={`${option.label}固定画风预览`}
|
||||
/>
|
||||
<figcaption>
|
||||
<strong>{option.label}</strong>
|
||||
</figcaption>
|
||||
</figure>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -16879,12 +16879,6 @@ button {
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.image-canvas-editor__scene-style-menu-heading span {
|
||||
color: #94a3b8;
|
||||
font-size: 0.67rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.image-canvas-editor__scene-style-row {
|
||||
position: relative;
|
||||
display: grid;
|
||||
@@ -17026,12 +17020,6 @@ button {
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.image-canvas-editor__scene-style-preview-card figcaption span {
|
||||
color: #94a3b8;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.image-canvas-editor__scene-style-anchor {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
Reference in New Issue
Block a user