{
  "name": "animated-tooltip",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "ui/animated-tooltip.tsx",
      "content": "\"use client\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type React from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Placement =\n  | \"top\"\n  | \"bottom\"\n  | \"left\"\n  | \"right\"\n  | \"top-start\"\n  | \"top-end\"\n  | \"bottom-start\"\n  | \"bottom-end\";\ntype Animation = \"fade\" | \"scale\" | \"slide\" | \"spring\";\n\n// Animated Tooltip\ninterface AnimatedTooltipProps {\n  children: React.ReactNode;\n  content: React.ReactNode;\n  placement?: Placement;\n  animation?: Animation;\n  delay?: number;\n  duration?: number;\n  className?: string;\n  contentClassName?: string;\n  arrow?: boolean;\n  offset?: number;\n  disabled?: boolean;\n}\n\nexport function AnimatedTooltip({\n  children,\n  content,\n  placement = \"top\",\n  animation = \"fade\",\n  delay = 0,\n  duration = 0.15,\n  className,\n  contentClassName,\n  arrow = true,\n  offset = 8,\n  disabled = false,\n}: AnimatedTooltipProps) {\n  const [isVisible, setIsVisible] = useState(false);\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const tooltipRef = useRef<HTMLDivElement>(null);\n  const timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n  const calculatePosition = useCallback(() => {\n    if (!triggerRef.current || !tooltipRef.current) return;\n\n    const triggerRect = triggerRef.current.getBoundingClientRect();\n    const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n    let x = 0;\n    let y = 0;\n\n    switch (placement) {\n      case \"top\":\n        x = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n        y = triggerRect.top - tooltipRect.height - offset;\n        break;\n      case \"top-start\":\n        x = triggerRect.left;\n        y = triggerRect.top - tooltipRect.height - offset;\n        break;\n      case \"top-end\":\n        x = triggerRect.right - tooltipRect.width;\n        y = triggerRect.top - tooltipRect.height - offset;\n        break;\n      case \"bottom\":\n        x = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n        y = triggerRect.bottom + offset;\n        break;\n      case \"bottom-start\":\n        x = triggerRect.left;\n        y = triggerRect.bottom + offset;\n        break;\n      case \"bottom-end\":\n        x = triggerRect.right - tooltipRect.width;\n        y = triggerRect.bottom + offset;\n        break;\n      case \"left\":\n        x = triggerRect.left - tooltipRect.width - offset;\n        y = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n        break;\n      case \"right\":\n        x = triggerRect.right + offset;\n        y = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n        break;\n    }\n\n    // Keep tooltip within viewport\n    x = Math.max(8, Math.min(x, window.innerWidth - tooltipRect.width - 8));\n    y = Math.max(8, Math.min(y, window.innerHeight - tooltipRect.height - 8));\n\n    setPosition({ x, y });\n  }, [placement, offset]);\n\n  useEffect(() => {\n    if (isVisible) {\n      calculatePosition();\n      window.addEventListener(\"scroll\", calculatePosition);\n      window.addEventListener(\"resize\", calculatePosition);\n    }\n    return () => {\n      window.removeEventListener(\"scroll\", calculatePosition);\n      window.removeEventListener(\"resize\", calculatePosition);\n    };\n  }, [isVisible, calculatePosition]);\n\n  const handleMouseEnter = () => {\n    if (disabled) return;\n    timeoutRef.current = setTimeout(() => setIsVisible(true), delay);\n  };\n\n  const handleMouseLeave = () => {\n    if (timeoutRef.current) clearTimeout(timeoutRef.current);\n    setIsVisible(false);\n  };\n\n  const getAnimationVariants = () => {\n    const baseDirection = placement.split(\"-\")[0];\n\n    switch (animation) {\n      case \"scale\":\n        return {\n          hidden: { opacity: 0, scale: 0.8 },\n          visible: { opacity: 1, scale: 1 },\n        };\n      case \"slide\": {\n        const slideOffset = 10;\n        const slideVariants = {\n          top: {\n            hidden: { opacity: 0, y: slideOffset },\n            visible: { opacity: 1, y: 0 },\n          },\n          bottom: {\n            hidden: { opacity: 0, y: -slideOffset },\n            visible: { opacity: 1, y: 0 },\n          },\n          left: {\n            hidden: { opacity: 0, x: slideOffset },\n            visible: { opacity: 1, x: 0 },\n          },\n          right: {\n            hidden: { opacity: 0, x: -slideOffset },\n            visible: { opacity: 1, x: 0 },\n          },\n        };\n        return (\n          slideVariants[baseDirection as keyof typeof slideVariants] ||\n          slideVariants.top\n        );\n      }\n      case \"spring\":\n        return {\n          hidden: { opacity: 0, scale: 0.5 },\n          visible: { opacity: 1, scale: 1 },\n        };\n      default:\n        return {\n          hidden: { opacity: 0 },\n          visible: { opacity: 1 },\n        };\n    }\n  };\n\n  const getArrowPosition = () => {\n    const baseDirection = placement.split(\"-\")[0];\n    const alignment = placement.split(\"-\")[1];\n\n    const arrowClasses = {\n      top: \"bottom-0 left-1/2 -translate-x-1/2 translate-y-full border-t-foreground border-x-transparent border-b-transparent\",\n      bottom:\n        \"top-0 left-1/2 -translate-x-1/2 -translate-y-full border-b-foreground border-x-transparent border-t-transparent\",\n      left: \"right-0 top-1/2 -translate-y-1/2 translate-x-full border-l-foreground border-y-transparent border-r-transparent\",\n      right:\n        \"left-0 top-1/2 -translate-y-1/2 -translate-x-full border-r-foreground border-y-transparent border-l-transparent\",\n    };\n\n    let alignmentClass = \"\";\n    if (alignment === \"start\") {\n      alignmentClass =\n        baseDirection === \"top\" || baseDirection === \"bottom\"\n          ? \"left-4 -translate-x-0\"\n          : \"\";\n    } else if (alignment === \"end\") {\n      alignmentClass =\n        baseDirection === \"top\" || baseDirection === \"bottom\"\n          ? \"left-auto right-4 translate-x-0\"\n          : \"\";\n    }\n\n    return cn(\n      arrowClasses[baseDirection as keyof typeof arrowClasses],\n      alignmentClass,\n    );\n  };\n\n  return (\n    <>\n      <button\n        ref={triggerRef}\n        className={cn(\"inline-block\", className)}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        onFocus={handleMouseEnter}\n        onBlur={handleMouseLeave}\n        type=\"button\"\n      >\n        {children}\n      </button>\n\n      <AnimatePresence>\n        {isVisible && (\n          <motion.div\n            ref={tooltipRef}\n            className={cn(\n              \"fixed z-50 max-w-xs rounded-md bg-foreground px-3 py-1.5 text-background text-sm shadow-lg\",\n              contentClassName,\n            )}\n            style={{ left: position.x, top: position.y }}\n            variants={getAnimationVariants()}\n            initial=\"hidden\"\n            animate=\"visible\"\n            exit=\"hidden\"\n            transition={\n              animation === \"spring\"\n                ? { type: \"spring\", stiffness: 500, damping: 25 }\n                : { duration }\n            }\n          >\n            {content}\n            {arrow && (\n              <div\n                className={cn(\"absolute h-0 w-0 border-4\", getArrowPosition())}\n              />\n            )}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n\n// Tooltip with Rich Content\ninterface RichTooltipProps {\n  children: React.ReactNode;\n  title: string;\n  description?: string;\n  image?: string;\n  placement?: Placement;\n  className?: string;\n}\n\nexport function RichTooltip({\n  children,\n  title,\n  description,\n  image,\n  placement = \"top\",\n  className,\n}: RichTooltipProps) {\n  return (\n    <AnimatedTooltip\n      placement={placement}\n      animation=\"scale\"\n      arrow={false}\n      className={className}\n      contentClassName=\"p-0 overflow-hidden max-w-[280px]\"\n      content={\n        <div className=\"rounded-lg border bg-card text-card-foreground shadow-xl\">\n          {image && (\n            <div className=\"h-32 w-full overflow-hidden\">\n              {/* biome-ignore lint/performance/noImgElement: next/image causes ESM issues with fumadocs-mdx */}\n              <img\n                src={image}\n                alt={title}\n                className=\"h-full w-full object-cover\"\n              />\n            </div>\n          )}\n          <div className=\"p-3\">\n            <p className=\"font-semibold text-foreground\">{title}</p>\n            {description && (\n              <p className=\"mt-1 text-muted-foreground text-sm\">\n                {description}\n              </p>\n            )}\n          </div>\n        </div>\n      }\n    >\n      {children}\n    </AnimatedTooltip>\n  );\n}\n\n// Icon Tooltip (compact)\ninterface IconTooltipProps {\n  children: React.ReactNode;\n  label: string;\n  placement?: Placement;\n  shortcut?: string;\n}\n\nexport function IconTooltip({\n  children,\n  label,\n  placement = \"top\",\n  shortcut,\n}: IconTooltipProps) {\n  return (\n    <AnimatedTooltip\n      placement={placement}\n      animation=\"fade\"\n      delay={200}\n      content={\n        <div className=\"flex items-center gap-2\">\n          <span>{label}</span>\n          {shortcut && (\n            <kbd className=\"rounded bg-background/20 px-1.5 py-0.5 font-mono text-xs\">\n              {shortcut}\n            </kbd>\n          )}\n        </div>\n      }\n    >\n      {children}\n    </AnimatedTooltip>\n  );\n}\n\n// Hover Card (larger tooltip with delay)\ninterface HoverCardTooltipProps {\n  children: React.ReactNode;\n  content: React.ReactNode;\n  placement?: Placement;\n  className?: string;\n}\n\nexport function HoverCardTooltip({\n  children,\n  content,\n  placement = \"bottom\",\n  className,\n}: HoverCardTooltipProps) {\n  return (\n    <AnimatedTooltip\n      placement={placement}\n      animation=\"spring\"\n      delay={300}\n      arrow={false}\n      contentClassName={cn(\n        \"p-0 bg-card text-card-foreground border rounded-xl shadow-2xl max-w-sm\",\n        className,\n      )}\n      content={content}\n    >\n      {children}\n    </AnimatedTooltip>\n  );\n}\n\n// Confirmation Tooltip\ninterface ConfirmTooltipProps {\n  children: React.ReactNode;\n  message: string;\n  onConfirm: () => void;\n  onCancel?: () => void;\n  placement?: Placement;\n  confirmText?: string;\n  cancelText?: string;\n}\n\nexport function ConfirmTooltip({\n  children,\n  message,\n  onConfirm,\n  onCancel,\n  placement: _placement = \"top\",\n  confirmText = \"Confirm\",\n  cancelText = \"Cancel\",\n}: ConfirmTooltipProps) {\n  const [isOpen, setIsOpen] = useState(false);\n\n  const handleConfirm = () => {\n    onConfirm();\n    setIsOpen(false);\n  };\n\n  const handleCancel = () => {\n    onCancel?.();\n    setIsOpen(false);\n  };\n\n  return (\n    <>\n      <button\n        type=\"button\"\n        onClick={() => setIsOpen(!isOpen)}\n        className=\"inline-block cursor-pointer\"\n      >\n        {children}\n      </button>\n\n      <AnimatePresence>\n        {isOpen && (\n          <>\n            {/* Backdrop */}\n            <motion.div\n              className=\"fixed inset-0 z-40\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              onClick={handleCancel}\n            />\n\n            {/* Tooltip */}\n            <motion.div\n              className=\"fixed z-50 w-64 rounded-lg border bg-card p-4 shadow-xl\"\n              initial={{ opacity: 0, scale: 0.9 }}\n              animate={{ opacity: 1, scale: 1 }}\n              exit={{ opacity: 0, scale: 0.9 }}\n              transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n            >\n              <p className=\"mb-3 text-sm\">{message}</p>\n              <div className=\"flex justify-end gap-2\">\n                <button\n                  onClick={handleCancel}\n                  className=\"rounded-md px-3 py-1.5 text-muted-foreground text-sm hover:bg-accent\"\n                >\n                  {cancelText}\n                </button>\n                <button\n                  onClick={handleConfirm}\n                  className=\"rounded-md bg-primary px-3 py-1.5 text-primary-foreground text-sm hover:bg-primary/90\"\n                >\n                  {confirmText}\n                </button>\n              </div>\n            </motion.div>\n          </>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n\n// Tooltip Group (for toolbar-style tooltips)\ninterface TooltipItem {\n  icon: React.ReactNode;\n  label: string;\n  shortcut?: string;\n  onClick?: () => void;\n}\n\ninterface TooltipGroupProps {\n  items: TooltipItem[];\n  className?: string;\n}\n\nexport function TooltipGroup({ items, className }: TooltipGroupProps) {\n  return (\n    <div\n      className={cn(\n        \"inline-flex items-center gap-1 rounded-lg border bg-card p-1\",\n        className,\n      )}\n    >\n      {items.map((item, index) => (\n        <IconTooltip key={index} label={item.label} shortcut={item.shortcut}>\n          <button\n            onClick={item.onClick}\n            className=\"flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n          >\n            {item.icon}\n          </button>\n        </IconTooltip>\n      ))}\n    </div>\n  );\n}\n\n// Floating Label (tooltip that stays visible on focus)\ninterface FloatingLabelProps {\n  children: React.ReactNode;\n  label: string;\n  className?: string;\n}\n\nexport function FloatingLabel({\n  children,\n  label,\n  className,\n}: FloatingLabelProps) {\n  const [isFocused, setIsFocused] = useState(false);\n\n  return (\n    <div className={cn(\"relative\", className)}>\n      <AnimatePresence>\n        {isFocused && (\n          <motion.div\n            className=\"absolute -top-6 left-0 font-medium text-primary text-xs\"\n            initial={{ opacity: 0, y: 5 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: 5 }}\n            transition={{ duration: 0.15 }}\n          >\n            {label}\n          </motion.div>\n        )}\n      </AnimatePresence>\n      <div\n        onFocus={() => setIsFocused(true)}\n        onBlur={() => setIsFocused(false)}\n        role=\"button\"\n        tabIndex={0}\n      >\n        {children}\n      </div>\n    </div>\n  );\n}\n\n// Status Tooltip (with colored indicator)\ninterface StatusTooltipProps {\n  children: React.ReactNode;\n  status: \"online\" | \"offline\" | \"away\" | \"busy\";\n  label?: string;\n  placement?: Placement;\n}\n\nexport function StatusTooltip({\n  children,\n  status,\n  label,\n  placement = \"top\",\n}: StatusTooltipProps) {\n  const statusConfig = {\n    online: { color: \"bg-green-500\", text: \"Online\" },\n    offline: { color: \"bg-gray-400\", text: \"Offline\" },\n    away: { color: \"bg-yellow-500\", text: \"Away\" },\n    busy: { color: \"bg-red-500\", text: \"Busy\" },\n  };\n\n  const config = statusConfig[status];\n\n  return (\n    <AnimatedTooltip\n      placement={placement}\n      animation=\"fade\"\n      content={\n        <div className=\"flex items-center gap-2\">\n          <div className={cn(\"h-2 w-2 rounded-full\", config.color)} />\n          <span>{label || config.text}</span>\n        </div>\n      }\n    >\n      {children}\n    </AnimatedTooltip>\n  );\n}\n\nexport type {\n  AnimatedTooltipProps,\n  Animation,\n  ConfirmTooltipProps,\n  FloatingLabelProps,\n  HoverCardTooltipProps,\n  IconTooltipProps,\n  Placement,\n  RichTooltipProps,\n  StatusTooltipProps,\n  TooltipGroupProps,\n};\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}