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:
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* @file SettingsHub.tsx
|
||||
* @description Tabbed shell for /settings that hosts the dashboard's own settings plus per-AI-tool configuration panels (Claude Code and Codex today, Cursor reserved), with the active tab kept in the `?tool=` query param.
|
||||
* @description Tabbed shell for /settings that hosts the dashboard's own settings plus one configuration panel per AI tool (Claude Code and Codex), with the active tab kept in the `?tool=` query param.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import type { ComponentType } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Boxes, Settings as SettingsIcon, SquareTerminal, MousePointerClick } from "lucide-react";
|
||||
import { Boxes, Settings as SettingsIcon, SquareTerminal } from "lucide-react";
|
||||
import { PageTabs } from "../components/PageTabs";
|
||||
import { Settings } from "./Settings";
|
||||
import { CcConfig } from "./CcConfig";
|
||||
@@ -16,8 +16,7 @@ interface ToolTab {
|
||||
id: string;
|
||||
labelKey: string;
|
||||
Icon: ComponentType<{ className?: string }>;
|
||||
/** Omit for tools that are not integrated yet - the tab renders a placeholder. */
|
||||
Panel?: ComponentType;
|
||||
Panel: ComponentType;
|
||||
}
|
||||
|
||||
const DASHBOARD_TAB: ToolTab = {
|
||||
@@ -32,7 +31,6 @@ const TOOL_TABS: ToolTab[] = [
|
||||
DASHBOARD_TAB,
|
||||
{ id: "claude-code", labelKey: "tools.claudeCode", Icon: Boxes, Panel: CcConfig },
|
||||
{ id: "codex", labelKey: "tools.codex", Icon: SquareTerminal, Panel: CodexConfig },
|
||||
{ id: "cursor", labelKey: "tools.cursor", Icon: MousePointerClick },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -51,24 +49,14 @@ export function SettingsHub() {
|
||||
ariaLabel={t("tools.label", "Tools")}
|
||||
activeId={active.id}
|
||||
onSelect={(id) => setParams(id === DASHBOARD_TAB.id ? {} : { tool: id }, { replace: true })}
|
||||
tabs={TOOL_TABS.map(({ id, labelKey, Icon, Panel }) => ({
|
||||
tabs={TOOL_TABS.map(({ id, labelKey, Icon }) => ({
|
||||
id,
|
||||
Icon,
|
||||
label: t(labelKey),
|
||||
...(Panel ? {} : { badge: t("tools.soonBadge", "soon") }),
|
||||
}))}
|
||||
/>
|
||||
|
||||
{active.Panel ? (
|
||||
<active.Panel />
|
||||
) : (
|
||||
<div className="card p-8 text-center space-y-1">
|
||||
<p className="text-sm font-medium text-fg-primary">
|
||||
{t("tools.comingSoon", { tool: t(active.labelKey) })}
|
||||
</p>
|
||||
<p className="text-xs text-fg-muted">{t("tools.comingSoonDesc")}</p>
|
||||
</div>
|
||||
)}
|
||||
<active.Panel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user