{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hint-popover",
  "type": "registry:ui",
  "dependencies": [
    "@base-ui/react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://usva.build/r/overlay-core.json"
  ],
  "files": [
    {
      "path": "hint-popover.tsx",
      "target": "components/ui/hint-popover.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport { Popover as Base } from \"@base-ui/react/popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { useScopedTheme } from \"./use-scoped-theme\";\n\nexport type HintTone =\n  | \"neutral\"\n  | \"accent\"\n  | \"success\"\n  | \"warning\"\n  | \"danger\"\n  | \"info\";\n\nconst toneSkin: Record<HintTone, string> = {\n  neutral: \"border-border bg-surface-2\",\n  accent: \"border-accent/25 bg-accent/12\",\n  success: \"border-success/25 bg-success/12\",\n  warning: \"border-warning/25 bg-warning/12\",\n  danger: \"border-danger/25 bg-danger/12\",\n  info: \"border-info/25 bg-info/12\",\n};\n\nconst toneInk: Record<HintTone, string> = {\n  neutral: \"text-muted\",\n  accent: \"text-accent\",\n  success: \"text-success\",\n  warning: \"text-warning\",\n  danger: \"text-danger\",\n  info: \"text-info\",\n};\n\nexport interface HintPopoverProps {\n  /** The element the hint hangs off. Rendered as the popover trigger. */\n  trigger: React.ReactNode;\n  title?: React.ReactNode;\n  icon?: React.ReactNode;\n  /** Interactive footer, typically a dismiss Button. */\n  action?: React.ReactNode;\n  tone?: HintTone;\n  children: React.ReactNode;\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  /** Hover dwell before opening. Guards against drive-by pointers. */\n  openDelay?: number;\n  /**\n   * Grace period after the pointer leaves. Without it the panel closes while\n   * the pointer crosses the gap, and the action inside becomes unclickable.\n   */\n  closeDelay?: number;\n  className?: string;\n}\n\n/**\n * A hover-and-focus popover that may contain interactive content.\n *\n * This is not a Tooltip and must not be one: a tooltip is announced through\n * `aria-describedby` and its contents are unreachable by keyboard, so a dismiss\n * button inside one is a trap. Base UI's Popover has no hover trigger of its\n * own, so the open state is controlled here.\n */\nexport function HintPopover({\n  trigger,\n  title,\n  icon,\n  action,\n  tone = \"neutral\",\n  children,\n  side = \"top\",\n  openDelay = 120,\n  closeDelay = 200,\n  className,\n}: HintPopoverProps) {\n  const [open, setOpen] = React.useState(false);\n  const [probe, scopedTheme] = useScopedTheme();\n  const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(\n    undefined,\n  );\n  /**\n   * A pointer press focuses the trigger before Base UI's click handler toggles\n   * it. Without this, opening on focus would open the panel and the click would\n   * immediately close it again.\n   */\n  const focusFromPointer = React.useRef(false);\n  const hovering = React.useRef(false);\n  /**\n   * Closing restores focus to the trigger, which would re-open it on focus.\n   * Dismissing has to stick until focus genuinely leaves and comes back.\n   */\n  const suppressFocusOpen = React.useRef(false);\n\n  const cancel = React.useCallback(() => {\n    if (timer.current != null) clearTimeout(timer.current);\n    timer.current = undefined;\n  }, []);\n\n  React.useEffect(() => cancel, [cancel]);\n\n  const schedule = React.useCallback(\n    (next: boolean, delay: number) => {\n      cancel();\n      timer.current = setTimeout(() => setOpen(next), delay);\n    },\n    [cancel],\n  );\n\n  const hoverIn = (e: React.PointerEvent) => {\n    if (e.pointerType === \"touch\") return;\n    hovering.current = true;\n    schedule(true, openDelay);\n  };\n  const hoverOut = (e: React.PointerEvent) => {\n    if (e.pointerType === \"touch\") return;\n    hovering.current = false;\n    schedule(false, closeDelay);\n  };\n\n  return (\n    <Base.Root\n      open={open}\n      onOpenChange={(next, details) => {\n        cancel();\n        // A mouse press lands on a panel hover already opened, and Base UI's\n        // trigger toggles. While the pointer is over the trigger, hover owns\n        // closing, otherwise the click that opens it also shuts it.\n        if (!next && details.reason === \"trigger-press\" && hovering.current)\n          return;\n        if (!next) suppressFocusOpen.current = true;\n        setOpen(next);\n      }}\n    >\n      <Base.Trigger\n        render={trigger as React.ReactElement}\n        onPointerEnter={hoverIn}\n        onPointerLeave={hoverOut}\n        onPointerDown={() => {\n          focusFromPointer.current = true;\n        }}\n        onFocus={() => {\n          if (focusFromPointer.current) {\n            focusFromPointer.current = false;\n            return;\n          }\n          if (suppressFocusOpen.current) return;\n          cancel();\n          setOpen(true);\n        }}\n        onBlur={() => {\n          suppressFocusOpen.current = false;\n        }}\n      />\n      <span ref={probe} hidden />\n      <Base.Portal>\n        <Base.Positioner\n          data-theme={scopedTheme}\n          side={side}\n          sideOffset={8}\n          className=\"z-overlay\"\n        >\n          <Base.Popup\n            onPointerEnter={cancel}\n            onPointerLeave={hoverOut}\n            className={cn(\n              \"rim-light max-w-72 rounded-xl border p-3 shadow-floating\",\n              \"origin-[var(--transform-origin)] transition-enter duration-base ease-spring\",\n              \"motion-reduce:transition-none motion-reduce:transform-none\",\n              \"data-[starting-style]:translate-y-1 data-[starting-style]:scale-[0.97] data-[starting-style]:opacity-0 data-[starting-style]:blur-[2px]\",\n              \"data-[ending-style]:scale-[0.98] data-[ending-style]:opacity-0 data-[ending-style]:duration-fast data-[ending-style]:ease-soft\",\n              toneSkin[tone],\n              className,\n            )}\n          >\n            <div className=\"flex gap-2.5\">\n              {icon != null && (\n                <span\n                  className={cn(\n                    \"mt-0.5 shrink-0 [&_svg]:size-3.5\",\n                    toneInk[tone],\n                  )}\n                >\n                  {icon}\n                </span>\n              )}\n              <div className=\"min-w-0 flex-1\">\n                {title != null && (\n                  <Base.Title className=\"text-xs font-semibold leading-snug text-ink\">\n                    {title}\n                  </Base.Title>\n                )}\n                <Base.Description className=\"text-xs leading-snug text-muted\">\n                  {children}\n                </Base.Description>\n                {action != null && <div className=\"mt-2\">{action}</div>}\n              </div>\n            </div>\n          </Base.Popup>\n        </Base.Positioner>\n      </Base.Portal>\n    </Base.Root>\n  );\n}\nHintPopover.displayName = \"HintPopover\";\n"
    }
  ]
}