From 4341e6bcdffaa713b645ecb9f6f3da101f8c914f Mon Sep 17 00:00:00 2001 From: Oliver Bryan <04oliverbryan@gmail.com> Date: Sat, 10 Jan 2026 18:01:05 +0000 Subject: [PATCH] length indicator on maxLength fields --- packages/frontend/src/components/ui/field.tsx | 3 + packages/frontend/src/components/ui/input.tsx | 60 ++++++++++++++++--- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/components/ui/field.tsx b/packages/frontend/src/components/ui/field.tsx index b51e75a..588c9b2 100644 --- a/packages/frontend/src/components/ui/field.tsx +++ b/packages/frontend/src/components/ui/field.tsx @@ -13,6 +13,7 @@ export function Field({ error, tabIndex, spellcheck, + maxLength, }: { label: string; value?: string; @@ -24,6 +25,7 @@ export function Field({ error?: string; tabIndex?: number; spellcheck?: boolean; + maxLength?: number; }) { const [internalTouched, setInternalTouched] = useState(false); const isTouched = submitAttempted || internalTouched; @@ -56,6 +58,7 @@ export function Field({ type={hidden ? "password" : "text"} tabIndex={tabIndex} spellCheck={spellcheck} + maxLength={maxLength} />
{error || invalidMessage !== "" ? ( diff --git a/packages/frontend/src/components/ui/input.tsx b/packages/frontend/src/components/ui/input.tsx index 983aff6..997e2ab 100644 --- a/packages/frontend/src/components/ui/input.tsx +++ b/packages/frontend/src/components/ui/input.tsx @@ -3,18 +3,64 @@ import type * as React from "react"; import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { + const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined; + const currentLength = typeof props.value === "string" ? props.value.length : undefined; + + const showCounter = typeof maxLength === "number" && maxLength > 0 && typeof currentLength === "number"; + + if (!showCounter) { + return ( + + ); + } + + const counterPercent = currentLength / maxLength; + return ( - + > + + = 1 + ? "text-destructive" + : counterPercent >= 0.8 + ? "text-amber-500" + : "text-muted-foreground", + )} + > + {currentLength}/{maxLength} + +
); }