diff --git a/src/App.jsx b/src/App.jsx index 9769276..e05dc03 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,7 +13,7 @@ // limitations under the License. import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react"; -import { Moon, PanelLeft, Sun } from "lucide-react"; +import { Moon, PanelLeft, PanelRight, Sun } from "lucide-react"; import { filterConceptIds } from "./navigation.js"; import { Sidebar } from "./Sidebar.jsx"; @@ -22,6 +22,10 @@ import { preferredTheme, readStoredTheme, saveTheme } from "./theme.js"; const Detail = lazy(() => import("./Detail.jsx").then((module) => ({ default: module.Detail }))); const Graph = lazy(() => import("./Graph.jsx").then((module) => ({ default: module.Graph }))); +const MIN_DETAIL_WIDTH = 260; +const MAX_DETAIL_WIDTH = 640; +const DEFAULT_DETAIL_WIDTH = 340; + export function App({ bundle }) { const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState(""); @@ -30,6 +34,8 @@ export function App({ bundle }) { const [sidebarOpen, setSidebarOpen] = useState( () => typeof window === "undefined" || !window.matchMedia("(max-width: 860px)").matches, ); + const [detailOpen, setDetailOpen] = useState(true); + const [detailWidth, setDetailWidth] = useState(DEFAULT_DETAIL_WIDTH); const [theme, setTheme] = useState(() => readStoredTheme() || preferredTheme()); const [manualTheme, setManualTheme] = useState(() => Boolean(readStoredTheme())); const conceptIds = useMemo(() => new Set(bundle.nodes.map((node) => node.data.id)), [bundle.nodes]); @@ -76,6 +82,29 @@ export function App({ bundle }) { saveTheme(nextTheme); } + function toggleDetail() { + setDetailOpen((open) => !open); + } + + function handleDetailResizeStart(event) { + event.preventDefault(); + const startX = event.clientX; + const startWidth = detailWidth; + + function handleMove(moveEvent) { + const nextWidth = startWidth + (startX - moveEvent.clientX); + setDetailWidth(Math.min(MAX_DETAIL_WIDTH, Math.max(MIN_DETAIL_WIDTH, nextWidth))); + } + + function handleUp() { + document.removeEventListener("pointermove", handleMove); + document.removeEventListener("pointerup", handleUp); + } + + document.addEventListener("pointermove", handleMove); + document.addEventListener("pointerup", handleUp); + } + return (