Skip to content

Commit 4633ea6

Browse files
committed
Convert Settings tabs to a left sidebar, fix two latent Tabs bugs
Switched Settings' tab bar from horizontal-at-top to a vertical sidebar (orientation="vertical"), which surfaced two real bugs in the previously-unused ui/tabs.tsx component - nobody had wired it into the app until now, so neither had ever been exercised: 1. The component's orientation-conditional classes (group-data-vertical/tabs:flex-col etc.) depend on Tailwind variants named `data-vertical`/`data-horizontal`, but this project's Tailwind v4 config never defined them - only `dark` was registered as a custom variant. Without them, those classes silently matched nothing, so "vertical" orientation looked identical to horizontal. Fixed by adding the two missing @custom-variant rules to index.css, mapping them to [data-orientation=vertical/horizontal]. 2. Switching tabs left both the outgoing and incoming panel visible at once. Base UI's Tabs.Panel marks the outgoing panel `inert` and keeps it mounted to support an exit transition, but the component shipped no CSS to actually hide it once no transition is defined - so it just sat there, fully visible, forever. Fixed by hiding any panel carrying the `inert` attribute. Verified end-to-end in a live browser session (with window.api mocked in place of the Electron bridge, since Settings only renders inside Electron): confirmed the sidebar actually stacks vertically via computed flex-direction, and clicked through all 6 tabs confirming each one's content is fully isolated with no bleed-through.
1 parent 85d4d2d commit 4633ea6

3 files changed

Lines changed: 21 additions & 10 deletions

File tree

‎frontend/src/components/ui/tabs.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
7171
return (
7272
<TabsPrimitive.Panel
7373
data-slot="tabs-content"
74-
className={cn("flex-1 text-sm outline-none", className)}
74+
// The outgoing panel during a tab switch is marked `inert` (and stays
75+
// mounted) rather than removed outright, since base-ui's Panel is
76+
// built to support an exit transition — without one defined here, it
77+
// would otherwise sit fully visible on top of the incoming panel.
78+
className={cn("flex-1 text-sm outline-none [&[inert]]:hidden", className)}
7579
{...props}
7680
/>
7781
)

‎frontend/src/index.css‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@import "tailwindcss";
22
@custom-variant dark (&:where(.dark, .dark *));
3+
@custom-variant data-vertical (&[data-orientation="vertical"]);
4+
@custom-variant data-horizontal (&[data-orientation="horizontal"]);
35

46
:root {
57
--radius: 0.625rem;

‎frontend/src/pages/Settings.tsx‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,19 @@ export default function Settings() {
581581

582582
return (
583583
<ScrollArea className="h-full">
584-
<div className="mx-auto max-w-2xl p-6 pb-16 2xl:max-w-3xl">
584+
<div className="mx-auto max-w-4xl p-6 pb-16 2xl:max-w-5xl">
585585
<div className="mb-6 flex items-center gap-2">
586586
<Settings2 className="size-5 text-muted-foreground" />
587587
<h1 className="text-lg font-semibold tracking-tight">{t.settings}</h1>
588588
</div>
589589

590-
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as SettingsTab)}>
591-
<TabsList className="mb-6 h-auto flex-wrap">
590+
<Tabs
591+
value={activeTab}
592+
onValueChange={(v) => setActiveTab(v as SettingsTab)}
593+
orientation="vertical"
594+
className="items-start gap-8"
595+
>
596+
<TabsList variant="line" className="w-48 shrink-0">
592597
<TabsTrigger value="general">{t.settingsTabGeneral}</TabsTrigger>
593598
<TabsTrigger value="models">{t.settingsTabModels}</TabsTrigger>
594599
<TabsTrigger value="integrations">{t.settingsTabIntegrations}</TabsTrigger>
@@ -597,7 +602,7 @@ export default function Settings() {
597602
<TabsTrigger value="data">{t.settingsTabData}</TabsTrigger>
598603
</TabsList>
599604

600-
<TabsContent value="general" className="flex flex-col gap-8">
605+
<TabsContent value="general" className="min-w-0 flex-1 flex flex-col gap-8">
601606
<div>
602607
<SettingsSection title={t.ollamaServer}>
603608
<SettingsRow
@@ -780,7 +785,7 @@ export default function Settings() {
780785
</div>
781786
</TabsContent>
782787

783-
<TabsContent value="models" className="flex flex-col gap-8">
788+
<TabsContent value="models" className="min-w-0 flex-1 flex flex-col gap-8">
784789
<div>
785790
{specs && (
786791
<SettingsSection title={t.yourSystem}>
@@ -1047,7 +1052,7 @@ export default function Settings() {
10471052
</div>
10481053
</TabsContent>
10491054

1050-
<TabsContent value="integrations" className="flex flex-col gap-8">
1055+
<TabsContent value="integrations" className="min-w-0 flex-1 flex flex-col gap-8">
10511056
<div>
10521057
<SettingsSection title={t.cloudProviders} description={t.keysEncryptedNote}>
10531058
<SettingsRow label="ChatGPT (OpenAI)" stacked>
@@ -1242,7 +1247,7 @@ export default function Settings() {
12421247
</div>
12431248
</TabsContent>
12441249

1245-
<TabsContent value="voice" className="flex flex-col gap-8">
1250+
<TabsContent value="voice" className="min-w-0 flex-1 flex flex-col gap-8">
12461251
<div>
12471252
{settings && (
12481253
<SettingsSection title={t.ttsSection}>
@@ -1297,7 +1302,7 @@ export default function Settings() {
12971302
</div>
12981303
</TabsContent>
12991304

1300-
<TabsContent value="chat" className="flex flex-col gap-8">
1305+
<TabsContent value="chat" className="min-w-0 flex-1 flex flex-col gap-8">
13011306
<div>
13021307
{settings && (
13031308
<>
@@ -1622,7 +1627,7 @@ export default function Settings() {
16221627
</div>
16231628
</TabsContent>
16241629

1625-
<TabsContent value="data" className="flex flex-col gap-8">
1630+
<TabsContent value="data" className="min-w-0 flex-1 flex flex-col gap-8">
16261631
<div>
16271632
<SettingsSection title={t.dataManagement}>
16281633
<SettingsRow label={t.exportAllConversations} description={t.exportAllDescription}>

0 commit comments

Comments
 (0)