The OpenFiles component displays a horizontal tab bar showing all currently open files, allowing users to switch between files and close tabs.
Usage
import { OpenFiles } from '@/components/open-files';
function Editor() {
const { openFiles, activeFilePath, setActiveFilePath, handleCloseTab, currentTheme } = useEditor();
return (
<div>
<OpenFiles
openFiles={openFiles}
activeFilePath={activeFilePath}
onTabClick={setActiveFilePath}
onCloseTab={handleCloseTab}
currentTheme={currentTheme}
/>
<EditorView />
</div>
);
}
Props
Array of currently open files
Path of the currently active/focused file
onTabClick
(path: string) => void
required
Callback fired when a tab is clicked to switch files
onCloseTab
(path: string) => void
required
Callback fired when the close button (X) is clicked on a tab
Current theme configuration for styling tabs
Features
Tab Display
- Shows file name for each open file
- Visual indicator (●) for modified/unsaved files
- Close button (X icon) on each tab
- Horizontal scrollable layout
Visual States
- Active tab: Transparent background
- Inactive tab: Uses
currentTheme.openFilePill background
- Modified file: Blue dot indicator (●) next to filename
- Hover effects: Reduced opacity on hover (0.5)
Interaction
- Click tab to switch to that file
- Click X icon to close tab (stops propagation to prevent switching)
- Whitespace handling with
whitespace-nowrap
OpenFile Type
interface OpenFile {
path: string; // Full file path
name: string; // File name only
content: string; // File contents
modified: boolean; // Unsaved changes flag
}
Implementation
src/components/open-files.tsx
import { OpenFile, ThemeConfig } from "@/lib/types";
import { XIcon } from "lucide-react";
export function OpenFiles(props: {
openFiles: OpenFile[];
onTabClick: (path: string) => void;
onCloseTab: (path: string) => void;
currentTheme: ThemeConfig;
activeFilePath: string | null;
}) {
return (
<>
{props.openFiles.length > 0 && (
<div className="flex gap-2 h-[25px]">
{props.openFiles.map((file) => (
<div
onClick={() => props.onTabClick(file.path)}
key={file.path}
className={`flex items-center gap-2 text-[12px] rounded-md px-3 cursor-pointer hover:opacity-50`}
style={{
backgroundColor:
file.path === props.activeFilePath
? "var(--colors-transparent)"
: props.currentTheme.openFilePill?.bg,
color: props.currentTheme.openFilePill?.fg,
}}
>
<p className="whitespace-nowrap">
{file.name}
{file.modified && <span className="text-[#569cd6]"> ●</span>}
</p>
<span
className="opacity-60 hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
props.onCloseTab(file.path);
}}
>
<XIcon size={12} />
</span>
</div>
))}
</div>
)}
</>
);
}
Styling
- Tab height: 25px fixed height
- Font size: 12px
- Spacing: 2px gap between tabs, 3px horizontal padding
- Border radius: Rounded corners with
rounded-md
- Icons: 12px X icon from lucide-react
Theme Integration
Tabs use the openFilePill theme property:
{
openFilePill: {
bg: "#252526", // Background for inactive tabs
fg: "#cccccc" // Text color
}
}
Usage in Editor Component
The OpenFiles component is integrated at the top of the Editor:
src/components/Editor.tsx
return (
<div className="w-[calc(100vw-240px)] space-y-2 py-2 pb-0 pr-2 h-screen flex flex-col">
<OpenFiles
openFiles={openFiles}
onTabClick={setActiveFilePath}
onCloseTab={handleCloseTab}
currentTheme={currentTheme}
activeFilePath={activeFilePath}
/>
<div ref={editorRef} className="h-full rounded-tl-2xl overflow-hidden" />
</div>
);