{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rich-tooltip",
  "title": "Rich Tooltip",
  "description": "The rich tooltip: a title, body text, and optional actions.",
  "dependencies": [
    "@base-ui/react@^1.7.0"
  ],
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/rich-tooltip.tsx",
      "content": "import { useCallback, useEffect, useRef, useState, type ComponentProps, type ReactNode } from \"react\"\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\"\n\nimport { cn } from \"@/lib/utils\"\nimport { RichTooltipContext, useRichTooltip } from \"./rich-tooltip-context\"\n\ntype RichTooltipProps = {\n  children?: ReactNode\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n  open?: boolean\n}\n\nfunction RichTooltip({\n  defaultOpen = false,\n  onOpenChange,\n  open: openProp,\n  children,\n}: RichTooltipProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)\n  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n  const open = openProp ?? uncontrolledOpen\n  const triggerRef = useRef<HTMLButtonElement | null>(null)\n  const focusSessionRef = useRef(false)\n  const restoringFocusRef = useRef(false)\n  const wasOpenRef = useRef(open)\n  const setOpen = useCallback((next: boolean) => {\n    if (openProp === undefined) setUncontrolledOpen(next)\n    onOpenChange?.(next)\n  }, [onOpenChange, openProp])\n  const cancelClose = useCallback(() => {\n    if (timerRef.current) clearTimeout(timerRef.current)\n    timerRef.current = null\n  }, [])\n  const openNow = useCallback(() => {\n    if (restoringFocusRef.current) return\n    cancelClose()\n    setOpen(true)\n  }, [cancelClose, setOpen])\n  const closeSoon = useCallback(() => {\n    cancelClose()\n    timerRef.current = setTimeout(() => setOpen(false), 120)\n  }, [cancelClose, setOpen])\n  const noteFocus = useCallback(() => {\n    focusSessionRef.current = true\n  }, [])\n  const setTriggerNode = useCallback((node: HTMLButtonElement | null) => {\n    triggerRef.current = node\n  }, [])\n\n  useEffect(() => cancelClose, [cancelClose])\n  useEffect(() => {\n    if (!open && wasOpenRef.current && focusSessionRef.current) {\n      const frame = requestAnimationFrame(() => {\n        restoringFocusRef.current = true\n        triggerRef.current?.focus()\n        restoringFocusRef.current = false\n      })\n      focusSessionRef.current = false\n      wasOpenRef.current = open\n      return () => cancelAnimationFrame(frame)\n    }\n    wasOpenRef.current = open\n  }, [open])\n\n  return (\n    <RichTooltipContext.Provider value={{ cancelClose, closeSoon, noteFocus, openNow, setTriggerNode }}>\n      <PopoverPrimitive.Root\n        open={open}\n        onOpenChange={(nextOpen) => {\n          if (openProp === undefined) setUncontrolledOpen(nextOpen)\n          onOpenChange?.(nextOpen)\n        }}\n      >\n        {children}\n      </PopoverPrimitive.Root>\n    </RichTooltipContext.Provider>\n  )\n}\n\nfunction RichTooltipTrigger({\n  onBlur,\n  onFocus,\n  onPointerEnter,\n  onPointerLeave,\n  ref,\n  ...props\n}: PopoverPrimitive.Trigger.Props) {\n  const context = useRichTooltip()\n  return (\n    <PopoverPrimitive.Trigger\n      {...props}\n      ref={(node: HTMLButtonElement | null) => {\n        context.setTriggerNode(node)\n        if (typeof ref === \"function\") ref(node)\n        else if (ref) ref.current = node\n      }}\n      data-slot=\"rich-tooltip-trigger\"\n      onPointerEnter={(event) => {\n        onPointerEnter?.(event)\n        if (!event.defaultPrevented) context.openNow()\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event)\n        if (!event.defaultPrevented) context.closeSoon()\n      }}\n      onFocus={(event) => {\n        onFocus?.(event)\n        if (!event.defaultPrevented) {\n          context.noteFocus()\n          context.openNow()\n        }\n      }}\n      onBlur={(event) => {\n        onBlur?.(event)\n        if (!event.defaultPrevented) context.closeSoon()\n      }}\n    />\n  )\n}\n\nfunction RichTooltipContent({\n  align = \"center\",\n  alignOffset = 0,\n  children,\n  className,\n  onBlur,\n  onFocus,\n  onPointerEnter,\n  onPointerLeave,\n  side = \"top\",\n  sideOffset = 8,\n  ...props\n}: PopoverPrimitive.Popup.Props & Pick<PopoverPrimitive.Positioner.Props, \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\">) {\n  const context = useRichTooltip()\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} className=\"isolate z-(--m3-z-menu)\">\n        <PopoverPrimitive.Popup\n          {...props}\n          data-slot=\"rich-tooltip-content\"\n          className={cn(\n            // Kit: 312dp, 12dp corners, 12 top / 16 side / 8 bottom padding, 4dp\n            // between subhead and body.\n            \"flex w-[312px] max-w-[calc(100vw-2rem)] flex-col gap-1 rounded-m3-md bg-m3-surface-container px-4 pt-3 pb-2 text-foreground shadow-m3-2 outline-none\",\n            \"origin-(--transform-origin) transition-[transform,opacity] duration-(--m3-spring-effects-fast-duration) data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:scale-95 data-ending-style:opacity-0 motion-reduce:transition-none\",\n            className,\n          )}\n          onPointerEnter={(event) => {\n            onPointerEnter?.(event)\n            if (!event.defaultPrevented) context.cancelClose()\n          }}\n          onPointerLeave={(event) => {\n            onPointerLeave?.(event)\n            if (!event.defaultPrevented) context.closeSoon()\n          }}\n          onFocus={(event) => {\n            onFocus?.(event)\n            if (!event.defaultPrevented) {\n              context.noteFocus()\n              context.cancelClose()\n            }\n          }}\n          onBlur={(event) => {\n            onBlur?.(event)\n            if (!event.defaultPrevented) context.closeSoon()\n          }}\n        >\n          {children}\n        </PopoverPrimitive.Popup>\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  )\n}\n\nfunction RichTooltipTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n  return <PopoverPrimitive.Title {...props} data-slot=\"rich-tooltip-title\" className={cn(\"text-m3-title-sm text-m3-on-surface-variant\", className)} />\n}\n\nfunction RichTooltipDescription({ className, ...props }: PopoverPrimitive.Description.Props) {\n  return <PopoverPrimitive.Description {...props} data-slot=\"rich-tooltip-description\" className={cn(\"text-m3-body-md text-muted-foreground\", className)} />\n}\n\nfunction RichTooltipActions({ className, ...props }: ComponentProps<\"div\">) {\n  return <div {...props} data-slot=\"rich-tooltip-actions\" className={cn(\"-mx-3 flex items-center gap-2 pt-1\", className)} />\n}\n\nexport {\n  RichTooltip,\n  RichTooltipActions,\n  RichTooltipContent,\n  RichTooltipDescription,\n  RichTooltipTitle,\n  RichTooltipTrigger,\n  type RichTooltipProps,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/rich-tooltip-context.ts",
      "content": "import { createContext, useContext } from \"react\"\n\ntype RichTooltipContextValue = {\n  cancelClose: () => void\n  closeSoon: () => void\n  noteFocus: () => void\n  openNow: () => void\n  setTriggerNode: (node: HTMLButtonElement | null) => void\n}\n\nconst RichTooltipContext = createContext<RichTooltipContextValue | null>(null)\n\nfunction useRichTooltip() {\n  const context = useContext(RichTooltipContext)\n  if (!context) throw new Error(\"RichTooltip components must be used within RichTooltip\")\n  return context\n}\n\nexport { RichTooltipContext, useRichTooltip }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}