feat(insights): bouton Fit view sur le graphe — reset zoom + clear focus
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m21s
CI / Deploy production (on server) (push) Failing after 2s

- zoomRef stocke le behavior d3.zoom pour accès externe au useEffect
- handleFitView: d3.zoomIdentity reset (600ms transition) + clear selectedClusterId
- Bouton Maximize2 en haut à droite du graphe avec aria-label
- cursor-pointer + focus-visible:ring pour a11y
This commit is contained in:
Antigravity
2026-06-28 09:28:36 +00:00
parent 1fc6728259
commit 1fc790f0c7
2 changed files with 23 additions and 2 deletions

View File

@@ -1,8 +1,8 @@
{ {
"version": 1, "version": 1,
"lastRunAtMs": 1782633053032, "lastRunAtMs": 1782633053032,
"turnsSinceLastRun": 6, "turnsSinceLastRun": 7,
"lastTranscriptMtimeMs": 1782633052959.9294, "lastTranscriptMtimeMs": 1782633052959.9294,
"lastProcessedGenerationId": "fa1ae817-7a99-4fd6-8b50-8407600557dd", "lastProcessedGenerationId": "5c41c5b1-0c40-4c8e-a6df-b22e873df8f6",
"trialStartedAtMs": null "trialStartedAtMs": null
} }

View File

@@ -2,6 +2,7 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import * as d3 from 'd3' import * as d3 from 'd3'
import { Maximize2 } from 'lucide-react'
interface Note { interface Note {
id: string id: string
@@ -49,6 +50,7 @@ export function NetworkGraph({
}: NetworkGraphProps) { }: NetworkGraphProps) {
const svgRef = useRef<SVGSVGElement>(null) const svgRef = useRef<SVGSVGElement>(null)
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const zoomRef = useRef<any>(null)
useEffect(() => { useEffect(() => {
if (!svgRef.current || !containerRef.current) return if (!svgRef.current || !containerRef.current) return
@@ -67,6 +69,7 @@ export function NetworkGraph({
g.attr('transform', event.transform) g.attr('transform', event.transform)
}) })
zoomRef.current = zoom
svg.call(zoom as any) svg.call(zoom as any)
// Filter notes with cluster assignments // Filter notes with cluster assignments
@@ -348,6 +351,15 @@ export function NetworkGraph({
} }
}, [notes, clusters, bridgeNotes, onNoteSelect, selectedClusterId]) }, [notes, clusters, bridgeNotes, onNoteSelect, selectedClusterId])
const handleFitView = () => {
if (!svgRef.current || !zoomRef.current) return
d3.select(svgRef.current)
.transition()
.duration(600)
.call(zoomRef.current.transform, d3.zoomIdentity)
onClusterSelect?.(null)
}
return ( return (
<div ref={containerRef} className="w-full h-full bg-paper dark:bg-[#121212] rounded-3xl overflow-hidden border border-border/40 relative"> <div ref={containerRef} className="w-full h-full bg-paper dark:bg-[#121212] rounded-3xl overflow-hidden border border-border/40 relative">
{/* Pastilles de cluster — cliquables pour activer le focus */} {/* Pastilles de cluster — cliquables pour activer le focus */}
@@ -378,6 +390,15 @@ export function NetworkGraph({
</button> </button>
)} )}
</div> </div>
{/* Fit view button */}
<button
onClick={handleFitView}
className="absolute top-6 right-6 z-10 flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-border/40 bg-white/90 dark:bg-black/80 text-concrete hover:text-ink dark:hover:text-dark-ink hover:border-concrete/40 text-[9px] font-bold uppercase tracking-wider transition-all shadow-sm cursor-pointer focus-visible:ring-2 focus-visible:ring-ochre/50 focus-visible:outline-none backdrop-blur-sm"
aria-label={fitViewLabel}
>
<Maximize2 size={11} />
{fitViewLabel}
</button>
<svg ref={svgRef} className="w-full h-full" /> <svg ref={svgRef} className="w-full h-full" />
</div> </div>
) )