|
11 | 11 | * @module pages/SkillsetDetailPage |
12 | 12 | */ |
13 | 13 |
|
14 | | -import { useState, useCallback, useMemo, useRef } from "react"; |
| 14 | +import { useState, useCallback, useMemo } from "react"; |
15 | 15 | import { useParams, useNavigate, useSearchParams } from "react-router-dom"; |
16 | 16 | import { useTranslation } from "react-i18next"; |
17 | 17 | import { PageTransition } from "@/components/layout/PageTransition"; |
@@ -64,44 +64,21 @@ export function SkillsetDetailPage() { |
64 | 64 | const [showPermissions, setShowPermissions] = useState(false); |
65 | 65 | const [showDelete, setShowDelete] = useState(false); |
66 | 66 |
|
67 | | - // Hover state for graph nodes (now canvas-based). Shows floating preview dialog. |
68 | | - // Position tracks cursor for "beside my cursor" placement. |
69 | | - const [hoveredMemberRef, setHoveredMemberRef] = useState<string | null>(null); |
70 | | - const [hoveredPos, setHoveredPos] = useState<{ clientX: number; clientY: number } | null>(null); |
| 67 | + // Click-to-open state for graph node package preview dialog. |
| 68 | + const [previewMemberRef, setPreviewMemberRef] = useState<string | null>(null); |
71 | 69 |
|
72 | | - // Grace timer so the popup survives the gap between the node and the dialog: |
73 | | - // leaving a node SCHEDULES a close, but entering the popup cancels it (#1094 — |
74 | | - // previously the popup was "gone already" before the cursor reached it). |
75 | | - const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null); |
76 | | - const cancelClose = useCallback(() => { |
77 | | - if (closeTimer.current) { |
78 | | - clearTimeout(closeTimer.current); |
79 | | - closeTimer.current = null; |
80 | | - } |
81 | | - }, []); |
82 | 70 | const closePreview = useCallback(() => { |
83 | | - cancelClose(); |
84 | | - setHoveredMemberRef(null); |
85 | | - setHoveredPos(null); |
86 | | - }, [cancelClose]); |
| 71 | + setPreviewMemberRef(null); |
| 72 | + }, []); |
87 | 73 |
|
88 | | - // Stable callback so the memoized graph doesn't re-render on every hover. |
89 | | - const handleHoverMember = useCallback( |
90 | | - (ref: string | null, pos?: { clientX: number; clientY: number }) => { |
| 74 | + // Stable callback so the memoized graph doesn't re-render on every click. |
| 75 | + const handleClickMember = useCallback( |
| 76 | + (ref: string | null, _pos?: { clientX: number; clientY: number }) => { |
91 | 77 | if (ref) { |
92 | | - cancelClose(); |
93 | | - setHoveredMemberRef(ref); |
94 | | - if (pos) setHoveredPos(pos); |
95 | | - } else { |
96 | | - // Left the node — let the cursor reach the dialog (~250ms) before close. |
97 | | - cancelClose(); |
98 | | - closeTimer.current = setTimeout(() => { |
99 | | - setHoveredMemberRef(null); |
100 | | - setHoveredPos(null); |
101 | | - }, 250); |
| 78 | + setPreviewMemberRef(ref); |
102 | 79 | } |
103 | 80 | }, |
104 | | - [cancelClose], |
| 81 | + [], |
105 | 82 | ); |
106 | 83 |
|
107 | 84 | // Two-id split: delete is GUID-only on the wire; cache cleanup keys on the |
@@ -237,40 +214,38 @@ export function SkillsetDetailPage() { |
237 | 214 | members={graphMembers} |
238 | 215 | edges={depEdges} |
239 | 216 | className="h-full" |
240 | | - onHoverMember={handleHoverMember} |
| 217 | + onHoverMember={handleClickMember} |
241 | 218 | /> |
242 | 219 |
|
243 | | - {/* Floating package preview dialog for the hovered graph node. |
244 | | - Positioned fixed beside the cursor (offset right+down) so it appears |
245 | | - "right beside my cursor". Larger size for better readability of the |
246 | | - package tree + content. Uses canvas node hover (no more blinking from |
247 | | - SVG/Mermaid). Dismiss on mouseleave of the popup. */} |
248 | | - {hoveredMemberRef && hoveredPos && ( |
| 220 | + {/* Click-to-open package preview dialog — fixed size, centered. */} |
| 221 | + {previewMemberRef && ( |
249 | 222 | <div |
250 | | - className="fixed z-[100] flex w-[800px] max-w-[calc(100vw-2rem)] h-[40vh] flex-col overflow-hidden rounded-md border border-subtle bg-card card-impression text-sm shadow-xl" |
251 | | - style={{ |
252 | | - left: Math.min((hoveredPos.clientX ?? 0) + 18, window.innerWidth - 816), |
253 | | - top: Math.min((hoveredPos.clientY ?? 0) + 8, window.innerHeight - 120), |
254 | | - }} |
255 | | - onMouseEnter={cancelClose} |
256 | | - onMouseLeave={closePreview} |
| 223 | + className="fixed inset-0 z-[100] flex items-center justify-center" |
| 224 | + onClick={closePreview} |
257 | 225 | > |
258 | | - <div className="flex shrink-0 items-center justify-between border-b border-subtle px-3 py-2 font-mono text-[11px] text-meta"> |
259 | | - <span className="truncate font-medium text-strong">{hoveredMemberRef}</span> |
260 | | - <button |
261 | | - type="button" |
262 | | - onClick={closePreview} |
263 | | - className="ml-2 shrink-0 text-meta hover:text-danger" |
264 | | - aria-label="Close preview" |
265 | | - > |
266 | | - × |
267 | | - </button> |
268 | | - </div> |
269 | | - <div className="min-h-0 flex-1 p-2"> |
270 | | - <SkillsetMemberViewer |
271 | | - members={skillset.members} |
272 | | - previewRef={hoveredMemberRef} |
273 | | - /> |
| 226 | + <div className="absolute inset-0 bg-black/30 backdrop-blur-sm" /> |
| 227 | + <div |
| 228 | + className="relative flex flex-col overflow-hidden rounded-md border border-subtle bg-card card-impression text-sm shadow-xl" |
| 229 | + style={{ top: '15vh', bottom: '15vh', left: '15vw', right: '15vw', position: 'fixed' }} |
| 230 | + onClick={(e) => e.stopPropagation()} |
| 231 | + > |
| 232 | + <div className="flex shrink-0 items-center justify-between border-b border-subtle px-3 py-2 font-mono text-[11px] text-meta"> |
| 233 | + <span className="truncate font-medium text-strong">{previewMemberRef}</span> |
| 234 | + <button |
| 235 | + type="button" |
| 236 | + onClick={closePreview} |
| 237 | + className="ml-2 shrink-0 text-meta hover:text-danger" |
| 238 | + aria-label="Close preview" |
| 239 | + > |
| 240 | + × |
| 241 | + </button> |
| 242 | + </div> |
| 243 | + <div className="min-h-0 flex-1 p-2"> |
| 244 | + <SkillsetMemberViewer |
| 245 | + members={skillset.members} |
| 246 | + previewRef={previewMemberRef} |
| 247 | + /> |
| 248 | + </div> |
274 | 249 | </div> |
275 | 250 | </div> |
276 | 251 | )} |
|
0 commit comments