mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
feat(ui): derive Input inputMode default from the type prop
Surface the right on-screen keyboard on iOS Safari and Android Chrome without forcing every call site to repeat the attribute. The primitive maps `type="number"` to `inputMode="decimal"` (most numeric fields in HealthLog accept decimals — kg, mmol/L, sleep hours), `type="email"` to `"email"`, `type="url"` to `"url"`, `type="tel"` to `"tel"`, and `type="search"` to `"search"`. Integer-only call sites (steps, doses per pen, reminder minutes) still opt into `inputMode="numeric"` explicitly and the override wins. Cover the derivation with seven additional unit tests on top of the three existing autoComplete contracts; the suite now pins every mapping plus the override path.
This commit is contained in:
1 parent
bd7cb938c9
commit
9036e71516
2 files changed
+70
No files matched your search
@@ -28,4 +28,44 @@ describe("<Input>", () => {
|
||||
expect(html).not.toContain("lpignore");
|
||||
expect(html).not.toContain("1p-ignore");
|
||||
});
|
||||
|
||||
it("derives inputMode=decimal from type=number", () => {
|
||||
const html = renderToStaticMarkup(<Input id="weight" type="number" />);
|
||||
expect(html).toContain('inputMode="decimal"');
|
||||
});
|
||||
|
||||
it("derives inputMode=email from type=email", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<Input id="email" type="email" autoComplete="email" />,
|
||||
);
|
||||
expect(html).toContain('inputMode="email"');
|
||||
});
|
||||
|
||||
it("derives inputMode=url from type=url", () => {
|
||||
const html = renderToStaticMarkup(<Input id="webhook" type="url" />);
|
||||
expect(html).toContain('inputMode="url"');
|
||||
});
|
||||
|
||||
it("derives inputMode=tel from type=tel", () => {
|
||||
const html = renderToStaticMarkup(<Input id="phone" type="tel" />);
|
||||
expect(html).toContain('inputMode="tel"');
|
||||
});
|
||||
|
||||
it("derives inputMode=search from type=search", () => {
|
||||
const html = renderToStaticMarkup(<Input id="filter" type="search" />);
|
||||
expect(html).toContain('inputMode="search"');
|
||||
});
|
||||
|
||||
it("omits inputMode for plain text inputs", () => {
|
||||
const html = renderToStaticMarkup(<Input id="note" type="text" />);
|
||||
expect(html).not.toContain("inputMode=");
|
||||
});
|
||||
|
||||
it("respects an explicit inputMode override on type=number", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<Input id="steps" type="number" inputMode="numeric" />,
|
||||
);
|
||||
expect(html).toContain('inputMode="numeric"');
|
||||
expect(html).not.toContain('inputMode="decimal"');
|
||||
});
|
||||
});
|
||||
@@ -19,21 +19,51 @@ import { cn } from "@/lib/utils";
|
||||
* `autoComplete` is anything other than `"off"`, the LastPass /
|
||||
* 1Password ignore attrs are dropped so password managers fill the
|
||||
* field normally.
|
||||
*
|
||||
* v1.4.27 mobile pass: derive a sensible `inputMode` default from the
|
||||
* `type` prop so every numeric / email / tel / url / search field
|
||||
* surfaces the right on-screen keyboard on iOS Safari and Android
|
||||
* Chrome without each call site having to repeat the attribute. Most
|
||||
* `type="number"` fields in HealthLog accept decimals (kg, mmol/L,
|
||||
* sleep hours), so the default is `"decimal"`; integer-only call
|
||||
* sites can still pass `inputMode="numeric"` explicitly.
|
||||
*/
|
||||
function deriveInputMode(
|
||||
type: React.HTMLInputTypeAttribute | undefined,
|
||||
): React.HTMLAttributes<HTMLInputElement>["inputMode"] | undefined {
|
||||
switch (type) {
|
||||
case "number":
|
||||
return "decimal";
|
||||
case "tel":
|
||||
return "tel";
|
||||
case "email":
|
||||
return "email";
|
||||
case "url":
|
||||
return "url";
|
||||
case "search":
|
||||
return "search";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function Input({
|
||||
className,
|
||||
type,
|
||||
autoComplete,
|
||||
inputMode,
|
||||
...props
|
||||
}: React.ComponentProps<"input">) {
|
||||
const resolvedAutoComplete = autoComplete ?? "off";
|
||||
const skipAutofill = resolvedAutoComplete === "off";
|
||||
const resolvedInputMode = inputMode ?? deriveInputMode(type);
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
autoComplete={resolvedAutoComplete}
|
||||
inputMode={resolvedInputMode}
|
||||
data-lpignore={skipAutofill ? "true" : undefined}
|
||||
data-1p-ignore={skipAutofill ? "true" : undefined}
|
||||
className={cn(
|
||||
|
||||
Reference in new issue
Block a user