web/app/collab/components/diagram-drawer.tsxblame
View source
0b4b5821"use client";
0b4b5822
0b4b5823import { useEffect, useRef } from "react";
0b4b5824import { useCollab } from "./collab-provider";
0b4b5825
0b4b5826export interface Diagram {
0b4b5827 id: string;
0b4b5828 title: string;
0b4b5829 section?: string;
0b4b58210 code: string;
0b4b58211}
0b4b58212
0b4b58213interface DiagramDrawerProps {
0b4b58214 diagrams: Diagram[];
0b4b58215 sections: string[];
0b4b58216 activeId: string | null;
0b4b58217 onSelect: (id: string) => void;
0b4b58218}
0b4b58219
0b4b58220export function DiagramDrawer({ diagrams, sections, activeId, onSelect }: DiagramDrawerProps) {
0b4b58221 const { notes, users } = useCollab();
0b4b58222 const listRef = useRef<HTMLDivElement>(null);
0b4b58223
0b4b58224 // Keyboard navigation
0b4b58225 useEffect(() => {
0b4b58226 function handleKey(e: KeyboardEvent) {
0b4b58227 if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
0b4b58228 if (e.key === "ArrowUp" || e.key === "k") {
0b4b58229 e.preventDefault();
0b4b58230 const idx = diagrams.findIndex((d) => d.id === activeId);
0b4b58231 if (idx > 0) onSelect(diagrams[idx - 1].id);
0b4b58232 }
0b4b58233 if (e.key === "ArrowDown" || e.key === "j") {
0b4b58234 e.preventDefault();
0b4b58235 const idx = diagrams.findIndex((d) => d.id === activeId);
0b4b58236 if (idx < diagrams.length - 1) onSelect(diagrams[idx + 1].id);
0b4b58237 }
0b4b58238 }
0b4b58239 document.addEventListener("keydown", handleKey);
0b4b58240 return () => document.removeEventListener("keydown", handleKey);
0b4b58241 }, [diagrams, activeId, onSelect]);
0b4b58242
0b4b58243 // Group diagrams by section
0b4b58244 const sectionOrder = sections.length > 0 ? sections : [""];
0b4b58245 const grouped = sectionOrder.map((sec) => ({
0b4b58246 section: sec,
0b4b58247 items: diagrams.filter((d) => (d.section ?? "") === sec),
0b4b58248 })).filter((g) => g.items.length > 0);
0b4b58249
0b4b58250 const userList = Object.values(users);
0b4b58251
0b4b58252 return (
0b4b58253 <div
0b4b58254 style={{
0b4b58255 width: 240,
0b4b58256 borderRight: "1px solid var(--border)",
0b4b58257 display: "flex",
0b4b58258 flexDirection: "column",
0b4b58259 flexShrink: 0,
0b4b58260 overflow: "hidden",
0b4b58261 background: "var(--bg-card)",
0b4b58262 }}
0b4b58263 >
0b4b58264 <div
0b4b58265 style={{
0b4b58266 padding: "8px 16px",
0b4b58267 fontSize: "11px",
0b4b58268 fontWeight: 600,
0b4b58269 fontFamily: "'JetBrains Mono', Menlo, monospace",
0b4b58270 letterSpacing: "0.04em",
0b4b58271 textTransform: "uppercase",
0b4b58272 color: "var(--text-faint)",
0b4b58273 borderBottom: "1px solid var(--divide)",
0b4b58274 }}
0b4b58275 >
0b4b58276 Diagrams
0b4b58277 </div>
0b4b58278 <div ref={listRef} style={{ flex: 1, overflowY: "auto" }}>
0b4b58279 {grouped.map((group) => (
0b4b58280 <div key={group.section || "_default"}>
0b4b58281 {group.section && (
0b4b58282 <div
0b4b58283 style={{
0b4b58284 padding: "6px 16px 4px",
0b4b58285 fontSize: "10px",
0b4b58286 fontWeight: 600,
0b4b58287 letterSpacing: "0.06em",
0b4b58288 textTransform: "uppercase",
0b4b58289 color: "var(--text-faint)",
0b4b58290 }}
0b4b58291 >
0b4b58292 {group.section}
0b4b58293 </div>
0b4b58294 )}
0b4b58295 {group.items.map((d) => {
0b4b58296 const isActive = d.id === activeId;
0b4b58297 const noteCount = (notes[d.id] ?? []).length;
0b4b58298 const viewerDots = userList.filter((u) => u.activeTab === d.id);
0b4b58299
0b4b582100 return (
0b4b582101 <button
0b4b582102 key={d.id}
0b4b582103 onClick={() => onSelect(d.id)}
0b4b582104 style={{
0b4b582105 display: "flex",
0b4b582106 alignItems: "center",
0b4b582107 gap: 8,
0b4b582108 width: "100%",
0b4b582109 textAlign: "left",
0b4b582110 padding: "7px 16px",
0b4b582111 border: "none",
0b4b582112 borderRadius: 0,
0b4b582113 background: isActive ? "var(--bg-hover)" : "transparent",
0b4b582114 color: isActive ? "var(--text-primary)" : "var(--text-muted)",
0b4b582115 fontWeight: isActive ? 500 : 400,
0b4b582116 fontSize: "13px",
0b4b582117 cursor: "pointer",
0b4b582118 font: "inherit",
0b4b582119 lineHeight: 1.4,
0b4b582120 }}
0b4b582121 >
0b4b582122 <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
0b4b582123 {d.title}
0b4b582124 </span>
0b4b582125 {(viewerDots.length > 0 || noteCount > 0) && (
0b4b582126 <span style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
0b4b582127 {viewerDots.map((u) => (
0b4b582128 <span
0b4b582129 key={u.id}
0b4b582130 style={{
0b4b582131 width: 6,
0b4b582132 height: 6,
0b4b582133 borderRadius: "50%",
0b4b582134 backgroundColor: u.color,
0b4b582135 }}
0b4b582136 />
0b4b582137 ))}
0b4b582138 {noteCount > 0 && (
0b4b582139 <span style={{ fontSize: "11px", color: "var(--text-faint)" }}>
0b4b582140 {noteCount}
0b4b582141 </span>
0b4b582142 )}
0b4b582143 </span>
0b4b582144 )}
0b4b582145 </button>
0b4b582146 );
0b4b582147 })}
0b4b582148 </div>
0b4b582149 ))}
0b4b582150 </div>
0b4b582151 <div
0b4b582152 style={{
0b4b582153 padding: "6px 16px",
0b4b582154 fontSize: "10px",
0b4b582155 fontFamily: "'JetBrains Mono', Menlo, monospace",
0b4b582156 color: "var(--text-faint)",
0b4b582157 borderTop: "1px solid var(--divide)",
0b4b582158 }}
0b4b582159 >
0b4b582160 ↑ ↓ to navigate
0b4b582161 </div>
0b4b582162 </div>
0b4b582163 );
0b4b582164}