feat: redesign UI with Twilight design system

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.
This commit is contained in:
2026-04-06 10:27:20 +03:00
parent 64e27f79a4
commit 4e5714b291
14 changed files with 674 additions and 278 deletions

View File

@@ -255,11 +255,12 @@ export function WorkspacePanel({
<ResizablePanelGroup orientation="vertical">
<ResizablePanel id="editor" defaultSize="40%" minSize="15%">
<div className="flex h-full flex-col">
<div className="flex items-center gap-2 border-b px-2 py-1">
{/* Editor action bar */}
<div className="flex items-center gap-1 border-b border-border/40 px-2 py-1">
<Button
size="sm"
size="xs"
variant="ghost"
className="h-6 gap-1 text-xs"
className="gap-1 text-[11px] text-primary hover:bg-primary/10 hover:text-primary"
onClick={handleExecute}
disabled={queryMutation.isPending || !sqlValue.trim()}
>
@@ -271,9 +272,9 @@ export function WorkspacePanel({
Run
</Button>
<Button
size="sm"
size="xs"
variant="ghost"
className="h-6 gap-1 text-xs"
className="gap-1 text-[11px]"
onClick={handleExplain}
disabled={queryMutation.isPending || !sqlValue.trim()}
>
@@ -285,9 +286,9 @@ export function WorkspacePanel({
Explain
</Button>
<Button
size="sm"
size="xs"
variant="ghost"
className="h-6 gap-1 text-xs"
className="gap-1 text-[11px]"
onClick={handleFormat}
disabled={!sqlValue.trim()}
title="Format SQL (Shift+Alt+F)"
@@ -296,9 +297,9 @@ export function WorkspacePanel({
Format
</Button>
<Button
size="sm"
size="xs"
variant="ghost"
className="h-6 gap-1 text-xs"
className="gap-1 text-[11px]"
onClick={() => setSaveDialogOpen(true)}
disabled={!sqlValue.trim()}
title="Save query"
@@ -306,20 +307,24 @@ export function WorkspacePanel({
<Bookmark className="h-3 w-3" />
Save
</Button>
<div className="mx-1 h-3.5 w-px bg-border/40" />
{/* AI actions group — purple-branded */}
<Button
size="sm"
size="xs"
variant={aiBarOpen ? "secondary" : "ghost"}
className="h-6 gap-1 text-xs"
className={`gap-1 text-[11px] ${aiBarOpen ? "text-tusk-purple" : ""}`}
onClick={() => setAiBarOpen(!aiBarOpen)}
title="AI SQL Generator"
>
<Sparkles className="h-3 w-3" />
<Sparkles className={`h-3 w-3 ${aiBarOpen ? "tusk-ai-icon" : ""}`} />
AI
</Button>
<Button
size="sm"
size="xs"
variant="ghost"
className="h-6 gap-1 text-xs"
className="gap-1 text-[11px]"
onClick={handleAiExplain}
disabled={isAiLoading || !sqlValue.trim()}
title="Explain query with AI"
@@ -331,35 +336,42 @@ export function WorkspacePanel({
)}
AI Explain
</Button>
{result && result.columns.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-6 gap-1 text-xs"
>
<Download className="h-3 w-3" />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => handleExport("csv")}>
Export CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleExport("json")}>
Export JSON
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<>
<div className="mx-1 h-3.5 w-px bg-border/40" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="xs"
variant="ghost"
className="gap-1 text-[11px]"
>
<Download className="h-3 w-3" />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => handleExport("csv")}>
Export CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleExport("json")}>
Export JSON
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
<span className="text-[11px] text-muted-foreground">
Ctrl+Enter to execute
<div className="flex-1" />
<span className="text-[10px] text-muted-foreground/50 font-mono">
{"\u2318"}Enter
</span>
{isReadOnly && (
<span className="flex items-center gap-1 rounded bg-yellow-500/10 px-1.5 py-0.5 text-[10px] font-semibold text-yellow-600 dark:text-yellow-500">
<Lock className="h-3 w-3" />
Read-Only
<span className="ml-2 flex items-center gap-1 rounded-sm bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-semibold tracking-wide text-amber-500">
<Lock className="h-2.5 w-2.5" />
READ
</span>
)}
</div>
@@ -389,35 +401,41 @@ export function WorkspacePanel({
<ResizablePanel id="results" defaultSize="60%" minSize="15%">
<div className="flex h-full flex-col overflow-hidden">
{(explainData || result || error || aiExplanation) && (
<div className="flex shrink-0 items-center border-b text-xs">
<div className="flex shrink-0 items-center border-b border-border/40 text-xs">
<button
className={`px-3 py-1 font-medium ${
className={`relative px-3 py-1.5 font-medium transition-colors ${
resultView === "results"
? "bg-background text-foreground"
: "text-muted-foreground hover:text-foreground"
? "text-foreground"
: "text-muted-foreground hover:text-foreground/70"
}`}
onClick={() => setResultView("results")}
>
Results
{resultView === "results" && (
<span className="absolute bottom-0 left-1/4 right-1/4 h-0.5 rounded-t bg-primary" />
)}
</button>
{explainData && (
<button
className={`px-3 py-1 font-medium ${
className={`relative px-3 py-1.5 font-medium transition-colors ${
resultView === "explain"
? "bg-background text-foreground"
: "text-muted-foreground hover:text-foreground"
? "text-foreground"
: "text-muted-foreground hover:text-foreground/70"
}`}
onClick={() => setResultView("explain")}
>
Explain
{resultView === "explain" && (
<span className="absolute bottom-0 left-1/4 right-1/4 h-0.5 rounded-t bg-primary" />
)}
</button>
)}
{resultView === "results" && result && result.columns.length > 0 && (
<div className="ml-auto mr-2 flex items-center rounded-md border">
<div className="ml-auto mr-2 flex items-center overflow-hidden rounded border border-border/40">
<button
className={`flex items-center gap-1 px-2 py-0.5 font-medium ${
className={`flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium transition-colors ${
resultViewMode === "table"
? "bg-muted text-foreground"
? "bg-accent text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
onClick={() => setResultViewMode("table")}
@@ -427,9 +445,9 @@ export function WorkspacePanel({
Table
</button>
<button
className={`flex items-center gap-1 px-2 py-0.5 font-medium ${
className={`flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium transition-colors ${
resultViewMode === "json"
? "bg-muted text-foreground"
? "bg-accent text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
onClick={() => setResultViewMode("json")}