Files
Mujoco_WASM/web_platform/src/components/ui/Popover.tsx
T
chenlin f4b415c54f
web-platform-ci / TypeScript、Lint、Unit、Build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
web-platform-ci / TypeScript、Lint、Unit、Build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
refactor(web-platform): release V0.6 精简代码
2026-08-28 14:10:16 +08:00

4 lines
1.6 KiB
TypeScript

/* eslint-disable react-hooks/refs -- refs are read only inside event callbacks passed to render props */
import {useCallback,useEffect,useRef,useState,type ReactNode} from 'react';
export function Popover({trigger,children,placement='bottom-right',label}:{trigger:(props:{open:boolean;toggle:()=>void})=>ReactNode;children:(props:{close:(restoreFocus?:boolean)=>void})=>ReactNode;placement?:'bottom-right'|'bottom-left'|'top-left';label:string}){const [open,setOpen]=useState(false),root=useRef<HTMLDivElement>(null),previous=useRef<HTMLElement|null>(null);const close=useCallback((restoreFocus=true)=>{setOpen(false);if(restoreFocus)previous.current?.focus();},[]);useEffect(()=>{if(!open)return;const pointer=(event:PointerEvent)=>{if(!root.current?.contains(event.target as Node))setOpen(false);},key=(event:KeyboardEvent)=>{if(event.key==='Escape'||((event.ctrlKey||event.metaKey)&&event.key.toLocaleLowerCase()==='k')){setOpen(false);previous.current?.focus();}};document.addEventListener('pointerdown',pointer);document.addEventListener('keydown',key);return()=>{document.removeEventListener('pointerdown',pointer);document.removeEventListener('keydown',key);};},[open]);const position=placement==='top-left'?'bottom-8 left-0':placement==='bottom-left'?'left-0 top-9':'right-0 top-9';return <div ref={root} className="relative">{trigger({open,toggle:()=>{if(!open)previous.current=document.activeElement instanceof HTMLElement?document.activeElement:null;setOpen(value=>!value);}})}{open&&<section role="dialog" aria-label={label} className={`absolute z-50 ${position}`}>{children({close})}</section>}</div>;}