feat(forms): wire keyboard hints and aria attributes across settings and admin forms

Bring the same `inputMode`, `enterKeyHint`, `autoComplete`, and
`spellCheck` defaults to the settings and admin surfaces:

- Account: profile email gets `autoComplete="email"` +
  `enterKeyHint="next"`, the disabled username carries
  `autoComplete="username"` so the password manager can still bind
  the credential, and the height field hints the decimal keyboard.
- Telegram card: bot token and chat ID both opt out of autofill,
  disable spell-check + autocapitalize, and lead the chained next →
  done keyboard order.
- Integrations: Withings client-id / secret and the moodLog URL +
  API key share the off-spell + no-capitalize secret-input contract
  and chain into the test / save action via `enterKeyHint`.
- Notifications anchors: each card wrapper carries `scroll-mt-28`
  so direct anchor navigation to `#telegram`, `#ntfy`, `#web-push`
  lands the section header below the safe-area sticky header
  instead of under it.
- Thresholds editor: min/max numeric inputs derive `inputMode`
  ("numeric" for integer ACTIVITY_STEPS, "decimal" elsewhere) and
  chain next → done.
- Admin reminders: the late-window and missed-window minute inputs
  pin `inputMode="numeric"` and the next → done keyboard order.
This commit is contained in:
Marc-André Bombeck committed 2026-05-15 18:36:33 +02:00
1 parent 48261b6797
commit 192170ee5f
6 files changed
+55 -4

No files matched your search

@@ -132,6 +132,8 @@ export function RemindersSection() {
<Input
id="admin-reminder-late"
type="number"
inputMode="numeric"
enterKeyHint="next"
min={15}
max={480}
value={reminderLateDraft ?? settings?.reminderLateMinutes ?? 120}
@@ -153,6 +155,8 @@ export function RemindersSection() {
<Input
id="admin-reminder-missed"
type="number"
inputMode="numeric"
enterKeyHint="done"
min={30}
max={720}
value={
+10 -1
View File
@@ -355,7 +355,12 @@ export function AccountSection() {
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="username">{t("settings.username")}</Label>
<Input id="username" value={user.username} disabled />
<Input
id="username"
value={user.username}
disabled
autoComplete="username"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">{t("auth.email")}</Label>
@@ -366,6 +371,8 @@ export function AccountSection() {
onChange={(e) => setEmail(e.target.value)}
placeholder={t("auth.emailPlaceholder")}
maxLength={320}
autoComplete="email"
enterKeyHint="next"
/>
</div>
</div>
@@ -392,6 +399,8 @@ export function AccountSection() {
<Input
id="height"
type="number"
inputMode="decimal"
enterKeyHint="next"
value={heightCm}
onChange={(e) => setHeightCm(e.target.value)}
placeholder="175"
@@ -404,6 +404,11 @@ function WithingsCard({
: t("settings.withingsClientId")
}
maxLength={200}
autoComplete="off"
inputMode="text"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="next"
/>
</div>
<div className="space-y-1.5">
@@ -420,6 +425,11 @@ function WithingsCard({
: t("settings.withingsClientSecret")
}
maxLength={200}
autoComplete="off"
inputMode="text"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="done"
/>
</div>
<div className="space-y-1.5">
@@ -703,6 +713,10 @@ function MoodLogCard({
placeholder={t("settings.moodLogUrlPlaceholder")}
value={url}
onChange={(e) => setUrl(e.target.value)}
autoComplete="off"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="next"
/>
</div>
<div>
@@ -718,6 +732,11 @@ function MoodLogCard({
}
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
autoComplete="off"
inputMode="text"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="done"
/>
</div>
<div className="flex flex-wrap items-start gap-2">
@@ -54,18 +54,23 @@ export function NotificationsSection() {
<NotificationStatusCard />
{/* v1.4.27 MB3 — `scroll-mt-28` keeps each notification card's
anchor target below the safe-area sticky page header when the
user lands on `/settings/notifications#telegram` or similar.
Without the offset the section title pins under the header on
mobile and the user has to scroll back up to find it. */}
{showTelegram && (
<div id="telegram">
<div id="telegram" className="scroll-mt-28">
<TelegramCard isAuthenticated={isAuthenticated} />
</div>
)}
{showNtfy && (
<div id="ntfy">
<div id="ntfy" className="scroll-mt-28">
<NtfyCard isAuthenticated={isAuthenticated} />
</div>
)}
{showWebPush && (
<div id="web-push">
<div id="web-push" className="scroll-mt-28">
<WebPushCard />
</div>
)}
+10
View File
@@ -148,6 +148,11 @@ export function TelegramCard({
: "123456:ABC-DEF..."
}
maxLength={100}
autoComplete="off"
inputMode="text"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="next"
/>
</div>
<div className="space-y-2">
@@ -158,6 +163,11 @@ export function TelegramCard({
onChange={(e) => setChatId(e.target.value)}
placeholder="123456789"
maxLength={50}
autoComplete="off"
inputMode="text"
spellCheck={false}
autoCapitalize="none"
enterKeyHint="done"
/>
</div>
</div>
@@ -282,6 +282,8 @@ function MetricRow({
<Input
id={`min-${metric}`}
type="number"
inputMode={metric === "ACTIVITY_STEPS" ? "numeric" : "decimal"}
enterKeyHint="next"
step={metric === "ACTIVITY_STEPS" ? 100 : 0.1}
min={bounds.min}
max={bounds.max}
@@ -298,6 +300,8 @@ function MetricRow({
<Input
id={`max-${metric}`}
type="number"
inputMode={metric === "ACTIVITY_STEPS" ? "numeric" : "decimal"}
enterKeyHint="done"
step={metric === "ACTIVITY_STEPS" ? 100 : 0.1}
min={bounds.min}
max={bounds.max}