codeverse / components /extensions /ExtensionPanel.tsx
shubhjn's picture
fix bul memeory problem
0b6a8e8
"use client";
import { useState} from "react";
import { motion } from "framer-motion";
import {
Package, Search, Download,
ToggleLeft, ToggleRight, RefreshCw
} from "lucide-react";
import { BUILTIN_EXTENSIONS } from "@/constants/extensions";
import { toast } from "sonner";
type ExtCategory = "all" | "installed" | "Language" | "Snippets" | "Formatter" | "Linter" | "SCM" | "Tools";
const CATEGORIES: { id: ExtCategory; label: string }[] = [
{ id: "all", label: "All" },
{ id: "installed", label: "Installed" },
{ id: "Language", label: "Language" },
{ id: "Snippets", label: "Snippets" },
{ id: "Formatter", label: "Formatter" },
{ id: "Linter", label: "Linter" },
{ id: "SCM", label: "SCM" },
];
export default function ExtensionPanel() {
const [search, setSearch] = useState("");
const [category, setCategory] = useState<ExtCategory>("all");
const [enabled, setEnabled] = useState<Record<string, boolean>>(() =>
Object.fromEntries(BUILTIN_EXTENSIONS.map((e) => [e.id, e.enabled]))
);
const [installed, setInstalled] = useState<Record<string, boolean>>(() =>
Object.fromEntries(BUILTIN_EXTENSIONS.map((e) => [e.id, e.preinstalled]))
);
const [installing, setInstalling] = useState<string | null>(null);
const toggle = (id: string) => {
setEnabled((prev) => ({ ...prev, [id]: !prev[id] }));
toast.success(enabled[id] ? "Extension disabled" : "Extension enabled");
};
const install = async (id: string) => {
setInstalling(id);
await new Promise((r) => setTimeout(r, 1200));
setInstalled((prev) => ({ ...prev, [id]: true }));
setEnabled((prev) => ({ ...prev, [id]: true }));
setInstalling(null);
toast.success(`Extension installed!`);
};
const filtered = BUILTIN_EXTENSIONS.filter((ext) => {
const matchSearch = search
? ext.name.toLowerCase().includes(search.toLowerCase()) ||
ext.description.toLowerCase().includes(search.toLowerCase())
: true;
const matchCat =
category === "all" ? true :
category === "installed" ? installed[ext.id] :
ext.category === category;
return matchSearch && matchCat;
});
return (
<div className="sidebar h-full flex flex-col overflow-hidden">
{/* Header */}
<div className="sidebar-header">
<span>Extensions</span>
<button className="activity-btn w-6 h-6" title="Refresh">
<RefreshCw size={12} />
</button>
</div>
{/* Search */}
<div className="px-2 py-2">
<div className="relative">
<Search size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-(--text-muted)" />
<input
className="input text-xs pl-7 py-1.5"
placeholder="Search extensions…"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>
{/* Category tabs */}
<div className="flex gap-1 px-2 pb-2 overflow-x-auto shrink-0">
{CATEGORIES.map((cat) => (
<button
key={cat.id}
onClick={() => setCategory(cat.id)}
className={`px-2.5 py-0.5 rounded text-[10px] font-medium whitespace-nowrap transition-all shrink-0 ${category === cat.id ? "bg-(--accent) text-(--text-on-accent)" : "bg-(--bg-3) text-(--text-2) hover:bg-(--surface-hover)"}`}
>
{cat.label}
</button>
))}
</div>
{/* Extension list */}
<div className="flex-1 overflow-y-auto px-2 py-1 flex flex-col gap-1.5">
{filtered.map((ext) => (
<motion.div
key={ext.id}
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="card p-3 gap-0"
>
<div className="flex items-start gap-2">
<div className="w-9 h-9 rounded-lg bg-(--bg-2) flex items-center justify-center text-lg shrink-0">
{ext.icon}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="font-medium text-xs text-(--text) truncate">{ext.name}</span>
{installed[ext.id] && (
<span className="badge badge-accent text-[8px]">Installed</span>
)}
</div>
<p className="text-(--text-muted) text-[10px] mt-0.5 line-clamp-2">{ext.description}</p>
<div className="flex items-center gap-1.5 mt-1.5">
<span className="text-[9px] text-(--text-muted)">{ext.publisher} · v{ext.version}</span>
<span className="badge bg-(--bg-3) text-(--text-muted) text-[8px]">{ext.category}</span>
</div>
</div>
</div>
{/* Actions */}
<div className="flex items-center justify-end gap-1.5 mt-2 pt-2 border-t border-(--border-subtle)">
{installed[ext.id] ? (
<button
onClick={() => toggle(ext.id)}
className={`flex items-center gap-1 text-[10px] px-2 py-1 rounded transition-all ${enabled[ext.id] ? "text-(--accent) bg-[rgba(57,211,83,0.08)]" : "text-(--text-muted) bg-(--bg-2)"}`}
>
{enabled[ext.id] ? <ToggleRight size={12} /> : <ToggleLeft size={12} />}
{enabled[ext.id] ? "Enabled" : "Disabled"}
</button>
) : (
<button
onClick={() => install(ext.id)}
disabled={installing === ext.id}
className="btn btn-primary py-1 px-2 text-[10px] gap-1"
>
{installing === ext.id ? (
<><span className="w-2.5 h-2.5 rounded-full border border-(--text-on-accent) border-t-transparent animate-spin" />Installing…</>
) : (
<><Download size={10} /> Install</>
)}
</button>
)}
</div>
</motion.div>
))}
{filtered.length === 0 && (
<div className="text-center py-8 text-(--text-muted) text-xs">
<Package size={24} className="mx-auto mb-2 opacity-30" />
<p>No extensions found</p>
</div>
)}
</div>
</div>
);
}