Refine settings page UX

Refine the settings page with a clearer hero, side navigation, Codex /goal discoverability, and a single global restart action.
This commit is contained in:
Eyejoker
2026-05-02 02:40:09 +09:00
committed by GitHub
parent 490ae7d324
commit 1a2f879d11
4 changed files with 457 additions and 85 deletions

View File

@@ -31,6 +31,34 @@ interface AccountData {
codexCurrentIndex?: number;
}
const SETTINGS_NAV_ITEMS = [
{
href: '#settings-general',
title: '일반',
detail: '표시 · 언어',
},
{
href: '#settings-models',
title: '모델',
detail: 'owner · reviewer · arbiter',
},
{
href: '#settings-moa',
title: 'MoA',
detail: '참조 모델 · 연결 테스트',
},
{
href: '#settings-codex',
title: 'Codex',
detail: 'fast mode · /goal',
},
{
href: '#settings-accounts',
title: '계정',
detail: 'Claude · Codex',
},
] as const;
export interface SettingsPanelProps {
locale: Locale;
nickname: string;
@@ -50,54 +78,134 @@ export function SettingsPanel({
}: SettingsPanelProps) {
return (
<div className="settings-panel">
<section className="settings-section">
<h3></h3>
<label className="settings-row">
<span className="settings-label">{t.settings.nicknameLabel}</span>
<input
maxLength={32}
onChange={(event) => onNicknameChange(event.target.value)}
placeholder={t.settings.nicknamePlaceholder}
type="text"
value={nickname}
/>
<small className="settings-hint">{t.settings.nicknameHelp}</small>
</label>
<label className="settings-row">
<span className="settings-label">{t.settings.languageLabel}</span>
<select
aria-label={t.settings.languageLabel}
onChange={(event) => onLocaleChange(event.target.value as Locale)}
value={locale}
>
{LOCALES.map((item) => (
<option key={item} value={item}>
{languageNames[item]}
</option>
))}
</select>
</label>
</section>
<SettingsHero />
<div className="settings-layout">
<SettingsNav />
<main className="settings-content" aria-label="설정 항목">
<section className="settings-section" id="settings-general">
<SettingsSectionHeading
detail="Dashboard identity"
title="일반"
description="브라우저에서 보이는 이름과 언어만 즉시 바뀝니다."
/>
<div className="settings-form-grid">
<label className="settings-row">
<span className="settings-label">
{t.settings.nicknameLabel}
</span>
<input
maxLength={32}
onChange={(event) => onNicknameChange(event.target.value)}
placeholder={t.settings.nicknamePlaceholder}
type="text"
value={nickname}
/>
<small className="settings-hint">
{t.settings.nicknameHelp}
</small>
</label>
<label className="settings-row">
<span className="settings-label">
{t.settings.languageLabel}
</span>
<select
aria-label={t.settings.languageLabel}
onChange={(event) =>
onLocaleChange(event.target.value as Locale)
}
value={locale}
>
{LOCALES.map((item) => (
<option key={item} value={item}>
{languageNames[item]}
</option>
))}
</select>
</label>
</div>
</section>
<SettingsApplyBar onRestartStack={onRestartStack} />
<SettingsApplyBar onRestartStack={onRestartStack} />
<ModelSettings />
<ModelSettings />
<MoaSettingsPanel />
<MoaSettingsPanel />
<FastModeSettings />
<section className="settings-section" id="settings-codex">
<SettingsSectionHeading
detail="Codex runtime"
title="Codex 옵션"
description="빠른 응답과 실험 기능을 관리합니다. /goal은 여기에서 찾을 수 있습니다."
/>
<div className="settings-section-stack">
<FastModeSettings />
<CodexFeatureSettings />
</div>
</section>
<CodexFeatureSettings />
<AccountSettings />
<AccountSettings />
</main>
</div>
</div>
);
}
function SettingsHero() {
return (
<header className="settings-hero">
<div>
<span className="settings-kicker">Operations console</span>
<h2></h2>
<p>
, MoA, Codex , .
.
</p>
</div>
<div className="settings-hero-badges" aria-label="주요 설정">
<span>Codex /goal</span>
<span>MoA</span>
<span>Accounts</span>
</div>
</header>
);
}
function SettingsNav() {
return (
<aside className="settings-nav" aria-label="설정 섹션">
{SETTINGS_NAV_ITEMS.map((item) => (
<a href={item.href} key={item.href}>
<strong>{item.title}</strong>
<small>{item.detail}</small>
</a>
))}
</aside>
);
}
function SettingsSectionHeading({
detail,
title,
description,
}: {
detail: string;
title: string;
description: string;
}) {
return (
<header className="settings-section-head">
<span>{detail}</span>
<h3>{title}</h3>
<p>{description}</p>
</header>
);
}
function SettingsApplyBar({ onRestartStack }: { onRestartStack: () => void }) {
return (
<section className="settings-apply-bar" aria-label="변경 적용">
<div>
<span className="settings-kicker">Apply changes</span>
<strong> </strong>
<small>
, MoA, Codex ,
@@ -188,8 +296,12 @@ function ModelSettings() {
JSON.stringify(draft) !== JSON.stringify(config);
return (
<section className="settings-section">
<h3></h3>
<section className="settings-section" id="settings-models">
<SettingsSectionHeading
detail="Agent routing"
title="모델"
description="역할별 모델과 reasoning effort를 지정합니다."
/>
{error ? <p className="settings-error">{error}</p> : null}
{!draft ? (
<p className="settings-hint">
@@ -228,7 +340,7 @@ function ModelSettings() {
</button>
{savedAt && !dirty ? (
<small className="settings-hint">
. .
. .
</small>
) : null}
</div>
@@ -279,8 +391,8 @@ function FastModeSettings() {
}
return (
<section className="settings-section">
<h3> </h3>
<section className="settings-subsection">
<h4> </h4>
{error ? <p className="settings-error">{error}</p> : null}
{!state ? (
<p className="settings-hint"> </p>
@@ -365,8 +477,8 @@ function CodexFeatureSettings() {
}
return (
<section className="settings-section">
<h3>Codex </h3>
<section className="settings-subsection">
<h4>Codex </h4>
{error ? <p className="settings-error">{error}</p> : null}
{!state ? (
<p className="settings-hint"> </p>
@@ -534,8 +646,12 @@ function AccountSettings() {
}
return (
<section className="settings-section">
<h3></h3>
<section className="settings-section" id="settings-accounts">
<SettingsSectionHeading
detail="Credentials"
title="계정"
description="Claude OAuth와 Codex 계정 상태를 확인하고 전환합니다."
/>
{error ? <p className="settings-error">{error}</p> : null}
<ClaudeAccounts
busy={busy}