From 2fd2e2a64b724727e09a8bee96912afd2bce2c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Sat, 20 Dec 2025 14:45:08 +0100 Subject: [PATCH] feat: drag icon for tab dragging --- src/components/ReqViewer.tsx | 2 +- src/components/TabBar.tsx | 182 ++++++++++++++++++++++------------- 2 files changed, 114 insertions(+), 70 deletions(-) diff --git a/src/components/ReqViewer.tsx b/src/components/ReqViewer.tsx index 1c9d388..8df7b67 100644 --- a/src/components/ReqViewer.tsx +++ b/src/components/ReqViewer.tsx @@ -1165,7 +1165,7 @@ export default function ReqViewer({ className="shadow-lg bg-accent text-accent-foreground opacity-100 hover:bg-accent" size="sm" > - + {newEventCount} new event{newEventCount !== 1 ? "s" : ""} diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index 48172b4..2b204d5 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -1,10 +1,107 @@ -import { Plus } from "lucide-react"; +import { Plus, GripVertical } from "lucide-react"; import { Button } from "./ui/button"; import { useGrimoire } from "@/core/state"; import { cn } from "@/lib/utils"; import { LayoutControls } from "./LayoutControls"; import { useEffect, useState } from "react"; -import { Reorder } from "framer-motion"; +import { Reorder, useDragControls } from "framer-motion"; +import { Workspace } from "@/types/app"; + +interface TabItemProps { + ws: Workspace; + isActive: boolean; + isEditing: boolean; + editingLabel: string; + setEditingLabel: (label: string) => void; + handleKeyDown: (e: React.KeyboardEvent) => void; + saveLabel: () => void; + setActiveWorkspace: (id: string) => void; + startEditing: (id: string, label?: string) => void; +} + +function TabItem({ + ws, + isActive, + isEditing, + editingLabel, + setEditingLabel, + handleKeyDown, + saveLabel, + setActiveWorkspace, + startEditing, +}: TabItemProps) { + const dragControls = useDragControls(); + + return ( + + {isEditing ? ( + // Render input field when editing +
e.stopPropagation()} + > + {ws.number} + 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 text-inherit" + /> +
+ ) : ( + // Render button when not editing +
+
dragControls.start(e)} + className="cursor-grab active:cursor-grabbing p-1 hover:bg-black/10 rounded flex items-center justify-center" + > + +
+ +
+ )} +
+ ); +} export function TabBar() { const { @@ -106,73 +203,20 @@ export function TabBar() { onReorder={(newOrder) => reorderWorkspaces(newOrder.map((w) => w.id))} className="flex items-center gap-1 flex-nowrap list-none p-0 m-0" > - {sortedWorkspaces.map((ws) => { - const isEditing = editingId === ws.id; - const isActive = ws.id === activeWorkspaceId; - - return ( - isEditing && e.preventDefault()} - > - {isEditing ? ( - // Render input field when editing -
e.stopPropagation()} - > - {ws.number} - 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 text-inherit" - /> -
- ) : ( - // Render button when not editing - - )} -
- ); - })} + {sortedWorkspaces.map((ws) => ( + + ))}