Outfit + JetBrains Mono typography, soft dark palette with blue undertones, electric teal primary, purple-branded AI features, noise texture, glow effects, glassmorphism, and refined grid/tree.
101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
import { useState } from "react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { SchemaTree } from "@/components/schema/SchemaTree";
|
|
import { HistoryPanel } from "@/components/history/HistoryPanel";
|
|
import { SavedQueriesPanel } from "@/components/saved-queries/SavedQueriesPanel";
|
|
import { AdminPanel } from "@/components/management/AdminPanel";
|
|
import { Search, RefreshCw, Layers, Clock, Bookmark, Shield } from "lucide-react";
|
|
|
|
type SidebarView = "schema" | "history" | "saved" | "admin";
|
|
|
|
const SCHEMA_QUERY_KEYS = [
|
|
"databases", "schemas", "tables", "views",
|
|
"functions", "sequences", "completionSchema", "column-details",
|
|
];
|
|
|
|
const SIDEBAR_TABS: { id: SidebarView; label: string; icon: React.ReactNode }[] = [
|
|
{ id: "schema", label: "Schema", icon: <Layers className="h-3.5 w-3.5" /> },
|
|
{ id: "history", label: "History", icon: <Clock className="h-3.5 w-3.5" /> },
|
|
{ id: "saved", label: "Saved", icon: <Bookmark className="h-3.5 w-3.5" /> },
|
|
{ id: "admin", label: "Admin", icon: <Shield className="h-3.5 w-3.5" /> },
|
|
];
|
|
|
|
export function Sidebar() {
|
|
const [view, setView] = useState<SidebarView>("schema");
|
|
const [search, setSearch] = useState("");
|
|
const queryClient = useQueryClient();
|
|
|
|
const handleRefreshSchema = () => {
|
|
for (const key of SCHEMA_QUERY_KEYS) {
|
|
queryClient.invalidateQueries({ queryKey: [key] });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex h-full flex-col" style={{ background: "var(--sidebar)" }}>
|
|
{/* Sidebar navigation tabs */}
|
|
<div className="flex border-b border-border/50">
|
|
{SIDEBAR_TABS.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
className={`relative flex flex-1 items-center justify-center gap-1.5 px-2 py-2 text-[11px] font-medium tracking-wide transition-colors ${
|
|
view === tab.id
|
|
? "text-foreground tusk-sidebar-tab-active"
|
|
: "text-muted-foreground hover:text-foreground/70"
|
|
}`}
|
|
onClick={() => setView(tab.id)}
|
|
>
|
|
{tab.icon}
|
|
<span className="hidden min-[220px]:inline">{tab.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{view === "schema" ? (
|
|
<>
|
|
<div className="flex items-center gap-1 p-2">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60" />
|
|
<Input
|
|
placeholder="Search objects..."
|
|
className="h-7 border-border/40 bg-background/50 pl-7 text-xs placeholder:text-muted-foreground/40 focus:border-primary/40 focus:ring-primary/20"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-xs"
|
|
onClick={handleRefreshSchema}
|
|
className="text-muted-foreground hover:text-foreground"
|
|
>
|
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">Refresh schema</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden">
|
|
<SchemaTree />
|
|
</div>
|
|
</>
|
|
) : view === "history" ? (
|
|
<HistoryPanel />
|
|
) : view === "saved" ? (
|
|
<SavedQueriesPanel />
|
|
) : (
|
|
<AdminPanel />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|