refactor(ui): fold Settings + Stats tabs into shared Tabs (R13 S2c step 6)

Tabs gains an `orientation` prop (+ optional per-tab `icon`/`testId`) so the
two hand-rolled NAV tab-sets that duplicated its active treatment now use it:
- SettingsPanel vertical rail (appearance/notifications/account) -> vertical
  Tabs with icons; settings-tab-* testIds preserved
- Stats overview/system tabs -> Tabs (rounded-full+border -> the rounded-lg
  tab canonical, per the agreed tab shape)

The underline ChannelPage tabs and the icon NavSidebar rail are intentionally
left as distinct idioms. Self-E2E'd: settings rail switches + persists with
icons, stats tabs toggle. gate green (tsc/eslint-0/vitest 136/knip/prettier).
Branch not merged.
This commit is contained in:
2026-08-15 01:04:59 +02:00
parent b57aec113e
commit dd2d53bb96
3 changed files with 46 additions and 41 deletions
+13 -19
View File
@@ -14,6 +14,7 @@ import { type SaveState } from "./ui/DraftSaveBar";
import { useConfirm } from "./ConfirmProvider";
import { useWizard } from "./WizardProvider";
import { usePrefs } from "./PrefsProvider";
import Tabs from "./Tabs";
import { useMe } from "../lib/useMe";
// The Settings page edits server-persisted preferences. Each change applies locally for instant
@@ -61,25 +62,18 @@ export default function SettingsPanel() {
<div className="glass rounded-2xl overflow-hidden">
<div className="flex">
{/* Vertical tab rail — never wraps; active is a clear accent pill. */}
<nav className="w-32 sm:w-36 shrink-0 border-r border-border/60 p-2 flex flex-col gap-1">
{TABS.map((tabItem) => {
const active = tab === tabItem.id;
return (
<button
key={tabItem.id}
data-testid={`settings-tab-${tabItem.id}`}
onClick={() => setTab(tabItem.id)}
aria-pressed={active}
className={`pill flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left ${
active ? "font-medium shadow-md shadow-accent/20" : "hover:bg-card/60"
}`}
>
<tabItem.icon className="w-4 h-4 shrink-0" />
{t(`settings.tabs.${tabItem.id}`)}
</button>
);
})}
</nav>
<Tabs
orientation="vertical"
className="w-32 sm:w-36 shrink-0 border-r border-border/60 p-2 flex flex-col gap-1"
active={tab}
onChange={setTab}
tabs={TABS.map((tabItem) => ({
id: tabItem.id,
label: t(`settings.tabs.${tabItem.id}`),
icon: tabItem.icon,
testId: `settings-tab-${tabItem.id}`,
}))}
/>
{/* Render only the active tab so the panel sizes to its actual content. (Stacking
every tab in one grid cell made the whole panel as tall as the tallest tab —