File size: 6,884 Bytes
5ccfcd1 3720e61 5ccfcd1 3720e61 5ccfcd1 3720e61 5ccfcd1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | import React from "react";
import {
FileText,
Plus,
Search,
X,
Trash2,
Wifi,
WifiOff,
RefreshCw
} from "lucide-react";
import { Document } from "../../types";
interface DocumentSidebarProps {
documents: Document[];
activeDocId: string;
setActiveDocId: (id: string) => void;
setShowDocModal: (show: boolean) => void;
handleDeleteDocument: (id: string, e: React.MouseEvent) => void;
documentSearchTerm: string;
setDocumentSearchTerm: (term: string) => void;
syncStatus: "synced" | "syncing" | "offline" | "error";
teamChangesEnabled: boolean;
activeCollaborators: number;
onClose?: () => void;
}
export default function DocumentSidebar({
documents,
activeDocId,
setActiveDocId,
setShowDocModal,
handleDeleteDocument,
documentSearchTerm,
setDocumentSearchTerm,
syncStatus,
teamChangesEnabled,
activeCollaborators,
onClose
}: DocumentSidebarProps) {
const filtered = documents.filter((doc) =>
doc.title.toLowerCase().includes(documentSearchTerm.toLowerCase())
);
return (
<div className="bg-[#15181E] border border-slate-800 rounded-xl p-4 space-y-3.5 shadow-md">
<div className="flex items-center justify-between">
<h2 className="text-xs font-mono uppercase tracking-wider text-slate-505 flex items-center gap-1.5 font-bold">
<FileText className="h-4 w-4 text-blue-500" /> Folder Documents
</h2>
<div className="flex items-center gap-1.5">
<button
onClick={() => setShowDocModal(true)}
className="p-1 px-2.5 bg-[#0F1115] hover:bg-slate-800 text-slate-300 hover:text-white border border-slate-800 rounded-md text-xs font-semibold flex items-center gap-1 transition cursor-pointer"
>
<Plus className="h-3 w-3" /> New
</button>
{onClose && (
<button
onClick={onClose}
className="p-1 bg-[#0F1115] hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800 rounded-md transition cursor-pointer"
title="Hide Sidebar"
>
<X className="h-3 w-3" />
</button>
)}
</div>
</div>
{/* Highly intuitive search input layout */}
<div className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2.5 pointer-events-none">
<Search className="h-3.5 w-3.5 text-slate-500" />
</span>
<input
type="text"
placeholder="Search by title..."
value={documentSearchTerm}
onChange={(e) => setDocumentSearchTerm(e.target.value)}
className="w-full text-xs bg-[#0F1115] border border-slate-800 rounded-lg pl-8 pr-7 py-2 text-slate-205 placeholder:text-slate-650 focus:outline-none focus:border-slate-700 tracking-tight transition-colors"
/>
{documentSearchTerm && (
<button
type="button"
onClick={() => setDocumentSearchTerm("")}
className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-500 hover:text-white transition-colors cursor-pointer"
title="Clear search query"
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>
{/* Documents List */}
<div className="space-y-1 max-h-[180px] overflow-y-auto pr-1">
{filtered.length === 0 ? (
<div className="text-center py-6 text-slate-500 text-xs font-mono">
No matching segments
</div>
) : (
filtered.map((doc) => {
const isSelected = doc.id === activeDocId;
return (
<div
key={doc.id}
onClick={() => setActiveDocId(doc.id)}
className={`w-full text-left px-3 py-2 rounded-lg transition cursor-pointer flex items-center justify-between gap-2 group ${
isSelected
? "bg-blue-600/10 border border-blue-500/20 text-white"
: "text-slate-400 hover:text-slate-200 hover:bg-[#0F1115]/50 border border-transparent"
}`}
>
<div className="font-mono text-xs truncate max-w-[80%]">
{doc.title}
</div>
<button
onClick={(e) => handleDeleteDocument(doc.id, e)}
className="opacity-0 group-hover:opacity-100 hover:text-red-400 transition p-1 cursor-pointer"
>
<Trash2 className="h-3 w-3" />
</button>
</div>
);
})
)}
</div>
{/* Secure Offline Persistent Sync Status Banner */}
<div className="pt-3 border-t border-slate-800 space-y-2.5">
<div className="flex items-center justify-between text-xs">
<span className="font-mono text-[10px] text-slate-500">Sync Pipeline</span>
{syncStatus === "synced" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20 text-[9px] font-bold">
<Wifi className="h-3 w-3" /> Online Synced
</span>
)}
{syncStatus === "syncing" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-amber-400 bg-amber-500/10 px-2 py-0.5 rounded-full border border-amber-500/20 text-[9px] font-bold">
<RefreshCw className="h-3 w-3 animate-spin" /> Uploading Segment
</span>
)}
{syncStatus === "offline" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-slate-400 bg-slate-800/10 px-2 py-0.5 rounded-full border border-slate-750 text-[9px] font-bold">
<WifiOff className="h-3 w-3" /> Offline Buffer
</span>
)}
{syncStatus === "error" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-red-405 bg-red-500/10 px-2 py-0.5 rounded-full border border-red-500/20 text-[9px] font-bold">
<WifiOff className="h-3 w-3" /> Sync Failed
</span>
)}
</div>
<div className="bg-[#0F1115]/60 border border-slate-800 p-2.5 rounded-lg space-y-1">
<div className="flex justify-between items-center text-[9px] text-slate-500 font-mono">
<span>Collaborators</span>
<span>{teamChangesEnabled ? activeCollaborators : "1"} Editor Online</span>
</div>
<div className="flex items-center gap-1 py-1">
<div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
<span className="font-mono text-[9px] text-slate-400">Sarah and Liam joined review panel</span>
</div>
</div>
</div>
</div>
);
}
|