/** * SortableTab - A draggable tab item used within SortableContext. * Wraps useSortable from @dnd-kit for tab reordering and cross-pane movement. */ import { useCallback, useState } from 'react'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useStore } from '@renderer/store'; import { Activity, Bell, FileText, LayoutDashboard, Pin, Search, Settings, Users, X, } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import type { Tab } from '@renderer/types/tabs'; interface SortableTabProps { tab: Tab; paneId: string; isActive: boolean; isSelected: boolean; onTabClick: (tabId: string, e: React.MouseEvent) => void; onMouseDown: (tabId: string, e: React.MouseEvent) => void; onContextMenu: (tabId: string, e: React.MouseEvent) => void; onClose: (tabId: string) => void; setRef: (tabId: string, el: HTMLDivElement | null) => void; } const TAB_ICONS = { dashboard: LayoutDashboard, notifications: Bell, settings: Settings, session: FileText, teams: Users, team: Users, report: Activity, } as const; export const SortableTab = ({ tab, paneId, isActive, isSelected, onTabClick, onMouseDown, onContextMenu, onClose, setRef, }: SortableTabProps): React.JSX.Element => { const [isHovered, setIsHovered] = useState(false); const isPinned = useStore( useShallow((s) => tab.type === 'session' && tab.sessionId ? s.pinnedSessionIds.includes(tab.sessionId) : false ) ); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: tab.id, data: { type: 'tab', tabId: tab.id, paneId, }, }); const style = { WebkitAppRegion: 'no-drag', transform: CSS.Transform.toString(transform), transition: isDragging ? 'none' : transition, opacity: isDragging ? 0.3 : 1, backgroundColor: isActive ? 'var(--color-surface-raised)' : isHovered ? 'var(--color-surface-overlay)' : 'transparent', color: isActive || isHovered ? 'var(--color-text)' : 'var(--color-text-muted)', outline: isSelected ? '1px solid var(--color-border-emphasis)' : 'none', outlineOffset: '-1px', }; const Icon = TAB_ICONS[tab.type]; const handleRef = useCallback( (el: HTMLDivElement | null) => { setNodeRef(el); setRef(tab.id, el); }, [setNodeRef, setRef, tab.id] ); return (
onTabClick(tab.id, e)} onMouseDown={(e) => onMouseDown(tab.id, e)} onContextMenu={(e) => onContextMenu(tab.id, e)} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onTabClick(tab.id, e as unknown as React.MouseEvent); } }} > {tab.fromSearch && ( )} {isPinned && ( )} {tab.label}
); }; /** * DragOverlayTab - Semi-transparent ghost of a tab shown during drag. */ export const DragOverlayTab = ({ tab }: { tab: Tab }): React.JSX.Element => { const Icon = TAB_ICONS[tab.type]; return (
{tab.label}
); };