improved counter logic

This commit is contained in:
Oliver Bryan
2026-01-10 18:13:12 +00:00
parent dcc7b4d0a8
commit 6d67d6a7b3
2 changed files with 23 additions and 33 deletions

View File

@@ -14,6 +14,7 @@ export function Field({
tabIndex, tabIndex,
spellcheck, spellcheck,
maxLength, maxLength,
showCounter = true,
}: { }: {
label: string; label: string;
value?: string; value?: string;
@@ -26,6 +27,7 @@ export function Field({
tabIndex?: number; tabIndex?: number;
spellcheck?: boolean; spellcheck?: boolean;
maxLength?: number; maxLength?: number;
showCounter?: boolean;
}) { }) {
const [internalTouched, setInternalTouched] = useState(false); const [internalTouched, setInternalTouched] = useState(false);
const isTouched = submitAttempted || internalTouched; const isTouched = submitAttempted || internalTouched;
@@ -59,6 +61,7 @@ export function Field({
tabIndex={tabIndex} tabIndex={tabIndex}
spellCheck={spellcheck} spellCheck={spellcheck}
maxLength={maxLength} maxLength={maxLength}
showCounter={showCounter}
/> />
<div className="flex items-end justify-end w-full text-xs mb-0 -mt-1"> <div className="flex items-end justify-end w-full text-xs mb-0 -mt-1">
{error || invalidMessage !== "" ? ( {error || invalidMessage !== "" ? (

View File

@@ -2,30 +2,15 @@ import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) { function Input({
className,
type,
showCounter = true,
...props
}: React.ComponentProps<"input"> & { showCounter?: boolean }) {
const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined; const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined;
const currentLength = typeof props.value === "string" ? props.value.length : undefined; const currentLength = typeof props.value === "string" ? props.value.length : undefined;
const showCounter = typeof maxLength === "number" && maxLength > 0 && typeof currentLength === "number";
if (!showCounter) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
)}
{...props}
/>
);
}
const counterPercent = currentLength / maxLength;
return ( return (
<div <div
className={cn( className={cn(
@@ -48,18 +33,20 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
)} )}
{...props} {...props}
/> />
<span {showCounter && currentLength !== undefined && maxLength !== undefined && (
className={cn( <span
"px-2 text-[11px] tabular-nums", className={cn(
counterPercent >= 1 "border-l px-2 h-full flex items-center justify-center text-[11px] tabular-nums",
? "text-destructive" currentLength / maxLength >= 1
: counterPercent >= 0.8 ? "text-destructive"
? "text-amber-500" : currentLength / maxLength >= 0.75
: "text-muted-foreground", ? "text-amber-500"
)} : "text-muted-foreground",
> )}
{currentLength}/{maxLength} >
</span> {currentLength}/{maxLength}
</span>
)}
</div> </div>
); );
} }