{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tooltip-09",
  "title": "Chart readout",
  "description": "One readout follows the pointer, a tap or the keyboard from bar to bar.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/recipes.json",
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/tooltip.json",
    "https://ui.ndekocode.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/tooltip/tooltip-09.tsx",
      "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\nimport { useId, useRef } from 'react';\n\nimport { POPUP_TRANSITION } from '@/registry/ndeko/lib/recipes';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport {\n  TOOLTIP_GLIDE,\n  Tooltip,\n  type TooltipHandle,\n  TooltipTrigger,\n  useTooltipHandle,\n} from '@/registry/ndeko/ui/tooltip';\n\ntype Source = 'direct' | 'search' | 'referral';\n\ntype SourceEntry = { key: Source; label: string; colorClass: string };\n\ntype DayVisits = {\n  day: string;\n  date: string;\n  visits: Record<Source, number>;\n};\n\nconst SOURCES: SourceEntry[] = [\n  { key: 'direct', label: 'Direct', colorClass: 'bg-chart-1' },\n  { key: 'search', label: 'Search', colorClass: 'bg-chart-2' },\n  { key: 'referral', label: 'Referral', colorClass: 'bg-chart-3' },\n];\n\nconst WEEK: DayVisits[] = [\n  {\n    day: 'Mon',\n    date: 'Monday, Aug 12',\n    visits: { direct: 380, search: 690, referral: 110 },\n  },\n  {\n    day: 'Tue',\n    date: 'Tuesday, Aug 13',\n    visits: { direct: 412, search: 736, referral: 128 },\n  },\n  {\n    day: 'Wed',\n    date: 'Wednesday, Aug 14',\n    visits: { direct: 356, search: 640, referral: 96 },\n  },\n  {\n    day: 'Thu',\n    date: 'Thursday, Aug 15',\n    visits: { direct: 440, search: 802, referral: 150 },\n  },\n  {\n    day: 'Fri',\n    date: 'Friday, Aug 16',\n    visits: { direct: 398, search: 710, referral: 134 },\n  },\n  {\n    day: 'Sat',\n    date: 'Saturday, Aug 17',\n    visits: { direct: 210, search: 388, referral: 64 },\n  },\n  {\n    day: 'Sun',\n    date: 'Sunday, Aug 18',\n    visits: { direct: 186, search: 342, referral: 58 },\n  },\n];\n\nconst BAR_TRIGGER =\n  'hover:bg-foreground/5 data-popup-open:bg-foreground/5 focus-visible:ring-ring focus-visible:ring-offset-background flex h-full w-9.5 flex-col items-center gap-1.5 rounded-md px-[9px] pt-2 pb-1 outline-none focus-visible:ring-2 focus-visible:ring-offset-2';\n\nconst READOUT = [\n  'surface-raised flex w-48 flex-col gap-2 rounded-xl p-3 text-[13px] outline-none',\n  POPUP_TRANSITION,\n  'data-instant:transition-none',\n].join(' ');\n\nfunction totalOf(day: DayVisits) {\n  const { direct, search, referral } = day.visits;\n  return direct + search + referral;\n}\n\nconst DAY_TOTALS = WEEK.map(totalOf);\nconst BUSIEST_DAY_TOTAL = Math.max(...DAY_TOTALS);\n\nfunction percentOf(part: number, whole: number) {\n  const ratio = part / whole;\n  return `${ratio * 100}%`;\n}\n\nfunction formatVisits(count: number) {\n  return count.toLocaleString('en-US');\n}\n\nfunction visitsOfSource(day: DayVisits, source: SourceEntry) {\n  const count = formatVisits(day.visits[source.key]);\n  const label = source.label.toLowerCase();\n  return `${count} ${label}`;\n}\n\nfunction readoutLabelOf(day: DayVisits) {\n  const parts = SOURCES.map((source) => visitsOfSource(day, source));\n  const summary = parts.join(', ');\n  return `${day.date}: ${summary} visits`;\n}\n\nfunction triggerIdOf(chartId: string, day: DayVisits) {\n  return `${chartId}-${day.day}`;\n}\n\ntype DayBarProps = {\n  day: DayVisits;\n  triggerId: string;\n  readout: TooltipHandle<DayVisits>;\n};\n\nfunction DayBar({ day, triggerId, readout }: DayBarProps) {\n  const pointerTypeRef = useRef('');\n  const total = totalOf(day);\n  const barHeight = percentOf(total, BUSIEST_DAY_TOTAL);\n\n  function rememberPointerType(event: React.PointerEvent<HTMLButtonElement>) {\n    pointerTypeRef.current = event.pointerType;\n  }\n\n  // Base UI tooltips ignore touch, so a tap opens the readout itself.\n  function openOnTap() {\n    const isTap = pointerTypeRef.current === 'touch';\n    if (!isTap) {\n      return;\n    }\n    readout.open(triggerId);\n  }\n\n  return (\n    <TooltipTrigger\n      id={triggerId}\n      handle={readout}\n      payload={day}\n      delay={0}\n      closeOnClick={false}\n      onPointerDown={rememberPointerType}\n      onClick={openOnTap}\n      aria-label={readoutLabelOf(day)}\n      className={BAR_TRIGGER}\n    >\n      <span className='flex w-full flex-1 items-end'>\n        <span\n          className='flex w-full flex-col-reverse overflow-hidden rounded-[3px]'\n          style={{ height: barHeight }}\n        >\n          {SOURCES.map((source) => (\n            <span\n              key={source.key}\n              className={source.colorClass}\n              style={{ height: percentOf(day.visits[source.key], total) }}\n            />\n          ))}\n        </span>\n      </span>\n      <span className='text-muted-foreground text-[11px]'>{day.day}</span>\n    </TooltipTrigger>\n  );\n}\n\nfunction ReadoutBody({ day }: { day: DayVisits | undefined }) {\n  if (!day) {\n    return null;\n  }\n  return (\n    <>\n      <p className='text-[13px] leading-none font-medium'>{day.date}</p>\n      <ul className='flex flex-col gap-1.5 text-xs'>\n        {SOURCES.map((source) => (\n          <li key={source.key} className='flex items-center gap-2'>\n            <span\n              className={cn('size-2 shrink-0 rounded-full', source.colorClass)}\n              aria-hidden='true'\n            />\n            {source.label}\n            <span className='ml-auto font-medium tabular-nums'>\n              {formatVisits(day.visits[source.key])}\n            </span>\n          </li>\n        ))}\n      </ul>\n    </>\n  );\n}\n\nexport default function Tooltip09() {\n  const chartId = useId();\n  const readout = useTooltipHandle<DayVisits>();\n\n  return (\n    <figure className='flex flex-col gap-2'>\n      <figcaption className='text-muted-foreground text-xs'>\n        Visits by source, week of Aug 12\n      </figcaption>\n      <div className='flex h-36 items-end'>\n        {WEEK.map((day) => (\n          <DayBar\n            key={day.day}\n            day={day}\n            triggerId={triggerIdOf(chartId, day)}\n            readout={readout}\n          />\n        ))}\n      </div>\n      <Tooltip handle={readout}>\n        {({ payload }) => (\n          <TooltipPrimitive.Portal>\n            <TooltipPrimitive.Positioner\n              side='top'\n              sideOffset={6}\n              className={cn('z-50', TOOLTIP_GLIDE)}\n            >\n              <TooltipPrimitive.Popup className={READOUT}>\n                <ReadoutBody day={payload} />\n              </TooltipPrimitive.Popup>\n            </TooltipPrimitive.Positioner>\n          </TooltipPrimitive.Portal>\n        )}\n      </Tooltip>\n    </figure>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "components",
    "tooltip"
  ],
  "type": "registry:component"
}