Sifa professional network frontend (Next.js, React, TailwindCSS)
sifa.id/
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} →
71 </a>
72 </>
73 )}
74 </span>
75 )}
76 </span>
77 );
78}