{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "tooltip", "title": "Tooltip", "description": "A hand-drawn callout that appears on hover with a rough polygon arrow.", "dependencies": [ "roughjs" ], "registryDependencies": [ "https://www.bydefaulthuman.fun/r/use-rough.json", "utils", "https://www.bydefaulthuman.fun/r/crumble-theme.json", "https://www.bydefaulthuman.fun/r/rough-lib.json" ], "files": [ { "path": "registry/new-york/ui/tooltip.tsx", "content": "\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport { cn } from \"@/lib/utils\";\nimport {\n resolveRoughVars,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\n\nexport type TooltipSide = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps extends CrumbleColorProps {\n children: ReactNode;\n className?: string;\n content: ReactNode;\n delayMs?: number;\n id?: string;\n side?: TooltipSide;\n theme?: CrumbleTheme;\n}\n\nconst ARROW = 8;\n\nexport function Tooltip({\n children,\n className,\n content,\n delayMs = 400,\n fill,\n id,\n side = \"top\",\n stroke,\n strokeMuted,\n theme: themeProp,\n}: TooltipProps) {\n const [visible, setVisible] = useState(false);\n const timerRef = useRef | null>(null);\n const tooltipRef = useRef(null);\n const externalSvgRef = useRef(null);\n const tooltipId = id ?? \"tooltip\";\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n const { drawPath, drawRect, svgRef } = useRough({\n stableId: tooltipId,\n svgRef: externalSvgRef,\n theme: themeProp,\n variant: \"border\",\n });\n\n const draw = useCallback(() => {\n const tooltip = tooltipRef.current;\n const svg = svgRef.current;\n if (!tooltip || !svg) return;\n\n svg.replaceChildren();\n\n const w = tooltip.offsetWidth;\n const h = tooltip.offsetHeight;\n const svgW = side === \"left\" || side === \"right\" ? w + ARROW : w;\n const svgH = side === \"top\" || side === \"bottom\" ? h + ARROW : h;\n\n svg.setAttribute(\"width\", String(svgW));\n svg.setAttribute(\"height\", String(svgH));\n svg.setAttribute(\"viewBox\", `0 0 ${svgW} ${svgH}`);\n\n const bx = side === \"right\" ? ARROW : 0;\n const by = side === \"bottom\" ? ARROW : 0;\n const box = drawRect(bx + 1, by + 1, w - 2, h - 2, {\n fill: \"var(--popover)\",\n fillStyle: \"solid\",\n stroke: \"var(--cr-stroke)\",\n });\n if (box) svg.appendChild(box);\n\n let arrowPath = \"\";\n if (side === \"top\") {\n const mx = w / 2;\n arrowPath = `M ${mx - ARROW} ${h - 1} L ${mx} ${h + ARROW - 1} L ${mx + ARROW} ${h - 1}`;\n } else if (side === \"bottom\") {\n const mx = w / 2;\n arrowPath = `M ${mx - ARROW} ${ARROW + 1} L ${mx} 1 L ${mx + ARROW} ${ARROW + 1}`;\n } else if (side === \"left\") {\n const my = h / 2;\n arrowPath = `M ${w - 1} ${my - ARROW} L ${w + ARROW - 1} ${my} L ${w - 1} ${my + ARROW}`;\n } else {\n const my = h / 2;\n arrowPath = `M ${ARROW + 1} ${my - ARROW} L 1 ${my} L ${ARROW + 1} ${my + ARROW}`;\n }\n\n const arrow = drawPath(arrowPath, {\n fill: \"var(--popover)\",\n fillStyle: \"solid\",\n roughness: 0.8,\n stroke: \"var(--cr-stroke)\",\n });\n if (arrow) svg.appendChild(arrow);\n }, [drawPath, drawRect, side, svgRef]);\n\n useEffect(() => {\n if (visible) {\n const id = requestAnimationFrame(() => draw());\n return () => cancelAnimationFrame(id);\n }\n }, [visible, draw]);\n\n const show = () => {\n timerRef.current = setTimeout(() => setVisible(true), delayMs);\n };\n\n const hide = () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n setVisible(false);\n };\n\n const sideClasses: Record = {\n bottom: \"top-full left-1/2 -translate-x-1/2 mt-2\",\n left: \"right-full top-1/2 -translate-y-1/2 mr-2\",\n right: \"left-full top-1/2 -translate-y-1/2 ml-2\",\n top: \"bottom-full left-1/2 -translate-x-1/2 mb-2\",\n };\n\n return (\n \n {children}\n {visible ? (\n \n
\n \n \n {content}\n
\n \n \n ) : null}\n \n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/tooltip.tsx" } ], "type": "registry:ui" }