补齐侧栏菜单失焦关闭

我的和帮助菜单点击外部或焦点离开后自动收起

保留菜单内部点击交互不被提前关闭

补充侧栏菜单关闭行为测试
This commit is contained in:
AIGameCreator App
2026-07-08 20:31:22 +08:00
parent 1c88b924d5
commit 788c085684
2 changed files with 57 additions and 2 deletions
+42 -2
View File
@@ -2176,6 +2176,8 @@ export function WorkspaceLauncher({
>([]);
const [showcaseStatus, setShowcaseStatus] = useState('正在读取灵感');
const homeAttachmentInputRef = useRef<HTMLInputElement | null>(null);
const accountMenuRef = useRef<HTMLDivElement | null>(null);
const helpMenuRef = useRef<HTMLDivElement | null>(null);
const launcherAgentChatAgents = deriveAgentStatusCards(seedManifest, null);
const [agentChatProjectPath, setAgentChatProjectPath] =
useState(defaultProjectPath);
@@ -2268,6 +2270,44 @@ export function WorkspaceLauncher({
void loadWalletLedger();
}, [walletPanelOpen, walletLedgerStatus]);
useEffect(() => {
function closeSidebarMenusIfOutside(target: EventTarget | null) {
if (!(target instanceof Node)) {
return;
}
if (
accountMenuRef.current?.contains(target) ||
helpMenuRef.current?.contains(target)
) {
return;
}
setAccountMenuOpen(false);
setHelpMenuOpen(false);
}
function handlePointerDown(event: PointerEvent) {
closeSidebarMenusIfOutside(event.target);
}
function handleFocusIn(event: FocusEvent) {
closeSidebarMenusIfOutside(event.target);
}
function handleWindowBlur() {
setAccountMenuOpen(false);
setHelpMenuOpen(false);
}
document.addEventListener('pointerdown', handlePointerDown);
document.addEventListener('focusin', handleFocusIn);
window.addEventListener('blur', handleWindowBlur);
return () => {
document.removeEventListener('pointerdown', handlePointerDown);
document.removeEventListener('focusin', handleFocusIn);
window.removeEventListener('blur', handleWindowBlur);
};
}, []);
function validateProjectPath(nextProjectPath: string) {
const trimmedProjectPath = nextProjectPath.trim();
if (!trimmedProjectPath || !isAbsoluteProjectPath(trimmedProjectPath)) {
@@ -2993,7 +3033,7 @@ export function WorkspaceLauncher({
</button>
</nav>
<div className="launcher-sidebar-bottom">
<div className="launcher-account-nav">
<div className="launcher-account-nav" ref={accountMenuRef}>
<button
type="button"
className="launcher-config-button"
@@ -3042,7 +3082,7 @@ export function WorkspaceLauncher({
</div>
) : null}
</div>
<div className="launcher-help-nav">
<div className="launcher-help-nav" ref={helpMenuRef}>
<button
type="button"
aria-label="帮助"
@@ -850,6 +850,16 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(
screen.getByRole('menuitem', { name: '联系我们' }),
).not.toBeNull();
fireEvent.pointerDown(screen.getByLabelText('首页输入'));
expect(
screen.queryByRole('menuitem', { name: '联系我们' }),
).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '帮助' }));
fireEvent.focusIn(screen.getByRole('button', { name: '首页' }));
expect(
screen.queryByRole('menuitem', { name: '联系我们' }),
).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '帮助' }));
fireEvent.click(screen.getByRole('menuitem', { name: '联系我们' }));
expect(screen.getByRole('dialog', { name: '联系我们' })).not.toBeNull();
expect(
@@ -861,6 +871,11 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(
screen.getByRole('menuitem', { name: '会员与升级' }),
).not.toBeNull();
fireEvent.pointerDown(screen.getByLabelText('首页输入'));
expect(
screen.queryByRole('menuitem', { name: '会员与升级' }),
).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '我的' }));
fireEvent.click(screen.getByRole('menuitem', { name: '会员与升级' }));
expect(screen.getByRole('dialog', { name: '会员与升级' })).not.toBeNull();
expect(