"use client"
import { useIsMobile } from "@hooks/use-mobile"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@ui/components/collapsible"
import { ChevronDown, ChevronRight } from "lucide-react"
import { memo, useEffect, useState } from "react"
import type { GraphEdge, GraphNode, LegendProps } from "./types"
import { cn } from "@lib/utils"
// Cookie utility functions for legend state
const setCookie = (name: string, value: string, days = 365) => {
if (typeof document === "undefined") return
const expires = new Date()
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000)
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`
}
const getCookie = (name: string): string | null => {
if (typeof document === "undefined") return null
const nameEQ = `${name}=`
const ca = document.cookie.split(";")
for (let i = 0; i < ca.length; i++) {
let c = ca[i]
if (!c) continue
while (c.charAt(0) === " ") c = c.substring(1, c.length)
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length)
}
return null
}
interface ExtendedLegendProps extends LegendProps {
id?: string
nodes?: GraphNode[]
edges?: GraphEdge[]
isLoading?: boolean
}
// Toggle switch component matching Figma design
const SmallToggle = memo(function SmallToggle({
checked,
onChange,
}: {
checked: boolean
onChange: (checked: boolean) => void
}) {
return (
)
})
// Hexagon SVG for memory nodes
const HexagonIcon = memo(function HexagonIcon({
fill = "#0D2034",
stroke = "#3B73B8",
opacity = 1,
size = 12,
}: {
fill?: string
stroke?: string
opacity?: number
size?: number
}) {
return (
)
})
// Document icon (rounded square)
const DocumentIcon = memo(function DocumentIcon() {
return (
)
})
// Connection icon (graph)
const ConnectionIcon = memo(function ConnectionIcon() {
return (
)
})
// Line icon for connections
const LineIcon = memo(function LineIcon({
color,
dashed = false,
}: {
color: string
dashed?: boolean
}) {
return (
)
})
// Similarity circle icon
const SimilarityCircle = memo(function SimilarityCircle({
variant,
}: {
variant: "strong" | "weak"
}) {
return (
)
})
// Accordion row with count
const StatRow = memo(function StatRow({
icon,
label,
count,
expandable = false,
expanded = false,
onToggle,
children,
}: {
icon: React.ReactNode
label: string
count: number
expandable?: boolean
expanded?: boolean
onToggle?: () => void
children?: React.ReactNode
}) {
return (
{expandable && expanded && children && (
{children}
)}
)
})
// Toggle row for relations/similarity
const ToggleRow = memo(function ToggleRow({
icon,
label,
checked,
onChange,
}: {
icon: React.ReactNode
label: string
checked: boolean
onChange: (checked: boolean) => void
}) {
return (
)
})
export const Legend = memo(function Legend({
variant: _variant = "console",
id,
nodes = [],
edges = [],
isLoading: _isLoading = false,
}: ExtendedLegendProps) {
const isMobile = useIsMobile()
const [isExpanded, setIsExpanded] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
// Toggle states for relations
const [showUpdates, setShowUpdates] = useState(true)
const [showExtends, setShowExtends] = useState(true)
const [showInferences, setShowInferences] = useState(false)
// Toggle states for similarity
const [showStrong, setShowStrong] = useState(true)
const [showWeak, setShowWeak] = useState(true)
// Expanded accordion states
const [memoriesExpanded, setMemoriesExpanded] = useState(false)
const [documentsExpanded, setDocumentsExpanded] = useState(false)
const [connectionsExpanded, setConnectionsExpanded] = useState(true)
// Load saved preference on client side
useEffect(() => {
if (!isInitialized) {
const savedState = getCookie("legendCollapsed")
if (savedState === "true") {
setIsExpanded(false)
} else if (savedState === "false") {
setIsExpanded(true)
} else {
// Default: collapsed on mobile, collapsed on desktop too (per Figma)
setIsExpanded(false)
}
setIsInitialized(true)
}
}, [isInitialized])
// Save to cookie when state changes
const handleToggleExpanded = (expanded: boolean) => {
setIsExpanded(expanded)
setCookie("legendCollapsed", expanded ? "false" : "true")
}
// Calculate stats
const memoryCount = nodes.filter((n) => n.type === "memory").length
const documentCount = nodes.filter((n) => n.type === "document").length
const connectionCount = edges.length
// Hide on mobile
if (isMobile) return null
return (
{/* Glass background */}
{/* Header - always visible */}
{isExpanded ? (
) : (
)}
Legend
{/* Main content column */}
{/* STATISTICS Section */}
STATISTICS
{/* Memories */}
}
label="Memories"
count={memoryCount}
expandable
expanded={memoriesExpanded}
onToggle={() => setMemoriesExpanded(!memoriesExpanded)}
>
{/* Documents */}
}
label="Documents"
count={documentCount}
expandable
expanded={documentsExpanded}
onToggle={() => setDocumentsExpanded(!documentsExpanded)}
/>
{/* Connections */}
}
label="Connections"
count={connectionCount}
expandable
expanded={connectionsExpanded}
onToggle={() =>
setConnectionsExpanded(!connectionsExpanded)
}
>
}
label="Doc similarity"
checked={showStrong}
onChange={setShowStrong}
/>
{/* RELATIONS Section */}
RELATIONS
}
label="Updates"
checked={showUpdates}
onChange={setShowUpdates}
/>
}
label="Extends"
checked={showExtends}
onChange={setShowExtends}
/>
}
label="Inferences"
checked={showInferences}
onChange={setShowInferences}
/>
{/* SIMILARITY Section */}
SIMILARITY
}
label="Strong"
checked={showStrong}
onChange={setShowStrong}
/>
}
label="Weak"
checked={showWeak}
onChange={setShowWeak}
/>
{/* Scrollbar indicator */}
)
})
Legend.displayName = "Legend"