5.2 KB165 lines
Blame
1"use client";
2
3import { useEffect, useRef } from "react";
4import { useCollab } from "./collab-provider";
5
6export interface Diagram {
7 id: string;
8 title: string;
9 section?: string;
10 code: string;
11}
12
13interface DiagramDrawerProps {
14 diagrams: Diagram[];
15 sections: string[];
16 activeId: string | null;
17 onSelect: (id: string) => void;
18}
19
20export function DiagramDrawer({ diagrams, sections, activeId, onSelect }: DiagramDrawerProps) {
21 const { notes, users } = useCollab();
22 const listRef = useRef<HTMLDivElement>(null);
23
24 // Keyboard navigation
25 useEffect(() => {
26 function handleKey(e: KeyboardEvent) {
27 if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
28 if (e.key === "ArrowUp" || e.key === "k") {
29 e.preventDefault();
30 const idx = diagrams.findIndex((d) => d.id === activeId);
31 if (idx > 0) onSelect(diagrams[idx - 1].id);
32 }
33 if (e.key === "ArrowDown" || e.key === "j") {
34 e.preventDefault();
35 const idx = diagrams.findIndex((d) => d.id === activeId);
36 if (idx < diagrams.length - 1) onSelect(diagrams[idx + 1].id);
37 }
38 }
39 document.addEventListener("keydown", handleKey);
40 return () => document.removeEventListener("keydown", handleKey);
41 }, [diagrams, activeId, onSelect]);
42
43 // Group diagrams by section
44 const sectionOrder = sections.length > 0 ? sections : [""];
45 const grouped = sectionOrder.map((sec) => ({
46 section: sec,
47 items: diagrams.filter((d) => (d.section ?? "") === sec),
48 })).filter((g) => g.items.length > 0);
49
50 const userList = Object.values(users);
51
52 return (
53 <div
54 style={{
55 width: 240,
56 borderRight: "1px solid var(--border)",
57 display: "flex",
58 flexDirection: "column",
59 flexShrink: 0,
60 overflow: "hidden",
61 background: "var(--bg-card)",
62 }}
63 >
64 <div
65 style={{
66 padding: "8px 16px",
67 fontSize: "11px",
68 fontWeight: 600,
69 fontFamily: "'JetBrains Mono', Menlo, monospace",
70 letterSpacing: "0.04em",
71 textTransform: "uppercase",
72 color: "var(--text-faint)",
73 borderBottom: "1px solid var(--divide)",
74 }}
75 >
76 Diagrams
77 </div>
78 <div ref={listRef} style={{ flex: 1, overflowY: "auto" }}>
79 {grouped.map((group) => (
80 <div key={group.section || "_default"}>
81 {group.section && (
82 <div
83 style={{
84 padding: "6px 16px 4px",
85 fontSize: "10px",
86 fontWeight: 600,
87 letterSpacing: "0.06em",
88 textTransform: "uppercase",
89 color: "var(--text-faint)",
90 }}
91 >
92 {group.section}
93 </div>
94 )}
95 {group.items.map((d) => {
96 const isActive = d.id === activeId;
97 const noteCount = (notes[d.id] ?? []).length;
98 const viewerDots = userList.filter((u) => u.activeTab === d.id);
99
100 return (
101 <button
102 key={d.id}
103 onClick={() => onSelect(d.id)}
104 style={{
105 display: "flex",
106 alignItems: "center",
107 gap: 8,
108 width: "100%",
109 textAlign: "left",
110 padding: "7px 16px",
111 border: "none",
112 borderRadius: 0,
113 background: isActive ? "var(--bg-hover)" : "transparent",
114 color: isActive ? "var(--text-primary)" : "var(--text-muted)",
115 fontWeight: isActive ? 500 : 400,
116 fontSize: "13px",
117 cursor: "pointer",
118 font: "inherit",
119 lineHeight: 1.4,
120 }}
121 >
122 <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
123 {d.title}
124 </span>
125 {(viewerDots.length > 0 || noteCount > 0) && (
126 <span style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
127 {viewerDots.map((u) => (
128 <span
129 key={u.id}
130 style={{
131 width: 6,
132 height: 6,
133 borderRadius: "50%",
134 backgroundColor: u.color,
135 }}
136 />
137 ))}
138 {noteCount > 0 && (
139 <span style={{ fontSize: "11px", color: "var(--text-faint)" }}>
140 {noteCount}
141 </span>
142 )}
143 </span>
144 )}
145 </button>
146 );
147 })}
148 </div>
149 ))}
150 </div>
151 <div
152 style={{
153 padding: "6px 16px",
154 fontSize: "10px",
155 fontFamily: "'JetBrains Mono', Menlo, monospace",
156 color: "var(--text-faint)",
157 borderTop: "1px solid var(--divide)",
158 }}
159 >
160 ↑ ↓ to navigate
161 </div>
162 </div>
163 );
164}
165