nextuiq
Version:
NextUIQ is a modern, lightweight, and developer-friendly UI component library for React and Next.js. Built with TypeScript and Tailwind CSS, it offers customizable, accessible, and performance-optimized components with built-in dark mode, theme customizat
130 lines (127 loc) • 4.85 kB
JavaScript
import { j as jsxRuntimeExports } from './index46.mjs';
import { forwardRef, useState, useRef } from 'react';
const OTPInput = forwardRef(({
length = 6,
value = "",
onChange,
disabled = false,
error = false,
hint,
className = "",
label = "Verification code",
description = "Enter the verification code",
inputType = "numeric",
...props
}, ref) => {
const [otp, setOTP] = useState(value.split(""));
const inputRefs = useRef([]);
const groupId = useRef(`otp-${Math.random().toString(36).slice(2, 11)}`);
const handleChange = (index, value2) => {
const validationPatterns = {
numeric: /^\d*$/,
alphanumeric: /^[a-zA-Z0-9]*$/,
any: /^.*$/
};
if (!validationPatterns[inputType].test(value2)) return;
const newOTP = [...otp];
newOTP[index] = value2;
setOTP(newOTP);
onChange?.(newOTP.join(""));
if (value2 && index < length - 1) {
inputRefs.current[index + 1]?.focus();
}
};
const handlePaste = (e) => {
e.preventDefault();
const pastedData = e.clipboardData.getData("text").slice(0, length);
const validationPatterns = {
numeric: /^\d*$/,
alphanumeric: /^[a-zA-Z0-9]*$/,
any: /^.*$/
};
if (!validationPatterns[inputType].test(pastedData)) return;
const newOTP = [...otp];
pastedData.split("").forEach((char, index) => {
newOTP[index] = char;
if (inputRefs.current[index]) {
inputRefs.current[index].value = char;
}
});
setOTP(newOTP);
onChange?.(newOTP.join(""));
inputRefs.current[Math.min(pastedData.length, length) - 1]?.focus();
};
const handleKeyDown = (index, e) => {
if (e.key === "Backspace" && !otp[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
} else if (e.key === "ArrowLeft" && index > 0) {
inputRefs.current[index - 1]?.focus();
} else if (e.key === "ArrowRight" && index < length - 1) {
inputRefs.current[index + 1]?.focus();
}
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref, className: "space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
role: "group",
"aria-labelledby": `${groupId.current}-label`,
"aria-describedby": `${groupId.current}-description ${error ? `${groupId.current}-error` : ""}`,
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"label",
{
id: `${groupId.current}-label`,
className: "block text-sm font-medium text-[oklch(var(--theme-foreground))]",
children: label
}
),
description && /* @__PURE__ */ jsxRuntimeExports.jsx(
"p",
{
id: `${groupId.current}-description`,
className: "text-sm text-[oklch(var(--theme-muted-foreground))]",
children: description
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `flex gap-2 ${className}`, children: Array.from({ length }, (_, index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
ref: (el) => {
if (el) inputRefs.current[index] = el;
},
type: "text",
inputMode: inputType === "numeric" ? "numeric" : "text",
pattern: inputType === "numeric" ? "\\d*" : void 0,
maxLength: 1,
value: otp[index] || "",
onChange: (e) => handleChange(index, e.target.value),
onKeyDown: (e) => handleKeyDown(index, e),
onPaste: handlePaste,
disabled,
"aria-label": `Digit ${index + 1} of ${length}`,
...props,
className: `h-12 w-12 text-center rounded-lg border text-lg font-semibold shadow-sm
bg-[oklch(var(--theme-background))]
text-[oklch(var(--theme-foreground))]
focus:outline-none focus:ring-2
${disabled ? "text-[oklch(var(--theme-muted-foreground))] border-[oklch(var(--theme-border))] bg-[oklch(var(--theme-muted))] cursor-not-allowed" : error ? "border-[oklch(var(--theme-destructive))] focus:ring-[oklch(var(--theme-destructive)/0.2)]" : "border-[oklch(var(--theme-border))] focus:border-[oklch(var(--theme-ring))] focus:ring-[oklch(var(--theme-ring)/0.2)]"}`
},
index
)) })
]
}
),
hint && /* @__PURE__ */ jsxRuntimeExports.jsx(
"p",
{
id: `${groupId.current}-error`,
className: `text-xs ${error ? "text-[oklch(var(--theme-destructive))]" : "text-[oklch(var(--theme-muted-foreground))]"}`,
children: hint
}
)
] });
});
OTPInput.displayName = "OTPInput";
export { OTPInput, OTPInput as default };