import { createSignal, JSXElement, onCleanup, Show } from "solid-js"; import { FloatingRoot, FloatingTrigger, FloatingContent, useFloatingContext, Placement } from "./Floating.tsx"; const TOOLTIP_DEFAULT_OFFSET = 8; const TOOLTIP_CLS = "bg-neutral-800 text-white text-sm px-2.5 py-1.5 rounded-default shadow-lg max-w-80 relative"; function arrowCls(base: string) { const common = "absolute w-0 h-0"; switch (base) { case "top": return common + " -bottom-[6px] left-1/2 -translate-x-1/2 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[6px] border-t-neutral-800"; case "bottom": return common + " -top-[6px] left-1/2 -translate-x-1/2 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-b-[6px] border-b-neutral-800"; case "left": return common + " -right-[6px] top-1/2 -translate-y-1/2 border-t-[6px] border-t-transparent border-b-[6px] border-b-transparent border-l-[6px] border-l-neutral-800"; case "right": return common + " -left-[6px] top-1/2 -translate-y-1/2 border-t-[6px] border-t-transparent border-b-[6px] border-b-transparent border-r-[6px] border-r-neutral-800"; default: return common; } } interface TooltipProps { content: JSXElement; trigger?: "hover" | "focus"; placement?: "top" | "bottom" | "left" | "right"; offset?: number; delay?: number; children?: JSXElement; } export function Tooltip(props: TooltipProps) { return ( {props.children} }> {props.children} ); } interface HoverTooltipProps { content: JSXElement; placement: Placement; offset: number; delay: number; children?: JSXElement; } export function HoverTooltip(props: HoverTooltipProps) { const [isOpen, setIsOpen] = createSignal(false); return ( {props.children} {props.content} ); } interface HoverTriggerProps { delay: number; setIsOpen: (v: boolean) => void; children?: JSXElement; } function HoverTooltipTrigger(props: HoverTriggerProps) { const { setTriggerRef } = useFloatingContext(); let hoverOpenTimeoutRef: ReturnType | null = null; let hoverCloseTimeoutRef: ReturnType | null = null; const clearTimeouts = () => { if (hoverOpenTimeoutRef) { clearTimeout(hoverOpenTimeoutRef); hoverOpenTimeoutRef = null; } if (hoverCloseTimeoutRef) { clearTimeout(hoverCloseTimeoutRef); hoverCloseTimeoutRef = null; } }; const handleMouseEnter = () => { clearTimeouts(); hoverOpenTimeoutRef = setTimeout(() => props.setIsOpen(true), props.delay); }; const handleMouseLeave = () => { clearTimeouts(); hoverCloseTimeoutRef = setTimeout(() => props.setIsOpen(false), 100); }; onCleanup(clearTimeouts); return ( setTriggerRef(el)} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} style={{ display: "inline-block" }}> {props.children} ); } interface FocusTooltipProps { content: JSXElement; placement: Placement; offset: number; children?: JSXElement; } export function FocusTooltip(props: FocusTooltipProps) { const [isOpen, setIsOpen] = createSignal(false); return ( {props.children} {props.content} ); } interface FocusTriggerProps { setIsOpen: (v: boolean) => void; children?: JSXElement; } function FocusTooltipTrigger(props: FocusTriggerProps) { const { setTriggerRef } = useFloatingContext(); return (
setTriggerRef(el)} onFocusIn={() => props.setIsOpen(true)} onFocusOut={() => props.setIsOpen(false)}> {props.children}
); } interface TooltipContentProps { placement: string; setIsOpen?: (v: boolean) => void; onMouseEnter?: () => void; onMouseLeave?: () => void; children?: JSXElement; } function TooltipContentWithArrow(props: TooltipContentProps) { const { position } = useFloatingContext(); let hoverCloseTimeoutRef: ReturnType | null = null; const handleMouseEnter = () => { if (hoverCloseTimeoutRef) { clearTimeout(hoverCloseTimeoutRef); hoverCloseTimeoutRef = null; } props.onMouseEnter?.(); }; const handleMouseLeave = () => { if (props.setIsOpen) { hoverCloseTimeoutRef = setTimeout(() => props.setIsOpen!(false), 50); } props.onMouseLeave?.(); }; onCleanup(() => { if (hoverCloseTimeoutRef) clearTimeout(hoverCloseTimeoutRef); }); const actualPlacement = () => position()?.placement ?? props.placement; const basePlacement = () => actualPlacement().split("-")[0]; return ( {props.children}
); }