Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function AnimatedBackground({

return (
<motion.div
className="absolute h-9 rounded-lg bg-neutral-800/60"
className="absolute h-9 rounded bg-neutral-800/60"
style={{
width: buttonWidth,
x: translateX,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Button } from "@superset/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import type { MotionValue } from "framer-motion";
import { AnimatedBackground } from "../AnimatedBackground";
import { modeIcons } from "../../constants";
import { modeIcons, modeLabels } from "../../constants";
import type { SidebarMode } from "../../types";
import { AnimatedBackground } from "../AnimatedBackground";

interface ModeNavigationProps {
modes: SidebarMode[];
Expand All @@ -20,30 +21,37 @@ export function ModeNavigation({
return (
<div className="flex items-center justify-center gap-1 px-2 py-2 border-t border-neutral-800/50 bg-neutral-900/50 backdrop-blur-sm">
<div className="relative flex items-center gap-1">
<AnimatedBackground progress={scrollProgress} modeCount={modes.length} />
<AnimatedBackground
progress={scrollProgress}
modeCount={modes.length}
/>

{modes.map((mode) => {
const Icon = modeIcons[mode];
const isActive = mode === currentMode;

return (
<Button
key={mode}
variant="ghost"
size="sm"
onClick={() => onModeSelect(mode)}
className={`relative z-10 h-9 w-9 rounded-lg transition-colors duration-200 ${
isActive
? "text-neutral-100"
: "text-neutral-400 hover:text-neutral-300"
}`}
>
<Icon className="w-4 h-4" />
</Button>
<Tooltip key={mode}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={() => onModeSelect(mode)}
className={`relative z-10 h-9 w-9 rounded transition-colors duration-200 ${isActive
? "text-neutral-100"
: "text-neutral-400 hover:text-neutral-300"
}`}
>
<Icon className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="top">
<p className="text-xs">{modeLabels[mode]}</p>
</TooltipContent>
</Tooltip>
);
})}
</div>
</div>
);
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Button } from "@superset/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { Monitor, Plus } from "lucide-react";
import { ChevronDown, Monitor, Plus, SquareTerminal } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import type { Workspace, Worktree } from "shared/types";
import { WorkspacePortIndicator } from "../WorkspacePortIndicator";
import { WorktreeItem } from "./components/WorktreeItem";
Expand All @@ -20,8 +19,8 @@ interface WorktreeListProps {

export function WorktreeList({
currentWorkspace,
expandedWorktrees,
onToggleWorktree,
expandedWorktrees: _expandedWorktrees,
onToggleWorktree: _onToggleWorktree,
onTabSelect,
onReload,
onUpdateWorktree,
Expand All @@ -30,6 +29,44 @@ export function WorktreeList({
selectedWorktreeId,
showWorkspaceHeader = false,
}: WorktreeListProps) {
// Hooks must be called before any early returns
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [defaultTabType, setDefaultTabType] = useState<"terminal" | "preview">(
() => {
// Load from localStorage or default to "terminal"
const saved = localStorage.getItem("newTabDefaultType");
return (saved === "preview" ? "preview" : "terminal") as
| "terminal"
| "preview";
},
);
const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const chevronRef = useRef<HTMLButtonElement>(null);

// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
buttonRef.current &&
chevronRef.current &&
!dropdownRef.current.contains(event.target as Node) &&
!buttonRef.current.contains(event.target as Node) &&
!chevronRef.current.contains(event.target as Node)
) {
setIsDropdownOpen(false);
}
};

if (isDropdownOpen) {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}
}, [isDropdownOpen]);

if (!currentWorkspace) {
return (
<div className="text-sm text-gray-500 px-3 py-2">No workspace open</div>
Expand All @@ -48,11 +85,14 @@ export function WorktreeList({
const hasPortForwarding =
currentWorkspace.ports && currentWorkspace.ports.length > 0;

// Get main branch from workspace config, fallback to 'main'
const mainBranch = currentWorkspace.branch || "main";

const handleAddTerminal = async () => {
const handleAddTerminal = async (updateDefault = false) => {
if (!currentWorkspace || !selectedWorktreeId) return;
setIsDropdownOpen(false);

if (updateDefault) {
setDefaultTabType("terminal");
localStorage.setItem("newTabDefaultType", "terminal");
}

try {
const result = await window.ipcRenderer.invoke("tab-create", {
Expand All @@ -74,8 +114,14 @@ export function WorktreeList({
}
};

const handleAddPreview = async () => {
const handleAddPreview = async (updateDefault = false) => {
if (!currentWorkspace || !selectedWorktreeId) return;
setIsDropdownOpen(false);

if (updateDefault) {
setDefaultTabType("preview");
localStorage.setItem("newTabDefaultType", "preview");
}

try {
const worktree = currentWorkspace.worktrees.find(
Expand Down Expand Up @@ -104,6 +150,19 @@ export function WorktreeList({
}
};

const handleCreateDefault = () => {
if (defaultTabType === "terminal") {
handleAddTerminal();
} else {
handleAddPreview();
}
};

const handleChevronClick = (e: React.MouseEvent) => {
e.stopPropagation();
setIsDropdownOpen(!isDropdownOpen);
};

return (
<>
{/* Workspace Header - more minimal */}
Expand All @@ -113,43 +172,6 @@ export function WorktreeList({
</div>
)}

{/* Action Buttons - more subtle, inline */}
{selectedWorktreeId && (
<div className="px-3 pb-1.5 flex items-center justify-center gap-1.5">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
onClick={handleAddTerminal}
className="h-6 w-6 hover:bg-neutral-800/60 text-neutral-400 hover:text-neutral-200"
>
<Plus size={14} />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<p className="text-xs">New Terminal</p>
</TooltipContent>
</Tooltip>

<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
onClick={handleAddPreview}
className="h-6 w-6 hover:bg-neutral-800/60 text-neutral-400 hover:text-neutral-200"
>
<Monitor size={14} />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<p className="text-xs">New Preview</p>
</TooltipContent>
</Tooltip>
</div>
)}

{currentWorkspace.worktrees.map((worktree) => (
<WorktreeItem
key={worktree.id}
Expand All @@ -166,6 +188,72 @@ export function WorktreeList({
onCloneWorktree={() => onCloneWorktree(worktree.id, worktree.branch)}
/>
))}

{/* Arc-style New Tab Button - styled like a tab at the bottom */}
{selectedWorktreeId && (
<div className="space-y-0.5 mt-2">
<div className="relative">
<button
ref={buttonRef}
type="button"
onClick={handleCreateDefault}
className={`group flex items-center gap-1.5 w-full h-7 px-2.5 text-xs rounded-md transition-all hover:bg-neutral-800/40 text-neutral-400 hover:text-neutral-300`}
>
<Plus size={12} className="shrink-0" />
<span className="truncate flex-1 text-left">
{defaultTabType === "terminal" ? "New Terminal" : "New Preview"}
</span>
<button
ref={chevronRef}
type="button"
onClick={handleChevronClick}
className="shrink-0 opacity-0 group-hover:opacity-60 hover:opacity-100 p-1 rounded transition-opacity"
onMouseDown={(e) => e.stopPropagation()}
>
<ChevronDown
size={14}
className={`transition-transform ${isDropdownOpen ? "rotate-180" : ""}`}
/>
</button>
</button>

{/* Dropdown Menu */}
{isDropdownOpen && (
<div
ref={dropdownRef}
className="absolute bottom-full left-0 right-0 mb-1 bg-neutral-800 border border-neutral-700 rounded-md shadow-lg z-50 overflow-hidden"
>
<button
type="button"
onClick={() =>
handleAddTerminal(defaultTabType !== "terminal")
}
className={`w-full px-3 py-2 text-left text-sm flex items-center gap-2 transition-colors ${defaultTabType === "terminal"
? "bg-neutral-700/50 text-neutral-200"
: "text-neutral-300 hover:bg-neutral-700/50"
}`}
>
<SquareTerminal size={14} className="text-neutral-400" />
<span>New Terminal</span>
</button>
<button
type="button"
onClick={() =>
handleAddPreview(defaultTabType !== "preview")
}
className={`w-full px-3 py-2 text-left text-sm flex items-center gap-2 transition-colors ${defaultTabType === "preview"
? "bg-neutral-700/50 text-neutral-200"
: "text-neutral-300 hover:bg-neutral-700/50"
}`}
>
<Monitor size={14} className="text-neutral-400" />
<span>New Preview</span>
</button>
</div>
)}
</div>
</div>
)}
</>
);
}
Loading