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:
@@ -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")}
|
||||
|
||||
Reference in New Issue
Block a user