feat(settings): move restart badge inline with option title

- Remove field-meta row (config path, advanced chip) from option rows
- Inline live/restart status badge beside each option label
- Extract getFieldTitleBadges into settings-field-layout module with tests
This commit is contained in:
2026-05-17 19:38:20 -07:00
parent db60365b0e
commit 2b13c82d69
6 changed files with 72 additions and 37 deletions
@@ -0,0 +1,30 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ConfigSettingsField } from '../types/settings';
import { getFieldTitleBadges } from './settings-field-layout';
const advancedRestartField: ConfigSettingsField = {
id: 'ankiConnect.knownWords.highlightEnabled',
label: 'Enabled',
description: 'Enable fast local highlighting for words already known in Anki.',
configPath: 'ankiConnect.knownWords.highlightEnabled',
category: 'mining-anki',
section: 'Known Words',
control: 'boolean',
defaultValue: false,
restartBehavior: 'restart',
advanced: true,
};
test('field title badges show restart status without config paths or advanced labels', () => {
const badges = getFieldTitleBadges(advancedRestartField);
assert.deepEqual(badges, [
{
className: 'restart-chip restart',
text: 'Restart',
},
]);
assert.equal(JSON.stringify(badges).includes(advancedRestartField.configPath), false);
assert.equal(JSON.stringify(badges).includes('Advanced'), false);
});
+15
View File
@@ -0,0 +1,15 @@
import type { ConfigSettingsField } from '../types/settings';
export interface FieldTitleBadge {
className: string;
text: string;
}
export function getFieldTitleBadges(field: ConfigSettingsField): FieldTitleBadge[] {
return [
{
className: `restart-chip ${field.restartBehavior}`,
text: field.restartBehavior === 'hot-reload' ? 'Live' : 'Restart',
},
];
}
+10 -20
View File
@@ -20,6 +20,7 @@ import {
setDraftValue,
type SettingsDraft,
} from './settings-model';
import { getFieldTitleBadges } from './settings-field-layout';
import { getSubtitleCssManagedConfigPaths, getSubtitleCssScopeForPath } from './subtitle-style-css';
declare global {
@@ -112,24 +113,6 @@ function createElement<K extends keyof HTMLElementTagNameMap>(
return element;
}
function createFieldMeta(field: ConfigSettingsField): HTMLElement {
const meta = createElement('div', 'field-meta');
const path = createElement('code');
path.textContent = field.configPath;
meta.append(path);
const restart = createElement('span', `restart-chip ${field.restartBehavior}`);
restart.textContent = field.restartBehavior === 'hot-reload' ? 'Live' : 'Restart';
meta.append(restart);
if (field.advanced) {
const advanced = createElement('span', 'advanced-chip');
advanced.textContent = 'Advanced';
meta.append(advanced);
}
return meta;
}
function valueForField(field: ConfigSettingsField): ConfigSettingsSnapshotValue {
if (!state.draft) {
return field.defaultValue;
@@ -221,10 +204,17 @@ function renderField(field: ConfigSettingsField): HTMLElement {
const row = createElement('article', 'field-row');
const header = createElement('div', 'field-copy');
const label = createElement('h3');
label.textContent = field.label;
const labelText = createElement('span', 'field-title-text');
labelText.textContent = field.label;
label.append(labelText);
for (const badge of getFieldTitleBadges(field)) {
const badgeEl = createElement('span', badge.className);
badgeEl.textContent = badge.text;
label.append(badgeEl);
}
const description = createElement('p');
description.textContent = field.description;
header.append(label, description, createFieldMeta(field));
header.append(label, description);
const controlWrap = createElement('div', 'field-control');
controlWrap.append(
+11 -15
View File
@@ -546,6 +546,10 @@ code {
}
.field-copy h3 {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin: 0 0 5px;
font-size: 14px;
font-weight: 700;
@@ -553,6 +557,10 @@ code {
letter-spacing: -0.005em;
}
.field-title-text {
min-width: 0;
}
.field-copy p {
max-width: 640px;
margin: 0;
@@ -561,16 +569,10 @@ code {
line-height: 1.55;
}
.field-meta {
display: flex;
flex-wrap: wrap;
.restart-chip {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 6px;
margin-top: 10px;
}
.restart-chip,
.advanced-chip {
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--line);
@@ -594,12 +596,6 @@ code {
color: var(--ctp-peach);
}
.advanced-chip {
border-color: rgba(198, 160, 246, 0.4);
background: rgba(198, 160, 246, 0.1);
color: var(--ctp-mauve);
}
.field-control {
display: flex;
justify-content: flex-end;