refactor: extract SettingsTabBar as reusable infrastructure component
Replace one-off inline tab bar in InstancePanel with a reusable SettingsTabBar that reads from SettingsSectionsContext. Any panel using tab mode can drop it in with zero configuration. Polish: proper underline indicator, consistent gap spacing, negative top margin to sit flush with content area padding.
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { useSettingsSectionsContext } from './SettingsSectionsContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable tab bar for settings panels in tab mode.
|
||||||
|
* Reads sections and activeSection from SettingsSectionsContext.
|
||||||
|
* Drop this into any panel that uses useSettingsSections with onNavigate.
|
||||||
|
*/
|
||||||
|
export function SettingsTabBar() {
|
||||||
|
const ctx = useSettingsSectionsContext();
|
||||||
|
if (!ctx || ctx.sections.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-4 border-b border-white/[0.06] -mt-2 mb-4">
|
||||||
|
{ctx.sections.map((s) => (
|
||||||
|
<button
|
||||||
|
key={s.id}
|
||||||
|
onClick={() => ctx.scrollToSection(s.id)}
|
||||||
|
className={`pb-2.5 text-sm transition-colors relative ${
|
||||||
|
ctx.activeSection === s.id
|
||||||
|
? 'text-txt-primary font-medium'
|
||||||
|
: 'text-txt-tertiary hover:text-txt-secondary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{s.label}
|
||||||
|
{ctx.activeSection === s.id && (
|
||||||
|
<span className="absolute bottom-0 left-0 right-0 h-0.5 bg-accent-primary rounded-full" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useSettingsStore } from '../../../stores/settingsStore';
|
import { useSettingsStore } from '../../../stores/settingsStore';
|
||||||
import { useSettingsSections } from '../../../hooks/useSettingsSections';
|
import { useSettingsSections } from '../../../hooks/useSettingsSections';
|
||||||
import type { SettingsSection } from '../SettingsSectionsContext';
|
import type { SettingsSection } from '../SettingsSectionsContext';
|
||||||
|
import { SettingsTabBar } from '../SettingsTabBar';
|
||||||
import { GeneralPanel } from '../instanceSettingsPanels/GeneralPanel';
|
import { GeneralPanel } from '../instanceSettingsPanels/GeneralPanel';
|
||||||
import { StreamingPanel } from '../instanceSettingsPanels/StreamingPanel';
|
import { StreamingPanel } from '../instanceSettingsPanels/StreamingPanel';
|
||||||
import { StoragePanel } from '../instanceSettingsPanels/StoragePanel';
|
import { StoragePanel } from '../instanceSettingsPanels/StoragePanel';
|
||||||
@@ -34,23 +35,9 @@ export function InstancePanel() {
|
|||||||
fetchStreamingLimits();
|
fetchStreamingLimits();
|
||||||
}, [fetchInstanceSettings, fetchStreamingLimits]);
|
}, [fetchInstanceSettings, fetchStreamingLimits]);
|
||||||
|
|
||||||
const tabPillClass = (t: SubTab) =>
|
|
||||||
`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
|
||||||
subTab === t
|
|
||||||
? 'text-txt-primary font-medium border-b-2 border-accent-primary'
|
|
||||||
: 'text-txt-tertiary hover:text-txt-secondary'
|
|
||||||
}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Top tab bar */}
|
<SettingsTabBar />
|
||||||
<div className="flex gap-1 border-b border-white/[0.04] pb-0">
|
|
||||||
{SECTIONS.map((s) => (
|
|
||||||
<button key={s.id} onClick={() => setSubTab(s.id as SubTab)} className={tabPillClass(s.id as SubTab)}>
|
|
||||||
{s.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{subTab === 'general' && <GeneralPanel />}
|
{subTab === 'general' && <GeneralPanel />}
|
||||||
{subTab === 'streaming' && <StreamingPanel />}
|
{subTab === 'streaming' && <StreamingPanel />}
|
||||||
|
|||||||
Reference in New Issue
Block a user