style(config): give Codex the same tab bar as Claude Code, drop the Cursor tab
The Codex tab strip wrapped onto a second row while Claude Code's stayed one scrollable row, so two panels that are meant to be twins read as different components. Both now render the same ConfigTabBar — the scroller lifted out of CcConfig, with the fade edges and chevrons that only appear when there is somewhere to scroll. The deferral note in CodexConfig that predicted this is gone with it. The bar also carries role="tablist"/"tab" and aria-selected now: Codex had them, Claude Code did not, and one of the two had to win. Cursor's placeholder tab goes away with the machinery that served only it — the optional Panel, the "soon" badge, the coming-soon card, and their four i18n keys. Element.scrollBy and friends move to test-setup: jsdom has none of them, and the tab bar now calls them from both pages.
This commit is contained in:
@@ -49,6 +49,7 @@ import {
|
||||
} from "../components/config/ConfirmDeleteModal";
|
||||
import { ToastNotice, type Toast } from "../components/config/ToastNotice";
|
||||
import { ScopeBadge } from "../components/config/ScopeBadge";
|
||||
import { ConfigTabBar } from "../components/config/ConfigTabBar";
|
||||
import { ProjectMemoryModal } from "../components/config/ProjectMemoryModal";
|
||||
import {
|
||||
Empty,
|
||||
@@ -82,9 +83,6 @@ interface TabDef {
|
||||
i18nKey: string;
|
||||
}
|
||||
|
||||
// ponytail: no horizontal-scroll chevrons for the tab bar - 8 tabs wrap
|
||||
// cleanly at common widths, unlike CcConfig's 12. Add the scroll affordance
|
||||
// from CcConfig's Tabs() if a future tab count needs it.
|
||||
const TABS: TabDef[] = [
|
||||
{ key: "overview", icon: Boxes, i18nKey: "tabs.overview" },
|
||||
{ key: "skills", icon: Sparkles, i18nKey: "tabs.skills" },
|
||||
@@ -625,40 +623,18 @@ function Tabs({
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="rounded-xl border border-border bg-surface-1">
|
||||
<div className="flex flex-wrap gap-1 p-1">
|
||||
{TABS.map(({ key, icon: Icon, i18nKey }) => {
|
||||
const c = countFor(key);
|
||||
const active = current === key;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
data-testid={`codex-tab-${key}`}
|
||||
role="tab"
|
||||
aria-selected={active}
|
||||
onClick={() => onSelect(key)}
|
||||
className={`flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors flex-shrink-0 whitespace-nowrap ${
|
||||
active
|
||||
? "bg-accent/15 text-accent border border-accent/30"
|
||||
: "text-fg-secondary hover:text-fg-primary hover:bg-surface-3 border border-transparent"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-3.5 h-3.5" />
|
||||
<span>{t(i18nKey)}</span>
|
||||
{c !== null && (
|
||||
<span
|
||||
className={`text-[10px] px-1.5 py-0.5 rounded-full font-semibold ${
|
||||
active ? "bg-accent/20 text-accent" : "bg-surface-3 text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
{c}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<ConfigTabBar
|
||||
tabs={TABS.map(({ key, icon, i18nKey }) => ({
|
||||
key,
|
||||
icon,
|
||||
label: t(i18nKey),
|
||||
count: countFor(key),
|
||||
}))}
|
||||
current={current}
|
||||
onSelect={onSelect}
|
||||
testIdPrefix="codex-tab"
|
||||
ariaLabel={t("title")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user