refactor(client): share the outside-pointer dismissal hook

The jobs list and the Cordis panel carried identical outside-pointerdown
close effects, which the duplication gate rejects; both now use
useDismissOnOutsidePointer from ui-primitives.

Refs #2526
This commit is contained in:
Yif
2026-08-13 19:39:10 +08:00
parent c5187d38a5
commit b5deda1f05
4 changed files with 34 additions and 22 deletions

View File

@@ -13,6 +13,7 @@ export { Input } from './Input.tsx'
export { Menu } from './Menu.tsx'
export type { MenuEntry, MenuItem, MenuSeparator, MenuLabel } from './Menu.tsx'
export { useAnchoredMaxHeight } from './useAnchoredMaxHeight.ts'
export { useDismissOnOutsidePointer } from './useDismissOnOutsidePointer.ts'
export { HoverCard } from './HoverCard.tsx'
export { Modal } from './Modal.tsx'
export { OnboardingSurface } from './OnboardingSurface.tsx'

View File

@@ -0,0 +1,29 @@
/**
* Outside-pointer dismissal for trigger-owned popovers (jobs list, Cordis
* panel): while the surface is open, a pointerdown outside the root closes it.
*/
import { useEffect } from 'react'
import type { RefObject } from 'react'
/**
* Close an open popover when a pointerdown lands outside its root element.
* @param root - element containing both the trigger and the open surface.
* @param open - whether the surface is showing; false detaches the listener.
* @param setOpen - state setter invoked with false on an outside pointerdown.
*/
export function useDismissOnOutsidePointer(
root: RefObject<HTMLElement | null>,
open: boolean,
setOpen: (open: boolean) => void,
): void {
useEffect(() => {
if (!open) return
const closeOutside = (event: PointerEvent): void => {
if (event.target instanceof Node && !root.current?.contains(event.target)) {
setOpen(false)
}
}
document.addEventListener('pointerdown', closeOutside)
return () => { document.removeEventListener('pointerdown', closeOutside) }
}, [root, open, setOpen])
}