Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useEffect, useId, useRef, useState } from "react"; | |
| import { PARAM_HELP, ParamKey } from "./paramHelpContent"; | |
| import { useParamHelp } from "./ParamHelpProvider"; | |
| interface Props { | |
| paramKey: ParamKey; | |
| label?: string; | |
| } | |
| /** | |
| * "?" trigger for a Parameters help entry. | |
| * | |
| * - Hover / focus shows the SHORT one-liner as a small popover tooltip | |
| * (same look-and-feel as the existing InfoTip). | |
| * - Click opens the rich detailed modal via ParamHelpProvider. | |
| * | |
| * On modal close, focus returns to this button. | |
| */ | |
| export default function ParamHelp({ paramKey, label }: Props) { | |
| const [tipOpen, setTipOpen] = useState(false); | |
| const wrapperRef = useRef<HTMLSpanElement>(null); | |
| const buttonRef = useRef<HTMLButtonElement>(null); | |
| const tooltipId = useId(); | |
| const meta = PARAM_HELP[paramKey]; | |
| const { open } = useParamHelp(); | |
| useEffect(() => { | |
| if (!tipOpen) return; | |
| function onDown(e: MouseEvent | TouchEvent) { | |
| if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { | |
| setTipOpen(false); | |
| } | |
| } | |
| function onKey(e: KeyboardEvent) { | |
| if (e.key === "Escape") setTipOpen(false); | |
| } | |
| document.addEventListener("mousedown", onDown); | |
| document.addEventListener("touchstart", onDown); | |
| document.addEventListener("keydown", onKey); | |
| return () => { | |
| document.removeEventListener("mousedown", onDown); | |
| document.removeEventListener("touchstart", onDown); | |
| document.removeEventListener("keydown", onKey); | |
| }; | |
| }, [tipOpen]); | |
| return ( | |
| <span | |
| ref={wrapperRef} | |
| className="relative inline-flex items-center align-middle" | |
| onMouseEnter={() => setTipOpen(true)} | |
| onMouseLeave={() => setTipOpen(false)} | |
| > | |
| <button | |
| ref={buttonRef} | |
| type="button" | |
| aria-label={label ?? `About ${meta.title}`} | |
| aria-describedby={tipOpen ? tooltipId : undefined} | |
| aria-haspopup="dialog" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| setTipOpen(false); | |
| open(paramKey, buttonRef.current); | |
| }} | |
| onFocus={() => setTipOpen(true)} | |
| onBlur={() => setTipOpen(false)} | |
| className="ml-1 inline-flex h-[18px] w-[18px] cursor-help items-center justify-center rounded-full border border-border bg-card text-[10px] font-semibold leading-none text-muted transition-colors hover:border-accent hover:text-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-accent" | |
| > | |
| ? | |
| </button> | |
| {tipOpen && ( | |
| <span | |
| id={tooltipId} | |
| role="tooltip" | |
| className="absolute left-0 top-6 z-30 w-72 rounded-md border border-border bg-card px-3 py-2 text-xs font-normal leading-snug text-ink shadow-[0_4px_18px_-6px_rgba(35,48,58,0.18)]" | |
| > | |
| {meta.short} | |
| <span className="mt-1 block text-[10.5px] text-muted"> | |
| click for details | |
| </span> | |
| </span> | |
| )} | |
| </span> | |
| ); | |
| } | |