Browse Source

ui: redesign ProjectSetupModal with pill tabs, segmented controls, and polish

Replace underline tab bar with pill-style segmented tabs. Replace radio
button lists in Canvas tab with compact segmented button groups. Add
backdrop blur, click-outside-to-close, rounded-lg inputs, and improved
text contrast (neutral-400 instead of neutral-500 for descriptions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
dc25e585c5
  1. 267
      src/components/ProjectSetupModal.tsx

267
src/components/ProjectSetupModal.tsx

@ -381,39 +381,47 @@ export function ProjectSetupModal({
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50"> <div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm"
onClick={onClose}
onWheelCapture={(e) => e.stopPropagation()}
>
<div <div
className="bg-neutral-800 rounded-lg w-[480px] border border-neutral-700 shadow-xl flex flex-col max-h-[80vh]" className="bg-neutral-800 rounded-xl w-[520px] border border-neutral-700 shadow-2xl overflow-clip flex flex-col max-h-[80vh]"
onClick={(e) => e.stopPropagation()}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >
<div className="px-6 pt-6 pb-0 shrink-0"> <div className="px-8 pt-8 pb-0 shrink-0">
<h2 className="text-lg font-semibold text-neutral-100 mb-4"> <div className="flex items-center gap-2 mb-5">
{mode === "new" ? "New Project" : "Project Settings"} <img src="/banana_icon.png" alt="" className="w-6 h-6" />
</h2> <h2 className="text-xl font-medium text-neutral-100">
{mode === "new" ? "New Project" : "Project Settings"}
</h2>
</div>
{/* Tab Bar */} {/* Tab Bar */}
<div className="flex gap-4 border-b border-neutral-700"> <div className="flex gap-1.5 p-1 bg-neutral-900/50 rounded-lg">
<button <button
onClick={() => setActiveTab("project")} onClick={() => setActiveTab("project")}
className={`pb-2 text-sm ${activeTab === "project" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} className={`px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${activeTab === "project" ? "bg-neutral-700 text-neutral-100 font-medium" : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/50"}`}
> >
Project Project
</button> </button>
<button <button
onClick={() => setActiveTab("providers")} onClick={() => setActiveTab("providers")}
className={`pb-2 text-sm ${activeTab === "providers" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} className={`px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${activeTab === "providers" ? "bg-neutral-700 text-neutral-100 font-medium" : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/50"}`}
> >
Providers Providers
</button> </button>
<button <button
onClick={() => setActiveTab("nodeDefaults")} onClick={() => setActiveTab("nodeDefaults")}
className={`pb-2 text-sm ${activeTab === "nodeDefaults" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} className={`px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${activeTab === "nodeDefaults" ? "bg-neutral-700 text-neutral-100 font-medium" : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/50"}`}
> >
Node Defaults Node Defaults
</button> </button>
<button <button
onClick={() => setActiveTab("canvas")} onClick={() => setActiveTab("canvas")}
className={`pb-2 text-sm ${activeTab === "canvas" ? "text-neutral-100 border-b-2 border-white" : "text-neutral-400"}`} className={`px-3 py-1.5 text-sm rounded-md transition-all duration-150 ${activeTab === "canvas" ? "bg-neutral-700 text-neutral-100 font-medium" : "text-neutral-400 hover:text-neutral-300 hover:bg-neutral-800/50"}`}
> >
Canvas Canvas
</button> </button>
@ -421,7 +429,7 @@ export function ProjectSetupModal({
</div> </div>
{/* Scrollable tab content area */} {/* Scrollable tab content area */}
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-4"> <div className="flex-1 min-h-0 overflow-y-auto px-8 py-5">
{/* Project Tab Content */} {/* Project Tab Content */}
{activeTab === "project" && ( {activeTab === "project" && (
@ -436,7 +444,7 @@ export function ProjectSetupModal({
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="my-project" placeholder="my-project"
autoFocus autoFocus
className="w-full px-3 py-2 bg-neutral-900 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" className="w-full px-3 py-2 bg-neutral-900 border border-neutral-600 rounded-lg text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
/> />
</div> </div>
@ -450,18 +458,18 @@ export function ProjectSetupModal({
value={directoryPath} value={directoryPath}
onChange={(e) => setDirectoryPath(e.target.value)} onChange={(e) => setDirectoryPath(e.target.value)}
placeholder="/Users/username/projects/my-project" placeholder="/Users/username/projects/my-project"
className="flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" className="flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded-lg text-neutral-100 text-sm focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
onClick={handleBrowse} onClick={handleBrowse}
disabled={isBrowsing} disabled={isBrowsing}
className="px-3 py-2 bg-neutral-700 hover:bg-neutral-600 disabled:bg-neutral-700 disabled:opacity-50 text-neutral-200 text-sm rounded transition-colors" className="px-3 py-2 bg-neutral-700 hover:bg-neutral-600 disabled:bg-neutral-700 disabled:opacity-50 text-neutral-200 text-sm rounded-lg transition-colors"
> >
{isBrowsing ? "..." : "Browse"} {isBrowsing ? "..." : "Browse"}
</button> </button>
</div> </div>
<p className="text-xs text-neutral-500 mt-1"> <p className="text-xs text-neutral-400 mt-1">
Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created. Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created.
</p> </p>
</div> </div>
@ -470,7 +478,7 @@ export function ProjectSetupModal({
<label className="flex items-center justify-between gap-3 cursor-pointer"> <label className="flex items-center justify-between gap-3 cursor-pointer">
<div> <div>
<span className="text-sm text-neutral-200">Embed images as base64</span> <span className="text-sm text-neutral-200">Embed images as base64</span>
<p className="text-xs text-neutral-500"> <p className="text-xs text-neutral-400">
Embeds all images in workflow, larger workflow files. Can hit memory limits on very large workflows. Embeds all images in workflow, larger workflow files. Can hit memory limits on very large workflows.
</p> </p>
</div> </div>
@ -490,7 +498,7 @@ export function ProjectSetupModal({
<label className="flex items-center justify-between gap-3 cursor-pointer"> <label className="flex items-center justify-between gap-3 cursor-pointer">
<div> <div>
<span className="text-sm text-neutral-200">Show model settings on nodes</span> <span className="text-sm text-neutral-200">Show model settings on nodes</span>
<p className="text-xs text-neutral-500"> <p className="text-xs text-neutral-400">
Show model parameters inside generation nodes instead of the side panel Show model parameters inside generation nodes instead of the side panel
</p> </p>
</div> </div>
@ -535,7 +543,7 @@ export function ProjectSetupModal({
value={localProviders.providers.gemini?.apiKey || ""} value={localProviders.providers.gemini?.apiKey || ""}
onChange={(e) => updateLocalProvider("gemini", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("gemini", { apiKey: e.target.value || null })}
placeholder="AIza..." placeholder="AIza..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -583,7 +591,7 @@ export function ProjectSetupModal({
value={localProviders.providers.openai?.apiKey || ""} value={localProviders.providers.openai?.apiKey || ""}
onChange={(e) => updateLocalProvider("openai", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("openai", { apiKey: e.target.value || null })}
placeholder="sk-..." placeholder="sk-..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -631,7 +639,7 @@ export function ProjectSetupModal({
value={localProviders.providers.anthropic?.apiKey || ""} value={localProviders.providers.anthropic?.apiKey || ""}
onChange={(e) => updateLocalProvider("anthropic", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("anthropic", { apiKey: e.target.value || null })}
placeholder="sk-ant-..." placeholder="sk-ant-..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -679,7 +687,7 @@ export function ProjectSetupModal({
value={localProviders.providers.replicate?.apiKey || ""} value={localProviders.providers.replicate?.apiKey || ""}
onChange={(e) => updateLocalProvider("replicate", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("replicate", { apiKey: e.target.value || null })}
placeholder="r8_..." placeholder="r8_..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -727,7 +735,7 @@ export function ProjectSetupModal({
value={localProviders.providers.fal?.apiKey || ""} value={localProviders.providers.fal?.apiKey || ""}
onChange={(e) => updateLocalProvider("fal", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("fal", { apiKey: e.target.value || null })}
placeholder="..." placeholder="..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -756,16 +764,10 @@ export function ProjectSetupModal({
{/* Kie.ai Provider */} {/* Kie.ai Provider */}
<div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700"> <div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <span className="text-sm font-medium text-neutral-100">Kie.ai</span>
<svg className="w-4 h-4 text-orange-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
</svg>
<span className="text-sm font-medium text-neutral-100">Kie.ai</span>
<span className="text-xs text-neutral-500">(Sora, Veo, Kling, etc.)</span>
</div>
{envStatus?.kie && !overrideActive.kie ? ( {envStatus?.kie && !overrideActive.kie ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs text-green-400"> From .env</span> <span className="text-xs text-green-400">Configured via .env</span>
<button <button
type="button" type="button"
onClick={() => setOverrideActive((prev) => ({ ...prev, kie: true }))} onClick={() => setOverrideActive((prev) => ({ ...prev, kie: true }))}
@ -781,7 +783,7 @@ export function ProjectSetupModal({
value={localProviders.providers.kie?.apiKey || ""} value={localProviders.providers.kie?.apiKey || ""}
onChange={(e) => updateLocalProvider("kie", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("kie", { apiKey: e.target.value || null })}
placeholder="..." placeholder="..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -829,7 +831,7 @@ export function ProjectSetupModal({
value={localProviders.providers.wavespeed?.apiKey || ""} value={localProviders.providers.wavespeed?.apiKey || ""}
onChange={(e) => updateLocalProvider("wavespeed", { apiKey: e.target.value || null })} onChange={(e) => updateLocalProvider("wavespeed", { apiKey: e.target.value || null })}
placeholder="..." placeholder="..."
className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded text-neutral-100 text-xs focus:outline-none focus:border-neutral-500" className="w-48 px-2 py-1 bg-neutral-800 border border-neutral-600 rounded-lg text-neutral-100 text-xs focus:outline-none focus:border-neutral-500"
/> />
<button <button
type="button" type="button"
@ -855,7 +857,7 @@ export function ProjectSetupModal({
</div> </div>
</div> </div>
<p className="text-xs text-neutral-500 mt-2"> <p className="text-xs text-neutral-400 mt-2">
Add API keys via <code className="px-1 py-0.5 bg-neutral-800 rounded">.env.local</code> for better security. Keys added here override .env and are stored in your browser. Add API keys via <code className="px-1 py-0.5 bg-neutral-800 rounded">.env.local</code> for better security. Keys added here override .env and are stored in your browser.
</p> </p>
</div> </div>
@ -897,7 +899,7 @@ export function ProjectSetupModal({
</> </>
) : ( ) : (
<> <>
<span className="text-xs text-neutral-500">System default (Gemini nano-banana-pro)</span> <span className="text-xs text-neutral-400">System default (Gemini nano-banana-pro)</span>
<button <button
type="button" type="button"
onClick={() => setShowImageModelDialog(true)} onClick={() => setShowImageModelDialog(true)}
@ -944,7 +946,7 @@ export function ProjectSetupModal({
</> </>
) : ( ) : (
<> <>
<span className="text-xs text-neutral-500">None set (select on first use)</span> <span className="text-xs text-neutral-400">None set (select on first use)</span>
<button <button
type="button" type="button"
onClick={() => setShowVideoModelDialog(true)} onClick={() => setShowVideoModelDialog(true)}
@ -978,7 +980,7 @@ export function ProjectSetupModal({
</div> </div>
{!localNodeDefaults.llm ? ( {!localNodeDefaults.llm ? (
<p className="text-xs text-neutral-500">Using system defaults (Google Gemini 3 Flash)</p> <p className="text-xs text-neutral-400">Using system defaults (Google Gemini 3 Flash)</p>
) : null} ) : null}
{/* Provider dropdown */} {/* Provider dropdown */}
@ -1092,14 +1094,14 @@ export function ProjectSetupModal({
className="flex-1 h-1 bg-neutral-700 rounded-lg appearance-none cursor-pointer accent-neutral-400" className="flex-1 h-1 bg-neutral-700 rounded-lg appearance-none cursor-pointer accent-neutral-400"
/> />
</div> </div>
<p className="text-xs text-neutral-500"> <p className="text-xs text-neutral-400">
Maximum number of nodes to execute in parallel during workflow execution. Maximum number of nodes to execute in parallel during workflow execution.
Higher values may improve speed but increase API rate limit risk. Higher values may improve speed but increase API rate limit risk.
</p> </p>
</div> </div>
</div> </div>
<p className="text-xs text-neutral-500 mt-2"> <p className="text-xs text-neutral-400 mt-2">
These defaults are applied when creating nodes via keyboard shortcuts (Shift+G, Shift+L, etc). These defaults are applied when creating nodes via keyboard shortcuts (Shift+G, Shift+L, etc).
</p> </p>
</div> </div>
@ -1107,106 +1109,107 @@ export function ProjectSetupModal({
{/* Canvas Tab Content */} {/* Canvas Tab Content */}
{activeTab === "canvas" && ( {activeTab === "canvas" && (
<div className="space-y-5"> <div className="space-y-3">
<p className="text-xs text-neutral-400">Configure how you navigate and interact with the canvas.</p>
{/* Pan Mode */} {/* Pan Mode */}
<div> <div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700">
<h3 className="text-sm font-medium text-neutral-200 mb-2">Pan Mode</h3> <div className="flex flex-col gap-3">
<div className="space-y-1.5"> <div className="flex items-center justify-between">
{([ <span className="text-sm font-medium text-neutral-100">Pan Mode</span>
{ value: "space" as PanMode, label: "Space + Drag", description: "Hold Space and drag to pan (default)" }, <p className="text-xs text-neutral-400">
{ value: "middleMouse" as PanMode, label: "Middle Mouse", description: "Click and drag with middle mouse button" }, {localCanvasSettings.panMode === "space" && "Hold Space and drag to pan"}
{ value: "always" as PanMode, label: "Always On", description: "Pan without holding any keys" }, {localCanvasSettings.panMode === "middleMouse" && "Click and drag with middle mouse button"}
] as const).map((option) => ( {localCanvasSettings.panMode === "always" && "Pan without holding any keys"}
<label </p>
key={option.value} </div>
className={`flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${ <div className="flex gap-1 p-0.5 bg-neutral-800 rounded-md">
localCanvasSettings.panMode === option.value {([
? "border-blue-500 bg-blue-500/10" { value: "space" as PanMode, label: "Space + Drag" },
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50" { value: "middleMouse" as PanMode, label: "Middle Mouse" },
}`} { value: "always" as PanMode, label: "Always On" },
> ] as const).map((option) => (
<input <button
type="radio" key={option.value}
name="panMode" type="button"
value={option.value} onClick={() => setLocalCanvasSettings({ ...localCanvasSettings, panMode: option.value })}
checked={localCanvasSettings.panMode === option.value} className={`flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange={(e) => setLocalCanvasSettings({ ...localCanvasSettings, panMode: e.target.value as PanMode })} localCanvasSettings.panMode === option.value
className="mt-0.5 mr-3" ? "bg-neutral-700 text-neutral-100 font-medium"
/> : "text-neutral-400 hover:text-neutral-300"
<div className="flex-1"> }`}
<div className="text-sm font-medium text-neutral-200">{option.label}</div> >
<div className="text-xs text-neutral-400 mt-0.5">{option.description}</div> {option.label}
</div> </button>
</label> ))}
))} </div>
</div> </div>
</div> </div>
{/* Zoom Mode */} {/* Zoom Mode */}
<div> <div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700">
<h3 className="text-sm font-medium text-neutral-200 mb-2">Zoom Mode</h3> <div className="flex flex-col gap-3">
<div className="space-y-1.5"> <div className="flex items-center justify-between">
{([ <span className="text-sm font-medium text-neutral-100">Zoom Mode</span>
{ value: "altScroll" as ZoomMode, label: "Alt + Scroll", description: "Hold Alt and scroll to zoom (default)" }, <p className="text-xs text-neutral-400">
{ value: "ctrlScroll" as ZoomMode, label: "Ctrl + Scroll", description: "Hold Ctrl/Cmd and scroll to zoom" }, {localCanvasSettings.zoomMode === "altScroll" && "Hold Alt and scroll to zoom"}
{ value: "scroll" as ZoomMode, label: "Scroll", description: "Scroll to zoom without holding any keys" }, {localCanvasSettings.zoomMode === "ctrlScroll" && "Hold Ctrl/Cmd and scroll to zoom"}
] as const).map((option) => ( {localCanvasSettings.zoomMode === "scroll" && "Scroll to zoom without modifier keys"}
<label </p>
key={option.value} </div>
className={`flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${ <div className="flex gap-1 p-0.5 bg-neutral-800 rounded-md">
localCanvasSettings.zoomMode === option.value {([
? "border-blue-500 bg-blue-500/10" { value: "altScroll" as ZoomMode, label: "Alt + Scroll" },
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50" { value: "ctrlScroll" as ZoomMode, label: "Ctrl + Scroll" },
}`} { value: "scroll" as ZoomMode, label: "Scroll" },
> ] as const).map((option) => (
<input <button
type="radio" key={option.value}
name="zoomMode" type="button"
value={option.value} onClick={() => setLocalCanvasSettings({ ...localCanvasSettings, zoomMode: option.value })}
checked={localCanvasSettings.zoomMode === option.value} className={`flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange={(e) => setLocalCanvasSettings({ ...localCanvasSettings, zoomMode: e.target.value as ZoomMode })} localCanvasSettings.zoomMode === option.value
className="mt-0.5 mr-3" ? "bg-neutral-700 text-neutral-100 font-medium"
/> : "text-neutral-400 hover:text-neutral-300"
<div className="flex-1"> }`}
<div className="text-sm font-medium text-neutral-200">{option.label}</div> >
<div className="text-xs text-neutral-400 mt-0.5">{option.description}</div> {option.label}
</div> </button>
</label> ))}
))} </div>
</div> </div>
</div> </div>
{/* Selection Mode */} {/* Selection Mode */}
<div> <div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700">
<h3 className="text-sm font-medium text-neutral-200 mb-2">Selection Mode</h3> <div className="flex flex-col gap-3">
<div className="space-y-1.5"> <div className="flex items-center justify-between">
{([ <span className="text-sm font-medium text-neutral-100">Selection Mode</span>
{ value: "click" as SelectionMode, label: "Click", description: "Click to select nodes (default)" }, <p className="text-xs text-neutral-400">
{ value: "altDrag" as SelectionMode, label: "Alt + Drag", description: "Hold Alt and drag to select multiple nodes" }, {localCanvasSettings.selectionMode === "click" && "Click to select nodes"}
{ value: "shiftDrag" as SelectionMode, label: "Shift + Drag", description: "Hold Shift and drag to select multiple nodes" }, {localCanvasSettings.selectionMode === "altDrag" && "Hold Alt and drag to select"}
] as const).map((option) => ( {localCanvasSettings.selectionMode === "shiftDrag" && "Hold Shift and drag to select"}
<label </p>
key={option.value} </div>
className={`flex items-start p-2.5 rounded-lg border cursor-pointer transition-colors ${ <div className="flex gap-1 p-0.5 bg-neutral-800 rounded-md">
localCanvasSettings.selectionMode === option.value {([
? "border-blue-500 bg-blue-500/10" { value: "click" as SelectionMode, label: "Click" },
: "border-neutral-700 hover:border-neutral-600 bg-neutral-900/50" { value: "altDrag" as SelectionMode, label: "Alt + Drag" },
}`} { value: "shiftDrag" as SelectionMode, label: "Shift + Drag" },
> ] as const).map((option) => (
<input <button
type="radio" key={option.value}
name="selectionMode" type="button"
value={option.value} onClick={() => setLocalCanvasSettings({ ...localCanvasSettings, selectionMode: option.value })}
checked={localCanvasSettings.selectionMode === option.value} className={`flex-1 px-2 py-1.5 text-xs rounded transition-all duration-150 ${
onChange={(e) => setLocalCanvasSettings({ ...localCanvasSettings, selectionMode: e.target.value as SelectionMode })} localCanvasSettings.selectionMode === option.value
className="mt-0.5 mr-3" ? "bg-neutral-700 text-neutral-100 font-medium"
/> : "text-neutral-400 hover:text-neutral-300"
<div className="flex-1"> }`}
<div className="text-sm font-medium text-neutral-200">{option.label}</div> >
<div className="text-xs text-neutral-400 mt-0.5">{option.description}</div> {option.label}
</div> </button>
</label> ))}
))} </div>
</div> </div>
</div> </div>
</div> </div>
@ -1215,7 +1218,7 @@ export function ProjectSetupModal({
</div> </div>
{/* Fixed footer */} {/* Fixed footer */}
<div className="flex justify-end gap-2 px-6 py-4 border-t border-neutral-700 shrink-0"> <div className="flex justify-end gap-2 px-8 py-5 border-t border-neutral-700/50 shrink-0">
<button <button
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-sm text-neutral-400 hover:text-neutral-100 transition-colors" className="px-4 py-2 text-sm text-neutral-400 hover:text-neutral-100 transition-colors"
@ -1225,7 +1228,7 @@ export function ProjectSetupModal({
<button <button
onClick={handleSave} onClick={handleSave}
disabled={activeTab === "project" && (isValidating || isBrowsing)} disabled={activeTab === "project" && (isValidating || isBrowsing)}
className="px-4 py-2 text-sm bg-white text-neutral-900 rounded hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="px-4 py-2 text-sm bg-white text-neutral-900 rounded-lg hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
{activeTab === "project" {activeTab === "project"
? (isValidating ? "Validating..." : mode === "new" ? "Create" : "Save") ? (isValidating ? "Validating..." : mode === "new" ? "Create" : "Save")

Loading…
Cancel
Save