2026-08-05 10:00:16 +08:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shared underline section tabs for prompt editor and workflow side panels.
|
2026-08-05 10:15:49 +08:00
|
|
|
* Left-aligned flex chips (not equal-width grid); active state is a primary
|
|
|
|
|
* bottom border (not pill Tabs).
|
2026-08-05 10:00:16 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
export type SectionAnchorTab = {
|
|
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function SectionAnchorTabs({
|
|
|
|
|
ariaLabel,
|
|
|
|
|
sections,
|
|
|
|
|
activeSectionId,
|
|
|
|
|
onSelect,
|
|
|
|
|
className,
|
|
|
|
|
contentClassName,
|
|
|
|
|
}: {
|
|
|
|
|
ariaLabel: string;
|
|
|
|
|
sections: readonly SectionAnchorTab[];
|
|
|
|
|
activeSectionId: string;
|
|
|
|
|
onSelect: (sectionId: string) => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
/** Optional width/centering wrapper around the tab row (prompt page uses max-w-7xl). */
|
|
|
|
|
contentClassName?: string;
|
|
|
|
|
}) {
|
|
|
|
|
if (sections.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
const list = (
|
2026-08-05 10:15:49 +08:00
|
|
|
<div className="flex min-w-max border-b border-hairline">
|
2026-08-05 10:00:16 +08:00
|
|
|
{sections.map((section) => {
|
|
|
|
|
const active = activeSectionId === section.id;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={section.id}
|
|
|
|
|
type="button"
|
|
|
|
|
aria-current={active ? "location" : undefined}
|
|
|
|
|
onClick={() => onSelect(section.id)}
|
|
|
|
|
className={cn(
|
2026-08-05 10:15:49 +08:00
|
|
|
"-mb-px min-w-24 whitespace-nowrap border-b-2 px-3 py-2.5 text-center text-xs transition-colors sm:text-sm",
|
2026-08-05 10:00:16 +08:00
|
|
|
active
|
|
|
|
|
? "border-primary text-foreground"
|
|
|
|
|
: "border-transparent text-muted-foreground hover:bg-surface-strong/50 hover:text-foreground",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{section.label}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<nav
|
|
|
|
|
aria-label={ariaLabel}
|
2026-08-05 14:36:31 +08:00
|
|
|
className={cn("scrollbar-subtle shrink-0 overflow-x-auto overscroll-none", className)}
|
2026-08-05 10:00:16 +08:00
|
|
|
>
|
|
|
|
|
{contentClassName ? (
|
|
|
|
|
<div className={contentClassName}>{list}</div>
|
|
|
|
|
) : (
|
|
|
|
|
list
|
|
|
|
|
)}
|
|
|
|
|
</nav>
|
|
|
|
|
);
|
|
|
|
|
}
|