/** * SpaceChatTabBar — 会話画面の単一タブバー(2ゾーン構造)。 * * 左: タブ群(overflow-x-auto のスクロールゾーン) * 右: actions(shrink-0 の固定ゾーン。継続 / 共有 / 削除ボタンが入る) * * タブが溢れて横スクロールになっても、actions は右端に固定されたまま * スクロールで流れない。これが2ゾーンに分ける理由(旧: アクション専用行 * space-chat-actions を廃止して縦約40pxを回収した)。 */ import { useRef } from 'react'; export interface SpaceChatTabDef { id: string; labelKey: string; } interface SpaceChatTabBarProps { tabs: SpaceChatTabDef[]; activeTab: string; onSelect: (id: string) => void; ariaLabel: string; renderLabel: (tab: SpaceChatTabDef) => string; /** browser/ssh タブの出現アニメ用クラス(detailTabs.tabAppearClass を渡す) */ appearClass: (id: string) => string; /** 右端固定ゾーンに置くアクション群。無ければゾーンごと描画しない。 */ actions?: React.ReactNode; } export function SpaceChatTabBar({ tabs, activeTab, onSelect, ariaLabel, renderLabel, appearClass, actions, }: SpaceChatTabBarProps) { const tabRefs = useRef>([]); // 矢印キーでタブ間移動(端で wrap)+ Home/End。選択とフォーカスを同時に動かす // (role=tablist の標準操作)。SpaceDetail から移設。 const handleKeyDown = (e: React.KeyboardEvent, index: number) => { let next: number; if (e.key === 'ArrowRight') next = (index + 1) % tabs.length; else if (e.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length; else if (e.key === 'Home') next = 0; else if (e.key === 'End') next = tabs.length - 1; else return; e.preventDefault(); onSelect(tabs[next].id); tabRefs.current[next]?.focus(); }; return (
{tabs.map((tb, i) => { const active = tb.id === activeTab; return ( ); })}
{actions && (
{actions}
)}
); }