{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "primitives",
    "finance",
    "accounting"
  ],
  "dependencies": [
    "@base-ui-components/react"
  ],
  "description": "Hover/focus restatement. Never the only home of a fact.",
  "files": [
    {
      "content": "\"use client\";\n\nimport { Tooltip as BaseTooltip } from \"@base-ui-components/react/tooltip\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\n\n/**\n * The system's one floating-context surface.\n *\n * Two components, and the distinction is not cosmetic — it is the whole point:\n *\n * - `Tooltip` labels something already on screen. Hover and focus only. It is\n *   unreachable on touch by design, so it may only ever carry a *restatement*\n *   — the unabbreviated figure, the full timestamp, the untruncated name.\n * - `InfoTip` carries context that is genuinely additional. It is a real\n *   button, so it opens on press as well as on hover, which makes it the only\n *   one of the two a touch user can open.\n *\n * The rule that follows: if a sentence would be lost on a phone, it belongs in\n * an `InfoTip` (or inline), never in a `Tooltip`.\n */\n\nexport const TOOLTIP_DELAY = 400;\n\n/** e2, radius 8, hairline + shadow. Shared so the two never drift apart. */\nexport const TOOLTIP_SURFACE = cn(\n  \"z-tooltip max-w-[264px] rounded-sm border border-border bg-surface-raised\",\n  \"px-3 py-2 text-caption text-text-primary shadow-elevation-2\",\n);\n\n/**\n * Enter 140ms, exit 100ms, opacity + scale only. `data-instant` is set by Base\n * UI when a tooltip opens without its delay — hovering along a toolbar after\n * the first one is already open — and it must skip the animation too, or the\n * \"instant\" second tooltip still visibly fades.\n */\nexport const TOOLTIP_MOTION = cn(\n  \"origin-[var(--transform-origin)] transition-[opacity,transform] duration-2 ease-out\",\n  \"data-[starting-style]:scale-[0.97] data-[starting-style]:opacity-0\",\n  \"data-[ending-style]:scale-[0.97] data-[ending-style]:opacity-0 data-[ending-style]:duration-1\",\n  \"data-[instant]:duration-0\",\n);\n\nexport interface TooltipProviderProps {\n  children?: React.ReactNode;\n  closeDelay?: number;\n  /** Before the first tooltip in a group. Subsequent ones open instantly. */\n  delay?: number;\n}\n\n/**\n * Wrap a surface — a toolbar, a table, an app — so its tooltips share one\n * delay. Without it every tooltip waits 400ms independently, which makes a\n * dense toolbar feel unresponsive; with it, only the first one waits.\n */\nexport function TooltipProvider({ children, delay = TOOLTIP_DELAY, closeDelay = 0 }: TooltipProviderProps) {\n  return (\n    <BaseTooltip.Provider closeDelay={closeDelay} delay={delay}>\n      {children}\n    </BaseTooltip.Provider>\n  );\n}\n\nexport type TooltipSide = \"top\" | \"bottom\" | \"left\" | \"right\";\nexport type TooltipAlign = \"start\" | \"center\" | \"end\";\n\nexport interface TooltipProps {\n  align?: TooltipAlign;\n  children: React.ReactNode;\n  /** Classes for the popup. */\n  className?: string;\n  /** Supplementary only. Never the sole home of anything a user must read. */\n  content: React.ReactNode;\n  /** Overrides the provider's delay for this one trigger. */\n  delay?: number;\n  disabled?: boolean;\n  /**\n   * The trigger's own display. `inline` wraps a phrase inside a line;\n   * `block` is for a child that fills its column — a truncating cell, a table\n   * memo — where an inline trigger would shrink-wrap it and defeat the\n   * ellipsis it exists to reveal.\n   */\n  display?: \"inline\" | \"block\";\n  side?: TooltipSide;\n  /** Classes for the trigger element, not the popup. */\n  triggerClassName?: string;\n}\n\n/**\n * `content` is nullable on purpose: a truncated cell has a tooltip and a short\n * one does not, and the call site should not have to branch.\n */\nexport function Tooltip({\n  content,\n  children,\n  side = \"top\",\n  align = \"center\",\n  delay,\n  disabled,\n  display = \"inline\",\n  triggerClassName,\n  className,\n}: TooltipProps) {\n  if (disabled || content == null || content === \"\") return <>{children}</>;\n\n  return (\n    <BaseTooltip.Root>\n      {/* The trigger is our own element with `children` inside it, rather than\n          Base UI cloning the caller's element via `render={children}`.\n\n          That looks like the tidier API and it is a trap: a Server Component\n          hands its JSX children across the boundary as a *lazy* node, not an\n          element — `isValidElement` is false and `type` is undefined — so the\n          clone renders `undefined` and the page throws. Rendering the child as\n          a child sidesteps it entirely, and works from either side. */}\n      <BaseTooltip.Trigger\n        className={cn(display === \"block\" ? \"block min-w-0\" : \"inline\", triggerClassName)}\n        delay={delay}\n        render={<span />}\n      >\n        {children}\n      </BaseTooltip.Trigger>\n      <BaseTooltip.Portal>\n        <BaseTooltip.Positioner align={align} className=\"z-tooltip\" side={side} sideOffset={8}>\n          <BaseTooltip.Popup className={cn(TOOLTIP_SURFACE, TOOLTIP_MOTION, className)}>\n            {content}\n          </BaseTooltip.Popup>\n        </BaseTooltip.Positioner>\n      </BaseTooltip.Portal>\n    </BaseTooltip.Root>\n  );\n}\n",
      "path": "components/ledger/tooltip.tsx",
      "target": "components/ledger/tooltip.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "tooltip",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn"
  ],
  "title": "Tooltip",
  "type": "registry:ui"
}
