Use shared form controls and enforce UI patterns (Refs #110)
CI / checks (push) Successful in 4m51s

This commit is contained in:
2026-10-10 00:19:48 -04:00
parent 8d1bb69591
commit cc9c10226e
54 changed files with 1431 additions and 1449 deletions
+56 -57
View File
@@ -1,4 +1,7 @@
<script lang="ts">
import Select from '$ui/components/primitives/Select.svelte';
import Field from '$ui/components/primitives/Field.svelte';
import Input from '$ui/components/primitives/Input.svelte';
import { untrack } from 'svelte';
import { beforeNavigate, goto } from '$app/navigation';
import { confirmAction } from '$ui/confirm.js';
@@ -155,18 +158,18 @@
<svelte:window onbeforeunload={beforeUnload} />
<div class="settings-layout">
<aside class="section-rail">
<label class="search-label" for="settings-search">Find a setting</label>
<input
id="settings-search"
type="search"
placeholder="Search settings"
bind:value={query}
disabled={saving}
oninput={() => (reviewInvalid = false)}
/>
<label class="mobile-section"
>Section
<select
<Field label="Find a setting" forId="settings-search">
<Input
id="settings-search"
type="search"
placeholder="Search settings"
bind:value={query}
disabled={saving}
oninput={() => (reviewInvalid = false)}
/></Field
>
<Field class="mobile-section" label="Section">
<Select
aria-label="Settings section"
value={selectedGroup}
disabled={saving}
@@ -177,8 +180,8 @@
? ` · ${changed.filter((key) => group.keys.includes(key)).length} changed`
: ''}</option
>{/each}
</select>
</label>
</Select>
</Field>
<nav class="group-links" aria-label="Settings sections">
{#each settingsGroups as group}
{@const count = changed.filter((key) => group.keys.includes(key)).length}
@@ -263,30 +266,37 @@
>
<td class="value-cell">
<div class="numeric-value">
{#if unlimited}<input
id={`setting-${key}`}
type="number"
disabled
placeholder="—"
aria-describedby={`setting-${key}-help setting-${key}-default`}
/>
{:else}<input
id={`setting-${key}`}
type="number"
min={Math.max(1, settingMinimum(key))}
max={key.endsWith('Days')
? 36500
: undefined}
step="1"
required
bind:value={draft[key]}
aria-invalid={!!validation}
aria-describedby={`setting-${key}-help setting-${key}-default${validation ? ` setting-${key}-error` : ''}`}
oninput={() => {
error = '';
notice = '';
}}
/>{/if}
{#if unlimited}<Field
><Input
id={`setting-${key}`}
type="number"
disabled
placeholder="—"
aria-describedby={`setting-${key}-help setting-${key}-default`}
/></Field
>
{:else}<Field
><Input
id={`setting-${key}`}
type="number"
min={Math.max(
1,
settingMinimum(key)
)}
max={key.endsWith('Days')
? 36500
: undefined}
step="1"
required
bind:value={draft[key]}
aria-invalid={!!validation}
aria-describedby={`setting-${key}-help setting-${key}-default${validation ? ` setting-${key}-error` : ''}`}
oninput={() => {
error = '';
notice = '';
}}
/></Field
>{/if}
<span class="unit">{settingUnit(key)}</span>
</div>
{#if zeroMode}<label
@@ -393,13 +403,7 @@
gap: var(--space-2);
min-width: 0;
}
.search-label {
font-size: var(--text-sm);
color: var(--text-secondary);
}
#settings-search {
width: 100%;
}
.group-links {
display: grid;
gap: var(--space-1);
@@ -521,17 +525,15 @@
flex-wrap: wrap;
gap: var(--space-1);
}
.numeric-value input {
width: 88px;
min-height: 32px;
padding: 5px var(--space-2);
font-family: var(--font-numeric);
font-variant-numeric: tabular-nums;
}
input:disabled {
color: var(--text-tertiary);
opacity: 0.75;
}
.numeric-value > :global(.field) {
width: 100px;
flex: 0 0 auto;
}
.unit {
color: var(--text-secondary);
font-size: var(--text-xs);
@@ -646,7 +648,7 @@
.notice {
color: var(--state-online);
}
.mobile-section,
.section-rail :global(.mobile-section),
.mobile-label {
display: none;
}
@@ -680,7 +682,7 @@
.group-links {
display: none;
}
.mobile-section {
.section-rail :global(.mobile-section) {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
@@ -688,9 +690,6 @@
font-size: var(--text-sm);
color: var(--text-secondary);
}
.mobile-section select {
width: 100%;
}
.table-wrap {
overflow: visible;
}