{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "base-tooltip",
  "title": "Base Tooltip",
  "description": "An accessible tooltip built on Base UI with spring-based keyframe scale and translation animations powered by Motion.",
  "dependencies": ["@base-ui/react", "motion"],
  "registryDependencies": ["utils", "@soralabs/hooks-use-controlled-state"],
  "files": [
    {
      "path": "registry/ui/base/tooltip/index.tsx",
      "content": "\"use client\";\n\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\nimport { useControlledState } from \"@/hooks/use-controlled-state\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  AnimatePresence,\n  motion,\n  type Transition,\n  useReducedMotion,\n} from \"motion/react\";\nimport { createContext, useContext, useMemo, useState } from \"react\";\n\ninterface TooltipContextValue {\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  setSide: (side: TooltipPrimitive.Positioner.Props[\"side\"]) => void;\n  side: TooltipPrimitive.Positioner.Props[\"side\"];\n}\n\nconst TooltipContext = createContext<TooltipContextValue | null>(null);\n\nfunction useTooltipContext() {\n  return useContext(TooltipContext);\n}\n\nconst defaultSpringTransition: Transition = {\n  visualDuration: 0.3,\n  type: \"spring\",\n  bounce: 0.6,\n};\n\ntype TooltipProviderProps = TooltipPrimitive.Provider.Props;\n\n/**\n * Provides shared delay and grouping settings for tooltips.\n */\nfunction TooltipProvider({ delay = 0, ...props }: TooltipProviderProps) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\ntype TooltipProps = TooltipPrimitive.Root.Props;\n\n/**\n * Root container for the Tooltip component powered by Base UI and Motion.\n */\nfunction Tooltip({\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  children,\n  ...props\n}: TooltipProps) {\n  const [open, setOpen] = useControlledState({\n    defaultValue: defaultOpen,\n    onChange: (nextOpen: boolean) => {\n      onOpenChange?.(\n        nextOpen,\n        undefined as unknown as TooltipPrimitive.Root.ChangeEventDetails\n      );\n    },\n    value: controlledOpen,\n  });\n  const [side, setSide] =\n    useState<TooltipPrimitive.Positioner.Props[\"side\"]>(\"top\");\n\n  const contextValue = useMemo(\n    () => ({\n      open: Boolean(open),\n      setOpen,\n      side,\n      setSide,\n    }),\n    [open, setOpen, side]\n  );\n\n  return (\n    <TooltipContext.Provider value={contextValue}>\n      <TooltipPrimitive.Root\n        data-slot=\"tooltip\"\n        onOpenChange={(nextOpen, eventDetails) => {\n          setOpen(nextOpen);\n          onOpenChange?.(nextOpen, eventDetails);\n        }}\n        open={open}\n        {...props}\n      >\n        {children}\n      </TooltipPrimitive.Root>\n    </TooltipContext.Provider>\n  );\n}\n\ntype TooltipTriggerProps = TooltipPrimitive.Trigger.Props;\n\n/**\n * Interactive element that activates the tooltip on hover or keyboard focus.\n */\nfunction TooltipTrigger({ ...props }: TooltipTriggerProps) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\ninterface TooltipContentProps\n  extends TooltipPrimitive.Popup.Props,\n    Pick<\n      TooltipPrimitive.Positioner.Props,\n      \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n    > {\n  /**\n   * Custom spring transition config for the entrance and exit animation.\n   * @default { visualDuration: 0.3, type: \"spring\", bounce: 0.6 }\n   */\n  transition?: Transition;\n}\n\n/**\n * The floating popup content container animated via Motion spring physics.\n */\nfunction TooltipContent({\n  className,\n  side = \"top\",\n  sideOffset = 4,\n  align = \"center\",\n  alignOffset = 0,\n  children,\n  render,\n  style,\n  transition = defaultSpringTransition,\n  ...props\n}: TooltipContentProps) {\n  const context = useTooltipContext();\n  const shouldReduceMotion = useReducedMotion();\n  const isOpen = context?.open ?? true;\n\n  if (context && context.side !== side) {\n    context.setSide(side);\n  }\n\n  const offset = useMemo(() => {\n    switch (side) {\n      case \"bottom\":\n        return { x: 0, y: -8 };\n      case \"left\":\n      case \"inline-start\":\n        return { x: 8, y: 0 };\n      case \"right\":\n      case \"inline-end\":\n        return { x: -8, y: 0 };\n      default:\n        return { x: 0, y: 8 };\n    }\n  }, [side]);\n\n  const initialMotion = shouldReduceMotion\n    ? { opacity: 0 }\n    : { opacity: 0, scale: 0.8, x: offset.x, y: offset.y };\n\n  const animateMotion = shouldReduceMotion\n    ? { opacity: 1 }\n    : { opacity: 1, scale: 1, x: 0, y: 0 };\n\n  const exitMotion = shouldReduceMotion\n    ? { opacity: 0 }\n    : { opacity: 0, scale: 0.8, x: offset.x, y: offset.y };\n\n  return (\n    <AnimatePresence>\n      {isOpen && (\n        <TooltipPrimitive.Portal keepMounted>\n          <TooltipPrimitive.Positioner\n            align={align}\n            alignOffset={alignOffset}\n            className=\"isolate z-50\"\n            side={side}\n            sideOffset={sideOffset}\n          >\n            <TooltipPrimitive.Popup\n              className={cn(\n                \"z-50 inline-flex w-fit max-w-xs items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs shadow-md outline-none has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm\",\n                className\n              )}\n              data-slot=\"tooltip-content\"\n              render={\n                render ?? (\n                  <motion.div\n                    animate={animateMotion}\n                    exit={{\n                      ...exitMotion,\n                      transition: shouldReduceMotion\n                        ? { duration: 0.1 }\n                        : { duration: 0.15, ease: \"easeIn\" },\n                    }}\n                    initial={initialMotion}\n                    style={{\n                      transformOrigin: \"var(--transform-origin)\",\n                      willChange: \"transform, opacity\",\n                      backfaceVisibility: \"hidden\",\n                      WebkitFontSmoothing: \"subpixel-antialiased\",\n                      ...style,\n                    }}\n                    transition={\n                      shouldReduceMotion ? { duration: 0.1 } : transition\n                    }\n                  />\n                )\n              }\n              {...props}\n            >\n              {children}\n            </TooltipPrimitive.Popup>\n          </TooltipPrimitive.Positioner>\n        </TooltipPrimitive.Portal>\n      )}\n    </AnimatePresence>\n  );\n}\n\ntype TooltipArrowProps = TooltipPrimitive.Arrow.Props;\n\n/**\n * Optional arrow pointing towards the tooltip trigger.\n */\nfunction TooltipArrow({ className, ...props }: TooltipArrowProps) {\n  return (\n    <TooltipPrimitive.Arrow\n      className={cn(\n        \"z-50 size-2.5 rotate-45 rounded-[2px] bg-foreground fill-foreground\",\n        \"data-[side=bottom]:top-[-5px]\",\n        \"data-[side=top]:bottom-[-5px]\",\n        \"data-[side=right]:left-[-5px]\",\n        \"data-[side=left]:right-[-5px]\",\n        \"data-[side=inline-end]:left-[-5px]\",\n        \"data-[side=inline-start]:right-[-5px]\",\n        className\n      )}\n      data-slot=\"tooltip-arrow\"\n      {...props}\n    />\n  );\n}\n\nexport type {\n  TooltipArrowProps,\n  TooltipContentProps,\n  TooltipProps,\n  TooltipProviderProps,\n  TooltipTriggerProps,\n};\nexport {\n  defaultSpringTransition,\n  Tooltip,\n  TooltipArrow,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n};\n",
      "type": "registry:ui",
      "target": "components/sora-ui/base/tooltip.tsx"
    }
  ],
  "meta": {
    "agentMetadata": {
      "intent": "Provide contextual hint or shortcut explanation when hovering or focusing an interactive element.",
      "role": "tooltip",
      "a11yConstraints": [
        "Render accessible name/description without trapping keyboard focus.",
        "Dismiss cleanly on Escape without activating underlying element."
      ],
      "motionEngine": "Motion spring scale, directional translation offset, and presence transition.",
      "compositionRules": [
        "Wrap interactive elements with TooltipTrigger using render prop or semantic button."
      ],
      "compositionRecipes": [
        "Compose with base/button for icon-only action button descriptions."
      ]
    }
  },
  "type": "registry:ui"
}
