diff --git a/packages/core/src/components/ActionPanel.tsx b/packages/core/src/components/ActionPanel.tsx index 8f4889e..6449210 100644 --- a/packages/core/src/components/ActionPanel.tsx +++ b/packages/core/src/components/ActionPanel.tsx @@ -7,7 +7,7 @@ import { } from '@react-trace/ui-components' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import type { ReactNode } from 'react' -import { useCallback } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { portalContainerAtom, @@ -257,6 +257,8 @@ export function ActionPanel({ plugins }: ActionPanelProps) { ) } +const SUBMENU_CLOSE_DELAY_MS = 150 + function Submenu({ entryContent, plugins, @@ -268,15 +270,40 @@ function Submenu({ }) { const portalContainer = useAtomValue(portalContainerAtom) const setSelectedSource = useSetAtom(selectedSourceAtom) + const [open, setOpen] = useState(false) + const closeTimerRef = useRef(undefined) + + useEffect(() => { + return () => window.clearTimeout(closeTimerRef.current) + }, []) + + const keepOpen = useCallback(() => { + window.clearTimeout(closeTimerRef.current) + setOpen(true) + setSelectedSource(source) + }, [source, setSelectedSource]) + + const scheduleClose = useCallback(() => { + window.clearTimeout(closeTimerRef.current) + closeTimerRef.current = window.setTimeout(() => setOpen(false), SUBMENU_CLOSE_DELAY_MS) + }, []) return ( open && setSelectedSource(source)} + open={open} + onOpenChange={(next) => { + setOpen(next) + if (next) { + window.clearTimeout(closeTimerRef.current) + setSelectedSource(source) + } + }} > entryStyle(state.open)} > {entryContent} @@ -301,6 +328,8 @@ function Submenu({ style={{ zIndex: 999999, pointerEvents: 'auto' }} > 0 ? 4 : 0,