import { useEffect, useRef, useState } from 'react' import { Term } from './api' import { Spinner } from './ui' interface Props { terms: Term[]; active: string | null; creating?: boolean onSelect: (id: string) => void; onNew: () => void onRename: (t: Term) => void; onClose: (t: Term) => void onMove: (id: string, before: string | null) => void // put a tab before another, or (null) at the end onFocusTerm: () => void } // Tab strip across the top of the terminal page: one tab per session, "+" at the end. export default function TermTabs({ terms, active, creating, onSelect, onNew, onRename, onClose, onMove, onFocusTerm }: Props) { const [dragging, setDragging] = useState(null) const [over, setOver] = useState(null) const strip = useRef(null) // keep the active tab in sight when the strip overflows useEffect(() => { strip.current?.querySelector('.ttab.active')?.scrollIntoView({ block: 'nearest', inline: 'nearest' }) }, [active, terms.length]) return ( // Buttons must not take focus from the terminal; the tabs themselves must stay draggable, which a // prevented mousedown would stop — so a tab click hands focus back afterwards instead.
{ if ((e.target as HTMLElement).closest('button')) e.preventDefault() }}> {terms.map((t, i) => { const sel = t.id === active return (
{ setDragging(t.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', t.id) }} onDragOver={(e) => { if (dragging) { e.preventDefault(); setOver(t.id) } }} onDragLeave={() => setOver((o) => (o === t.id ? null : o))} onDrop={(e) => { e.preventDefault(); if (dragging && dragging !== t.id) onMove(dragging, t.id); setDragging(null); setOver(null); onFocusTerm() }} onDragEnd={() => { setDragging(null); setOver(null) }} onClick={() => { onSelect(t.id); onFocusTerm() }} onDoubleClick={() => onRename(t)} onAuxClick={(e) => { if (e.button === 1) { e.preventDefault(); onClose(t) } }}> {t.name} {sel && terms.length > 1 && } {sel && }
) })}
) }