Files
markdown-converter/src/renderer/components/layout/TabBar.tsx
T
amitwh 650c266945 feat(renderer): TabBar wired to file store (dirty dot, close, active highlight)
- Renders tabs from useFileStore.openTabs with title + dirty dot + close button
- Uses Zustand selectors for granular re-renders
- Active tab highlighted with aria-current and accent background
- Close button stops propagation; does not trigger tab activation
- Horizontal scroll on overflow, h-9 fixed bar with border
- Empty state preserved when no tabs open

Tests: 6 cases (empty, render, highlight, click-switch, click-close, dirty indicator)
- 75 total tests (was 70, gained 5 new)
- All pass
- Build: succeeds

Amit Haridas
2026-06-05 15:03:01 +05:30

59 lines
2.1 KiB
TypeScript

import { X } from 'lucide-react';
import { useFileStore } from '@/stores/file-store';
import { cn } from '@/lib/utils';
export function TabBar() {
const openTabs = useFileStore((s) => s.openTabs);
const activeTabId = useFileStore((s) => s.activeTabId);
const setActiveTab = useFileStore((s) => s.setActiveTab);
const closeTab = useFileStore((s) => s.closeTab);
if (openTabs.length === 0) {
return (
<div className="flex h-9 items-center border-b border-border bg-card/20 px-3 text-xs text-muted-foreground">
<span>No files open</span>
</div>
);
}
return (
<div className="flex h-9 items-center border-b border-border bg-card/20 overflow-x-auto">
<div className="flex h-full items-center px-1">
{openTabs.map((tab) => {
const isActive = tab.id === activeTabId;
return (
<div
key={tab.id}
role="tab"
aria-selected={isActive}
aria-current={isActive ? 'page' : undefined}
className={cn(
'group flex h-full cursor-pointer items-center gap-1 border-r border-border px-3 text-xs transition-colors',
isActive
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
)}
onClick={() => setActiveTab(tab.id)}
>
{tab.dirty && (
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-primary" aria-label="Unsaved changes" />
)}
<span className="truncate max-w-[120px]">{tab.title}</span>
<button
aria-label={`Close ${tab.title}`}
className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center rounded opacity-0 group-hover:opacity-100 hover:bg-muted-foreground/20 transition-opacity"
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
}}
>
<X className="h-3 w-3" />
</button>
</div>
);
})}
</div>
</div>
);
}