Repository Public
1'use client';2import Link from 'next/link';3import { usePathname, useRouter } from 'next/navigation';4import { ListChecks, Plus, X } from 'lucide-react';5import { useSessions, useTasks } from '@/lib/queries';6import { useTerminals } from '@/stores/terminals';7import { useUi } from '@/stores/ui';8import { closeTab } from '@/lib/session-actions';9import { TASK_ACTIVE } from '@/lib/states';10import { SessionDot } from '@/components/sessions/state-badge';11import { IconButton } from '@/components/ui/button';12import { cn } from '@/lib/cn';13import { ConnectionIndicator } from './connection-indicator';14import { NotificationsBell } from './notifications';1516export function BottomBar() {17 const pathname = usePathname();18 const router = useRouter();19 const { openIds, activeId, activate } = useTerminals();20 const sessions = useSessions().data ?? [];21 const tasks = useTasks().data ?? [];22 const running = tasks.filter((t) => TASK_ACTIVE.includes(t.state)).length;23 const onTerminal = pathname.startsWith('/terminal');24 return (25 <footer className="flex h-11 shrink-0 items-center border-t border-line bg-bg">26 <div role="tablist" aria-label="Open terminals" className="no-scrollbar flex min-w-0 flex-1 items-center gap-1 overflow-x-auto px-2" data-testid="terminal-tabs">27 {openIds.map((id) => {28 const s = sessions.find((x) => x.id === id);29 const active = id === activeId && onTerminal;30 return (31 <div key={id} className={cn('group flex h-8 max-w-56 shrink-0 items-center rounded-full transition-colors', active ? 'bg-surface shadow-card' : 'hover:bg-hover')}>32 <button33 role="tab"34 aria-selected={active}35 data-testid="terminal-tab"36 data-session-id={id}37 onClick={() => {38 activate(id);39 router.push(`/terminal/?s=${id}`);40 }}41 className={cn('flex h-full min-w-0 items-center gap-2 pl-3 pr-1.5 text-xs font-medium', active ? 'text-text' : 'text-text-2')}42 >43 {s ? <SessionDot state={s.state} /> : <span className="size-2" />}44 <span className="truncate">{s?.title ?? 'Session'}</span>45 </button>46 <button aria-label={`Close tab ${s?.title ?? ''}`} onClick={() => closeTab(id)} className="mr-1.5 inline-flex size-5 items-center justify-center rounded-full text-muted opacity-0 hover:bg-hover hover:text-text focus-visible:opacity-100 group-hover:opacity-100">47 <X className="size-3" />48 </button>49 </div>50 );51 })}52 <IconButton label="New session" size="sm" className="mx-1 self-center" onClick={() => useUi.getState().openNewSession()}>53 <Plus className="size-3.5" />54 </IconButton>55 </div>56 <div className="flex shrink-0 items-center gap-1 px-3">57 <Link href="/tasks/" className={cn('tnum flex h-7 items-center gap-1.5 rounded-sm px-2 text-[11px] hover:bg-surface-2', running ? 'text-accent' : 'text-muted')} data-testid="running-tasks">58 <ListChecks className="size-3.5" aria-hidden /> {running} running {running === 1 ? 'task' : 'tasks'}59 </Link>60 <NotificationsBell className="size-7" />61 <ConnectionIndicator className="px-1.5" />62 </div>63 </footer>64 );65}66