diff --git a/src/components/OtpField.astro b/src/components/OtpField.astro index 3f78313..87107c4 100644 --- a/src/components/OtpField.astro +++ b/src/components/OtpField.astro @@ -1,21 +1,22 @@ --- import type { HTMLAttributes } from "astro/types"; - -type Size = "sm" | "md" | "lg"; +import { cn } from "../utils/cn"; interface Props extends HTMLAttributes<"div"> { id?: string; - name: string; // hidden input name (what Zod receives) + name: string; label?: string; description?: string; - error?: string; - length?: number; // number of boxes - placeholderChar?: string; // shown in each box - autoFocus?: boolean; + error?: string; // SSR error (optional) + required?: boolean; disabled?: boolean; - size?: Size; - - // Resend (optional) + value?: string; // initial value (e.g., from server) + length?: number; // number of boxes + placeholder?: string | string[]; + onlyDigits?: boolean; // restrict to 0-9 + size?: "sm" | "md" | "lg"; // box sizes + autoFocus?: boolean; + class?: string; resendEnabled?: boolean; // show/hide resend UI resendLabel?: string; // button label resendCooldown?: number; // seconds @@ -25,316 +26,372 @@ interface Props extends HTMLAttributes<"div"> { const p = Astro.props as Props; const fieldId = p.id ?? p.name; +const descId = p.description ? `${fieldId}-desc` : undefined; const errId = `${fieldId}-err`; -const LEN = Math.max(4, Math.min(p.length ?? 6, 10)); -const SIZE = p.size ?? "md"; +const len = Math.max(1, p.length ?? 6); +const initial = (p.value ?? "").slice(0, len); +const boxesSSR = Array.from({ length: len }, (_, i) => initial[i] ?? ""); -const boxCls = - SIZE === "sm" +const sizeCls = + p.size === "sm" ? "wr:h-10 wr:w-10 wr:text-base" - : SIZE === "lg" + : p.size === "lg" ? "wr:h-14 wr:w-14 wr:text-xl" : "wr:h-12 wr:w-12 wr:text-lg"; -const ringErr = "wr:border-danger wr:focus-visible:ring-danger/40"; -const ringOk = "wr:border-border wr:focus-visible:ring-primary/40"; +const boxBase = + "wr:rounded-lg wr:border wr:border-gray-200 wr:bg-background wr:text-center wr:tracking-widest " + + "wr:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 " + + "wr:disabled:opacity-50 wr:disabled:pointer-events-none " + + "wr:dark:bg-neutral-900 wr:dark:border-neutral-700 wr:dark:text-neutral-100"; + +const groupCls = "wr:flex wr:items-center wr:gap-2 wr:select-none"; -const showResend = !!p.resendEnabled; const resendLabel = p.resendLabel ?? "Resend code"; -const resendCooldown = Math.max(5, p.resendCooldown ?? 30); - -const containerId = `otp-${Math.random().toString(36).slice(2)}`; ---
- { - p.label && ( - - ) - } +
+ { + p.label && ( + + ) + } - -
-
+ + + + +
{ - Array.from({ length: LEN }).map((_, i) => ( + boxesSSR.map((ch, i) => ( )) }
{ - p.resendEnabled && ( -
- - -
+ p.description && ( +

+ {p.description} +

) } +
- - - - { - p.description && ( -

{p.description}

+ p.resendEnabled && ( +
+ + +
) } - -
diff --git a/src/pages/otp-form.astro b/src/pages/otp-form.astro index 98fb02f..c7a4eaa 100644 --- a/src/pages/otp-form.astro +++ b/src/pages/otp-form.astro @@ -10,11 +10,11 @@ import ComponentLayout from "../layouts/ComponentLayout.astro"; label="Verification code" description="Enter the 6-digit code we sent to your email." length={6} - placeholderChar="-" + placeholder="-" size="md" autoFocus={true} resendEnabled={true} - resendCooldown={120} + resendCooldown={10} resendLabel="Resend" onResend="submit" />