DesignX
IDE@paxeer/ide/editor-tabsimplemented

Editor tabs

shadcn Tabs strip with dirty markers and overflow scrolling.

Install

$pnpm exec designx add @paxeer/ide/editor-tabs

Preview

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>
  );
}