Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
/** Tabs — a horizontal, optionally closable tab bar (LD).
|
||
*
|
||
* Presentation-only and controlled: the parent owns the active id and the open
|
||
* set. Used for the project tab bar (one tab per open project, ARCHITECTURE §10)
|
||
* but generic enough for any tabbed surface.
|
||
*/
|
||
|
||
import { cn } from "../lib/cn";
|
||
import { IconButton } from "./IconButton";
|
||
|
||
export interface TabItem {
|
||
/** Stable id, returned by `onSelect`/`onClose`. */
|
||
id: string;
|
||
/** Visible label. */
|
||
label: string;
|
||
}
|
||
|
||
export interface TabsProps {
|
||
/** Tabs to render, left to right. */
|
||
items: TabItem[];
|
||
/** Currently-active tab id. */
|
||
value: string | null;
|
||
/** Called with the id of the tab the user activates. */
|
||
onSelect: (id: string) => void;
|
||
/** When provided, each tab shows a close (×) control. */
|
||
onClose?: (id: string) => void;
|
||
className?: string;
|
||
}
|
||
|
||
/** A themed tab strip with selection and optional per-tab close. */
|
||
export function Tabs({ items, value, onSelect, onClose, className }: TabsProps) {
|
||
return (
|
||
<div
|
||
role="tablist"
|
||
className={cn("flex flex-wrap items-stretch gap-1", className)}
|
||
>
|
||
{items.map((tab) => {
|
||
const active = tab.id === value;
|
||
return (
|
||
<div
|
||
key={tab.id}
|
||
className={cn(
|
||
"group flex items-center gap-1 rounded-md border px-1 transition-colors",
|
||
active
|
||
? "border-border-strong bg-raised"
|
||
: "border-transparent hover:bg-raised",
|
||
)}
|
||
>
|
||
<button
|
||
type="button"
|
||
role="tab"
|
||
aria-selected={active}
|
||
onClick={() => onSelect(tab.id)}
|
||
className={cn(
|
||
"px-2 py-1 text-sm focus-visible:outline-none",
|
||
active ? "font-semibold text-content" : "text-muted hover:text-content",
|
||
)}
|
||
>
|
||
{tab.label}
|
||
</button>
|
||
{onClose && (
|
||
<IconButton
|
||
size="sm"
|
||
aria-label={`close ${tab.label}`}
|
||
onClick={() => onClose(tab.id)}
|
||
className="opacity-60 group-hover:opacity-100"
|
||
>
|
||
×
|
||
</IconButton>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
}
|