diff --git a/packages/memory-graph/src/__tests__/legend.test.tsx b/packages/memory-graph/src/__tests__/legend.test.tsx new file mode 100644 index 00000000..f9cafcb9 --- /dev/null +++ b/packages/memory-graph/src/__tests__/legend.test.tsx @@ -0,0 +1,42 @@ +import { cleanup, fireEvent, render, screen } from "@testing-library/react" +import { afterEach, describe, expect, it } from "vitest" +import { Legend } from "../components/legend" +import { DEFAULT_COLORS } from "../constants" + +afterEach(cleanup) + +describe("Legend", () => { + it("keeps disclosure state and motion behavior in sync", () => { + const { container } = render( + , + ) + + const legendButton = screen.getByRole("button", { name: "Legend" }) + const legendReveal = container.querySelector(".mg-legend-reveal") + + expect(legendButton.getAttribute("aria-expanded")).toBe("false") + expect(legendReveal?.getAttribute("data-expanded")).toBe("false") + expect( + document.getElementById("supermemory-graph-legend-motion")?.textContent, + ).toContain("prefers-reduced-motion") + + fireEvent.click(legendButton) + + expect(legendButton.getAttribute("aria-expanded")).toBe("true") + expect(legendReveal?.getAttribute("data-expanded")).toBe("true") + }) + + it("exposes the connections disclosure state", () => { + render() + fireEvent.click(screen.getByRole("button", { name: "Legend" })) + + const connectionsButton = screen.getByRole("button", { + name: /Connections/, + }) + expect(connectionsButton.getAttribute("aria-expanded")).toBe("true") + + fireEvent.click(connectionsButton) + + expect(connectionsButton.getAttribute("aria-expanded")).toBe("false") + }) +}) diff --git a/packages/memory-graph/src/components/legend.tsx b/packages/memory-graph/src/components/legend.tsx index c65073f4..14a4d27a 100644 --- a/packages/memory-graph/src/components/legend.tsx +++ b/packages/memory-graph/src/components/legend.tsx @@ -1,4 +1,4 @@ -import { memo, useState } from "react" +import { memo, useEffect, useState } from "react" import { DEFAULT_LABELS } from "../constants" import type { GraphEdge, @@ -161,25 +161,6 @@ function ChevronDownIcon({ color }: { color: string }) { ) } -function ChevronRightIcon({ color }: { color: string }) { - return ( - - ) -} - function StatRow({ icon, label, @@ -241,6 +222,7 @@ function StatRow({ return (
{count} - {expandable && expanded && children && ( -
{children}
+ {expandable && children && ( + +
{children}
+
)} ) } +const LEGEND_MOTION_STYLE_ID = "supermemory-graph-legend-motion" +const LEGEND_MOTION_STYLES = ` +.mg-legend-reveal { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + transition: + grid-template-rows 200ms cubic-bezier(0.23, 1, 0.32, 1), + opacity 200ms cubic-bezier(0.23, 1, 0.32, 1); +} +.mg-legend-reveal[data-expanded="true"] { + grid-template-rows: 1fr; + opacity: 1; +} +.mg-legend-chevron { + display: inline-flex; + flex-shrink: 0; + transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1); +} +@media (prefers-reduced-motion: reduce) { + .mg-legend-reveal, + .mg-legend-chevron { + transition: none; + } +} +` + +function LegendChevron({ + color, + expanded, +}: { + color: string + expanded: boolean +}) { + return ( + + + + ) +} + +function LegendReveal({ + expanded, + children, +}: { + expanded: boolean + children: React.ReactNode +}) { + return ( +
+
{children}
+
+ ) +} + function countEdgesByType(edges: GraphEdge[], edgeType: GraphEdge["edgeType"]) { return edges.filter((edge) => edge.edgeType === edgeType).length } @@ -306,6 +345,14 @@ export const Legend = memo(function Legend({ const [isExpanded, setIsExpanded] = useState(false) const [connectionsExpanded, setConnectionsExpanded] = useState(true) + useEffect(() => { + if (document.getElementById(LEGEND_MOTION_STYLE_ID)) return + const style = document.createElement("style") + style.id = LEGEND_MOTION_STYLE_ID + style.textContent = LEGEND_MOTION_STYLES + document.head.appendChild(style) + }, []) + const memoryCount = nodes.filter((n) => n.type === "memory").length const documentCount = nodes.filter((n) => n.type === "document").length const connectionCount = edges.length @@ -324,7 +371,6 @@ export const Legend = memo(function Legend({ const hasClusterColors = nodes.some((node) => node.clusterColor) const outerStyle: React.CSSProperties = { - overflow: "hidden", width: compact ? "min(214px, calc(100vw - 32px))" : 214, maxWidth: "100%", } @@ -408,7 +454,7 @@ export const Legend = memo(function Legend({ } const expandedContentStyle: React.CSSProperties = { - marginTop: 16, + paddingTop: 16, display: "flex", flexDirection: "column", gap: 16, @@ -427,19 +473,16 @@ export const Legend = memo(function Legend({
- {isExpanded && ( +
{/* Statistics section */}
@@ -672,7 +715,7 @@ export const Legend = memo(function Legend({
- )} +