feat(frontend): implement SectionAnchorTabs for improved navigation

- Replaced the existing navigation in PromptEditor and PanelAnchorNavigation with the new SectionAnchorTabs component for better code reuse and consistency.
- The SectionAnchorTabs component provides a shared interface for section navigation with equal-width columns and an active state indicator.
This commit is contained in:
Eric Wang
2026-08-05 10:00:16 +08:00
parent 6863567fa9
commit aa72c522c2
3 changed files with 100 additions and 56 deletions
@@ -0,0 +1,78 @@
"use client";
/**
* Shared underline section tabs for prompt editor and workflow side panels.
* Equal-width columns; active state is a primary bottom border (not pill Tabs).
*/
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 = (
<div
// w-full matters inside overflow-x-auto parents: without it the grid
// shrink-wraps to minWidth and tabs look left-clustered.
className="grid w-full border-b border-hairline"
style={{
gridTemplateColumns: `repeat(${sections.length}, minmax(0, 1fr))`,
minWidth: `${Math.max(sections.length * 5.6, 12)}rem`,
}}
>
{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(
"-mb-px whitespace-nowrap border-b-2 px-2 py-2.5 text-center text-xs transition-colors sm:px-3 sm:text-sm",
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}
className={cn("shrink-0 overflow-x-auto overscroll-none", className)}
>
{contentClassName ? (
<div className={contentClassName}>{list}</div>
) : (
list
)}
</nav>
);
}