1- import { createSignal , JSX , Show } from "solid-js" ;
1+ import { createSignal , JSX , onCleanup , Show } from "solid-js" ;
22import QuestionMarkIcon from "../icon/QuestionMarkIcon" ;
33
44export enum TooltipAlignment {
@@ -8,8 +8,10 @@ export enum TooltipAlignment {
88}
99
1010interface TooltipProps {
11- text : string ;
12- children ?: JSX . Element ;
11+ /** Optional trigger label – defaults to the question-mark icon */
12+ label ?: JSX . Element ;
13+ /** Popover content (can include links, rich JSX, etc.) */
14+ children : JSX . Element ;
1315}
1416
1517export default function QuestionTooltip ( props : TooltipProps ) {
@@ -21,6 +23,12 @@ export default function QuestionTooltip(props: TooltipProps) {
2123 } > ( { x : 0 , align : TooltipAlignment . Center , containerWidth : 0 } ) ;
2224
2325 let containerRef : HTMLDivElement | undefined ;
26+ let hideTimeout : ReturnType < typeof setTimeout > | undefined ;
27+
28+ // Clean up any pending timeout when the component unmounts
29+ onCleanup ( ( ) => {
30+ if ( hideTimeout ) clearTimeout ( hideTimeout ) ;
31+ } ) ;
2432
2533 const determineAlignment = ( rect : DOMRect ) : TooltipAlignment => {
2634 const tooltipWidth = 256 ; // w-64 is 16rem = 256px
@@ -41,7 +49,11 @@ export default function QuestionTooltip(props: TooltipProps) {
4149 }
4250 } ;
4351
44- const handleMouseEnter = ( ) => {
52+ const show = ( ) => {
53+ if ( hideTimeout ) {
54+ clearTimeout ( hideTimeout ) ;
55+ hideTimeout = undefined ;
56+ }
4557 if ( containerRef ) {
4658 const rect = containerRef . getBoundingClientRect ( ) ;
4759 const align = determineAlignment ( rect ) ;
@@ -50,39 +62,39 @@ export default function QuestionTooltip(props: TooltipProps) {
5062 setIsVisible ( true ) ;
5163 } ;
5264
53- const handleMouseLeave = ( ) => setIsVisible ( false ) ;
65+ const hide = ( ) => {
66+ // Small delay so the user can move their cursor into the popover
67+ hideTimeout = setTimeout ( ( ) => setIsVisible ( false ) , 150 ) ;
68+ } ;
5469
5570 return (
5671 < div
5772 ref = { containerRef }
5873 class = "relative inline-flex items-center justify-center cursor-help"
59- onMouseEnter = { handleMouseEnter }
60- onMouseLeave = { handleMouseLeave }
61- onFocus = { handleMouseEnter }
62- onBlur = { handleMouseLeave }
74+ onMouseEnter = { show }
75+ onMouseLeave = { hide }
76+ onFocus = { show }
77+ onBlur = { hide }
6378 tabIndex = { 0 }
6479 aria-label = "More information"
6580 >
66- < Show
67- when = { props . children }
68- fallback = {
69- < QuestionMarkIcon class = "size-5 text-gray-500 hover:text-gray-700 transition-colors" />
70- }
71- >
72- { props . children }
73- </ Show >
81+ { props . label ?? (
82+ < QuestionMarkIcon class = "size-5 text-gray-500 hover:text-gray-700 transition-colors" />
83+ ) }
7484
7585 < Show when = { isVisible ( ) } >
7686 < div
77- class = { `absolute z-50 w-64 p-3 mt-2 text-sm text-gray-800 bg-white border border-gray-200 rounded-lg shadow-lg top-full pointer-events-none fade-in ${
87+ class = { `absolute z-50 w-64 p-3 mt-2 text-sm text-gray-800 bg-white border border-gray-200 rounded-lg shadow-lg top-full fade-in ${
7888 position ( ) . align === TooltipAlignment . Center
7989 ? "left-1/2 -translate-x-1/2"
8090 : position ( ) . align === TooltipAlignment . Left
8191 ? "left-0"
8292 : "right-0"
8393 } `}
94+ onMouseEnter = { show }
95+ onMouseLeave = { hide }
8496 >
85- { props . text }
97+ { props . children }
8698 { /* Decorative arrow pointing up */ }
8799 < div
88100 class = { `absolute w-3 h-3 bg-white border-t border-l border-gray-200 rotate-45 -top-[7px] ${
0 commit comments