All files / src/components/agents AgentTabBar.tsx

0% Statements 0/64
100% Branches 1/1
100% Functions 1/1
0% Lines 0/64

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                                                                                                                                                                                     
/**
 * AgentTabBar — presentational tab bar for the agent detail page (#1419).
 * Renders Chat / Sessions / Schedules / Settings tabs, reusing AppNav styling.
 *
 * @module
 */
 
import { useRef, type JSX, type KeyboardEvent } from "react";
import { Activity, CalendarClock, MessageSquare, Settings } from "lucide-react";
import { useAppNavigate, agentUrl, type AgentTab } from "../../utils/navigation.js";
import navStyles from "../layout/AppNav.module.scss";
 
const ICON_SIZE: number = 18;
 
interface AgentTabDef {
  id: AgentTab;
  label: string;
  icon: JSX.Element;
}
 
const AGENT_TABS: AgentTabDef[] = [
  { id: "chat", label: "Chat", icon: <MessageSquare size={ICON_SIZE} /> },
  { id: "sessions", label: "Sessions", icon: <Activity size={ICON_SIZE} /> },
  { id: "schedules", label: "Schedules", icon: <CalendarClock size={ICON_SIZE} /> },
  { id: "settings", label: "Settings", icon: <Settings size={ICON_SIZE} /> },
];
 
/** Props for {@link AgentTabBar}. */
export interface AgentTabBarProps {
  /** The agent whose tabs are being rendered. */
  agentId: string;
  /** Which tab is currently active. */
  activeTab: AgentTab;
}
 
/** Horizontal tab bar for navigating between agent detail views. */
export function AgentTabBar({ agentId, activeTab }: AgentTabBarProps): JSX.Element {
  const navigate = useAppNavigate();
  const navRef = useRef<HTMLElement>(null);
 
  const handleKeyDown = (e: KeyboardEvent<HTMLElement>): void => {
    const buttons = navRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]');
    if (!buttons || buttons.length === 0) return;
 
    const currentIndex = AGENT_TABS.findIndex((t) => t.id === activeTab);
    let nextIndex = currentIndex;
 
    if (e.key === "ArrowRight" || e.key === "ArrowDown") {
      e.preventDefault();
      nextIndex = (currentIndex + 1) % AGENT_TABS.length;
    } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
      e.preventDefault();
      nextIndex = (currentIndex - 1 + AGENT_TABS.length) % AGENT_TABS.length;
    } else if (e.key === "Home") {
      e.preventDefault();
      nextIndex = 0;
    } else if (e.key === "End") {
      e.preventDefault();
      nextIndex = AGENT_TABS.length - 1;
    } else {
      return;
    }
 
    const nextTab = AGENT_TABS[nextIndex];
    navigate(agentUrl(agentId, nextTab.id));
    buttons[nextIndex].focus();
  };
 
  return (
    <nav
      ref={navRef}
      className={navStyles.nav}
      role="tablist"
      aria-label="Agent navigation"
      aria-orientation="horizontal"
      onKeyDown={handleKeyDown}
      data-testid="agent-tab-bar"
    >
      {AGENT_TABS.map((tab) => {
        const isActive = tab.id === activeTab;
        return (
          <button
            key={tab.id}
            role="tab"
            aria-selected={isActive}
            tabIndex={isActive ? 0 : -1}
            className={`${navStyles.tab} ${isActive ? navStyles.tabActive : ""}`}
            onClick={() => navigate(agentUrl(agentId, tab.id))}
            data-testid={`agent-tab-${tab.id}`}
          >
            <span className={navStyles.tabIcon}>{tab.icon}</span>
            <span className={navStyles.tabLabel}>{tab.label}</span>
          </button>
        );
      })}
    </nav>
  );
}