Sifa professional network frontend (Next.js, React, TailwindCSS) sifa.id/
at main 78 lines 2.4 kB view raw
1'use client'; 2 3import { useState, useRef, type ReactNode } from 'react'; 4 5interface ActivityTooltipProps { 6 appName: string; 7 tooltipDescription: string; 8 tooltipNetworkNote?: string; 9 appUrl?: string; 10 children: ReactNode; 11} 12 13export function ActivityTooltip({ 14 appName, 15 tooltipDescription, 16 tooltipNetworkNote, 17 appUrl, 18 children, 19}: ActivityTooltipProps) { 20 const [open, setOpen] = useState(false); 21 const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined); 22 const tooltipId = `tooltip-${appName.replace(/\s+/g, '-').toLowerCase()}`; 23 24 function handleEnter() { 25 clearTimeout(timeoutRef.current); 26 setOpen(true); 27 } 28 29 function handleLeave() { 30 timeoutRef.current = setTimeout(() => setOpen(false), 150); 31 } 32 33 return ( 34 /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- tooltip trigger delegates focus/keyboard to the interactive child */ 35 <span 36 className="relative inline-flex" 37 onMouseEnter={handleEnter} 38 onMouseLeave={handleLeave} 39 onFocus={handleEnter} 40 onBlur={handleLeave} 41 > 42 <span aria-describedby={open ? tooltipId : undefined}>{children}</span> 43 {open && ( 44 /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- tooltip must track mouse to stay open when user moves to it */ 45 <span 46 id={tooltipId} 47 role="tooltip" 48 className="absolute bottom-full left-1/2 z-50 mb-2 w-56 -translate-x-1/2 rounded-md border border-border bg-popover px-3 py-2 text-xs text-popover-foreground shadow-md" 49 onMouseEnter={handleEnter} 50 onMouseLeave={handleLeave} 51 > 52 <span className="font-medium">{appName}</span> 53 <br /> 54 <span className="text-muted-foreground">{tooltipDescription}</span> 55 {tooltipNetworkNote && ( 56 <> 57 <br /> 58 <span className="text-muted-foreground/80">{tooltipNetworkNote}</span> 59 </> 60 )} 61 {appUrl && ( 62 <> 63 <br /> 64 <a 65 href={appUrl} 66 target="_blank" 67 rel="noopener noreferrer" 68 className="mt-1 inline-block text-primary hover:underline" 69 > 70 View on {new URL(appUrl).hostname} &rarr; 71 </a> 72 </> 73 )} 74 </span> 75 )} 76 </span> 77 ); 78}