{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tooltip",
  "title": "Tooltip",
  "description": "A small bubble with an arrow, after a short pause.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/utils.json",
    "https://ui.ndekocode.dev/r/animated-size.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/ui/tooltip.tsx",
      "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\nimport { useState } from 'react';\n\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { AnimatedSize } from '@/registry/ndeko/ui/animated-size';\n\ntype WithClassName<Props> = Omit<Props, 'className'> & { className?: string };\n\ntype TooltipHandle<Payload> = TooltipPrimitive.Handle<Payload>;\n\nconst TOOLTIP_DELAY_MS = 400;\n\n/** Long enough for the pointer to cross the gap between two triggers of one gliding tooltip. */\nconst TOOLTIP_GLIDE_CLOSE_DELAY = 120;\n\nfunction TooltipProvider({\n  delay = TOOLTIP_DELAY_MS,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return <TooltipPrimitive.Provider delay={delay} {...props} />;\n}\n\nfunction Tooltip<Payload>(props: TooltipPrimitive.Root.Props<Payload>) {\n  return <TooltipPrimitive.Root data-slot='tooltip' {...props} />;\n}\n\nfunction TooltipTrigger<Payload>(\n  props: TooltipPrimitive.Trigger.Props<Payload>\n) {\n  return <TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />;\n}\n\n/** One handle per mounted component, so two copies of a demo never share an open state. */\nfunction useTooltipHandle<Payload>() {\n  const [handle] = useState(() => TooltipPrimitive.createHandle<Payload>());\n  return handle;\n}\n\nconst BUBBLE = [\n  'relative max-w-64 rounded-lg px-2 py-1 text-xs font-medium leading-snug text-balance text-popover-foreground',\n  '[--tooltip-top:var(--popover)] [--tooltip-bottom:color-mix(in_oklab,var(--popover),var(--primary)_9%)]',\n  'bg-linear-to-b from-(--tooltip-top) to-(--tooltip-bottom) hairline-ring ring-border shadow-md shadow-black/6',\n  'origin-(--transform-origin) transition-[opacity,scale] duration-[125ms] ease-(--ease-out-strong)',\n  'data-starting-style:scale-[0.97] data-starting-style:opacity-0 data-ending-style:scale-[0.97] data-ending-style:opacity-0 data-ending-style:duration-100',\n  'data-instant:transition-none',\n].join(' ');\n\nconst ARROW = [\n  'flex fill-(--tooltip-bottom) stroke-border',\n  'data-[side=bottom]:-top-[6px] data-[side=bottom]:fill-(--tooltip-top)',\n  'data-[side=top]:-bottom-[6px] data-[side=top]:rotate-180',\n  'data-[side=left]:-right-[9px] data-[side=left]:rotate-90',\n  'data-[side=right]:-left-[9px] data-[side=right]:-rotate-90',\n].join(' ');\n\n/**\n * The first open does not glide: Base UI positions the popup while it still holds transitions\n * off. `data-instant` covers the provider's instant phase and cursor tracking.\n */\nconst TOOLTIP_GLIDE = [\n  'transition-[top,left,right,bottom,transform] duration-200 ease-(--ease-out-strong)',\n  'data-closed:transition-none data-instant:transition-none',\n].join(' ');\n\n/** The arrow remounts when the side changes, so it fades in on its new edge instead of jumping round a corner. */\nconst ARROW_GLIDE = [\n  'transition-[left,top,opacity] duration-[200ms,200ms,125ms] ease-(--ease-out-strong)',\n  'starting:opacity-0 data-instant:transition-none',\n].join(' ');\n\n/** The bubble's `max-w-64` minus its padding, so a long label still wraps inside the resizing box. */\nconst GLIDE_LABEL_MAX_WIDTH = 'max-w-62';\n\nfunction positionerClassOf(glide: boolean) {\n  return cn('z-50', glide && TOOLTIP_GLIDE);\n}\n\nfunction arrowClassOf(glide: boolean) {\n  return cn(ARROW, glide && ARROW_GLIDE);\n}\n\nfunction TooltipBody({\n  glide,\n  children,\n}: {\n  glide: boolean;\n  children: React.ReactNode;\n}) {\n  if (!glide) {\n    return children;\n  }\n  return (\n    <AnimatedSize axis='both' contentClassName={GLIDE_LABEL_MAX_WIDTH}>\n      {children}\n    </AnimatedSize>\n  );\n}\n\ntype TooltipContentProps = WithClassName<TooltipPrimitive.Popup.Props> &\n  Pick<TooltipPrimitive.Positioner.Props, 'side' | 'sideOffset' | 'align'> & {\n    /** Glide to the next trigger of a shared tooltip and ease to the size of its label. */\n    glide?: boolean;\n  };\n\nfunction TooltipContent({\n  className,\n  children,\n  side = 'top',\n  sideOffset = 8,\n  align = 'center',\n  glide = false,\n  ...props\n}: TooltipContentProps) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n        className={positionerClassOf(glide)}\n      >\n        <TooltipPrimitive.Popup\n          data-slot='tooltip-content'\n          className={cn(BUBBLE, className)}\n          {...props}\n        >\n          <TooltipBody glide={glide}>{children}</TooltipBody>\n          <TooltipPrimitive.Arrow key={side} className={arrowClassOf(glide)}>\n            <svg width='12' height='6' viewBox='0 0 12 6' aria-hidden='true'>\n              <path d='M0 6 6 0l6 6Z' stroke='none' />\n              <polyline points='0,6 6,0.5 12,6' fill='none' strokeWidth='1' />\n            </svg>\n          </TooltipPrimitive.Arrow>\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport {\n  TOOLTIP_GLIDE,\n  TOOLTIP_GLIDE_CLOSE_DELAY,\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n  useTooltipHandle,\n  type TooltipHandle,\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}