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.description} +
) } ++ {p.error ?? ""} +
{p.description}
+ p.resendEnabled && ( +- {p.error ?? ""} -