IDE@paxeer/ide/editor-tabsimplemented
Editor tabs
shadcn Tabs strip with dirty markers and overflow scrolling.
Install
$
pnpm exec designx add @paxeer/ide/editor-tabsPreview
Loading preview...
Source code
components/designx/ide/editor-tabs.tsxtsx
"use client";
import { Button, ScrollRegion, Tabs, TabsList, TabsTrigger, cn } from "@paxeer/core";
export interface EditorTab {
id: string;
title: string;
dirty?: boolean;
path?: string;
}
export interface EditorTabsProps {
tabs: EditorTab[];
activeId?: string;
onActivate?: (tab: EditorTab) => void;
onClose?: (tab: EditorTab) => void;
className?: string;
}
export function EditorTabs({
tabs,
activeId,
onActivate,
onClose,
className,
}: EditorTabsProps) {
if (tabs.length === 0) {
return (
<div
className={cn(
"flex h-10 items-center px-3 text-[length:var(--md-sys-typescale-label-medium-size)] text-on-surface-variant",
className,
)}
>
No open editors
</div>
);
}
return (
<Tabs
value={activeId}
onValueChange={(value) => {
const tab = tabs.find((entry) => entry.id === value);
if (tab) onActivate?.(tab);
}}
className={cn("gap-0", className)}
>
<ScrollRegion orientation="horizontal" label="Open editors" className="w-full">
<TabsList variant="line" className="h-10 w-max min-w-full justify-start rounded-none border-b border-outline-variant p-0">
{tabs.map((tab) => (
<div key={tab.id} className="flex items-center border-r border-outline-variant">
<TabsTrigger value={tab.id} title={tab.path ?? tab.title} className="rounded-none">
{tab.title}
{tab.dirty ? (
<span aria-label="Unsaved" className="size-1.5 rounded-full bg-tertiary" />
) : null}
</TabsTrigger>
{onClose ? (
<Button
variant="ghost"
size="icon-sm"
aria-label={`Close ${tab.title}`}
className="mr-1"
onClick={() => onClose(tab)}
>
×
</Button>
) : null}
</div>
))}
</TabsList>
</ScrollRegion>
</Tabs>
);
}
Dependencies
Registry dependencies