feat: tab names

This commit is contained in:
Alejandro Gómez
2025-12-19 12:49:29 +01:00
parent 812b719ea0
commit 8f80742ef1
5 changed files with 137 additions and 18 deletions

View File

@@ -3,7 +3,7 @@ import { Button } from "./ui/button";
import { useGrimoire } from "@/core/state";
import { cn } from "@/lib/utils";
import { LayoutControls } from "./LayoutControls";
import { useEffect } from "react";
import { useEffect, useState } from "react";
export function TabBar() {
const {
@@ -11,13 +11,50 @@ export function TabBar() {
setActiveWorkspace,
createWorkspace,
createWorkspaceWithNumber,
updateWorkspaceLabel,
} = useGrimoire();
const { workspaces, activeWorkspaceId } = state;
// State for inline label editing
const [editingId, setEditingId] = useState<string | null>(null);
const [editingLabel, setEditingLabel] = useState("");
const handleNewTab = () => {
createWorkspace();
};
// Start editing a workspace label
const startEditing = (workspaceId: string, currentLabel?: string) => {
setEditingId(workspaceId);
setEditingLabel(currentLabel || "");
};
// Save label changes
const saveLabel = () => {
if (editingId) {
updateWorkspaceLabel(editingId, editingLabel);
setEditingId(null);
setEditingLabel("");
}
};
// Cancel editing
const cancelEditing = () => {
setEditingId(null);
setEditingLabel("");
};
// Handle keyboard events in input
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
saveLabel();
} else if (e.key === "Escape") {
e.preventDefault();
cancelEditing();
}
};
// Sort workspaces by number (for both rendering and keyboard shortcuts)
const sortedWorkspaces = Object.values(workspaces).sort(
(a, b) => a.number - b.number,
@@ -62,22 +99,60 @@ export function TabBar() {
<div className="h-8 border-t border-border bg-background flex items-center px-2 gap-1 overflow-x-auto">
{/* Left side: Workspace tabs + new workspace button */}
<div className="flex items-center gap-1 flex-nowrap">
{sortedWorkspaces.map((ws) => (
<button
key={ws.id}
onClick={() => setActiveWorkspace(ws.id)}
className={cn(
"px-3 py-1 text-xs font-mono rounded transition-colors whitespace-nowrap flex-shrink-0",
ws.id === activeWorkspaceId
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
>
{ws.label && ws.label.trim()
? `${ws.number} ${ws.label}`
: ws.number}
</button>
))}
{sortedWorkspaces.map((ws) => {
const isEditing = editingId === ws.id;
const isActive = ws.id === activeWorkspaceId;
if (isEditing) {
// Render input field when editing
return (
<div
key={ws.id}
className={cn(
"px-3 py-1 text-xs font-mono rounded flex items-center gap-2 flex-shrink-0",
isActive
? "bg-primary text-primary-foreground"
: "bg-muted text-foreground",
)}
onClick={(e) => e.stopPropagation()}
>
<span>{ws.number}</span>
<input
type="text"
value={editingLabel}
onChange={(e) => setEditingLabel(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={saveLabel}
autoFocus
style={{ width: `${Math.max(editingLabel.length, 1)}ch` }}
className="bg-transparent border-0 outline-none focus:outline-none focus:ring-0 p-0 m-0"
/>
</div>
);
}
// Render button when not editing
return (
<button
key={ws.id}
onClick={() => setActiveWorkspace(ws.id)}
onDoubleClick={() => startEditing(ws.id, ws.label)}
className={cn(
"flex items-center gap-2 px-3 py-1 text-xs font-mono rounded transition-colors whitespace-nowrap flex-shrink-0",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
>
<span>{ws.number}</span>
{ws.label && ws.label.trim() && (
<span style={{ width: `${ws.label.trim().length || 0}ch` }}>
{ws.label.trim()}
</span>
)}
</button>
);
})}
<Button
variant="ghost"