fix otp
This commit is contained in:
parent
1453a545ab
commit
42eace5e4f
|
|
@ -2,6 +2,8 @@ import { useLocal } from "lib/utils/use-local";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { FieldLocal, FieldProp, FMLocal } from "../../typings";
|
import { FieldLocal, FieldProp, FMLocal } from "../../typings";
|
||||||
import { PropTypeInput } from "./TypeInput";
|
import { PropTypeInput } from "./TypeInput";
|
||||||
|
import { InputOTP, InputOTPGroup, InputOTPSlot } from "lib/comps/ui/input-otp";
|
||||||
|
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
||||||
|
|
||||||
export const FieldOTP: FC<{
|
export const FieldOTP: FC<{
|
||||||
digit: number;
|
digit: number;
|
||||||
|
|
@ -11,81 +13,44 @@ export const FieldOTP: FC<{
|
||||||
arg: FieldProp;
|
arg: FieldProp;
|
||||||
}> = ({ digit, fm, field }) => {
|
}> = ({ digit, fm, field }) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
otp: [] as string[],
|
otp: "",
|
||||||
ref: [] as HTMLInputElement[],
|
ref: [] as HTMLInputElement[],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (local.otp.length === 0 && digit) {
|
|
||||||
for (let i = 0; i < digit; i++) {
|
|
||||||
local.otp.push("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="c-flex-1 c-flex c-justify-center c-items-center">
|
<div
|
||||||
{local.otp.map((item, idx) => (
|
|
||||||
<input
|
|
||||||
key={idx}
|
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-rounded-md c-text-center",
|
"c-flex-1 c-flex c-justify-center c-items-center",
|
||||||
css`
|
css`
|
||||||
margin: 3px;
|
|
||||||
font-size: 3em;
|
|
||||||
padding: 0px 10px;
|
|
||||||
width: 60px;
|
|
||||||
height: 100px;
|
height: 100px;
|
||||||
border: 1px solid #ddd;
|
.otp-single {
|
||||||
background: white;
|
height: 80px;
|
||||||
|
width: 50px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
<InputOTP
|
||||||
|
maxLength={4}
|
||||||
|
value={local.otp}
|
||||||
|
onChange={(value) => {
|
||||||
|
local.otp = value;
|
||||||
|
local.render();
|
||||||
|
if (field.on_change) {
|
||||||
|
field.on_change({ value, name: field.name, fm });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
pattern={REGEXP_ONLY_DIGITS}
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
pattern="[0-9]*"
|
>
|
||||||
value={item}
|
<InputOTPGroup>
|
||||||
ref={(ref) => {
|
<InputOTPSlot index={0} className="otp-single" />
|
||||||
if (ref) local.ref[idx] = ref;
|
<InputOTPSlot index={1} className="otp-single" />
|
||||||
}}
|
<InputOTPSlot index={2} className="otp-single" />
|
||||||
onPaste={(e) => {
|
<InputOTPSlot index={3} className="otp-single" />
|
||||||
e.preventDefault();
|
</InputOTPGroup>
|
||||||
|
</InputOTP>
|
||||||
var clipboardData =
|
|
||||||
e.clipboardData || (window as any).clipboardData;
|
|
||||||
var pastedData = clipboardData.getData("text");
|
|
||||||
for (let i = 0; i < pastedData.length; i++) {
|
|
||||||
if (i >= local.otp.length) break;
|
|
||||||
local.otp[i] = pastedData[i];
|
|
||||||
}
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
onKeyDown={async (e) => {
|
|
||||||
if (e.key === "Backspace") {
|
|
||||||
let _idx = idx;
|
|
||||||
if (local.otp[_idx].length === 0) {
|
|
||||||
_idx--;
|
|
||||||
}
|
|
||||||
local.otp[_idx] = "";
|
|
||||||
local.render();
|
|
||||||
const ref = local.ref[Math.max(0, _idx - 1)];
|
|
||||||
if (ref) {
|
|
||||||
ref.focus();
|
|
||||||
}
|
|
||||||
} else if (parseInt(e.key) || e.key === "0") {
|
|
||||||
local.otp[idx] = e.key;
|
|
||||||
local.render();
|
|
||||||
const ref = local.ref[idx + 1];
|
|
||||||
if (ref) {
|
|
||||||
ref.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const otp = local.otp.join("");
|
|
||||||
fm.data[field.name] = otp;
|
|
||||||
if (otp.length === digit) {
|
|
||||||
fm.render();
|
|
||||||
}
|
|
||||||
// local.render();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { OTPInput, OTPInputContext } from "input-otp"
|
||||||
|
import { Dot } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/utils"
|
||||||
|
|
||||||
|
const InputOTP = React.forwardRef<
|
||||||
|
React.ElementRef<typeof OTPInput>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||||
|
>(({ className, containerClassName, ...props }, ref) => (
|
||||||
|
<OTPInput
|
||||||
|
ref={ref}
|
||||||
|
containerClassName={cn(
|
||||||
|
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
||||||
|
containerClassName
|
||||||
|
)}
|
||||||
|
className={cn("disabled:c-cursor-not-allowed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
InputOTP.displayName = "InputOTP"
|
||||||
|
|
||||||
|
const InputOTPGroup = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("c-flex c-items-center", className)} {...props} />
|
||||||
|
))
|
||||||
|
InputOTPGroup.displayName = "InputOTPGroup"
|
||||||
|
|
||||||
|
const InputOTPSlot = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div"> & { index: number }
|
||||||
|
>(({ index, className, ...props }, ref) => {
|
||||||
|
const inputOTPContext = React.useContext(OTPInputContext)
|
||||||
|
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"c-relative c-flex c-h-10 c-w-10 c-items-center c-justify-center c-border-y c-border-r c-border-input c-text-sm c-transition-all first:c-rounded-l-md first:c-border-l last:c-rounded-r-md",
|
||||||
|
isActive && "c-z-10 c-ring-2 c-ring-ring c-ring-offset-background",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
{hasFakeCaret && (
|
||||||
|
<div className="c-pointer-events-none c-absolute c-inset-0 c-flex c-items-center c-justify-center">
|
||||||
|
<div className="c-h-4 c-w-px c-animate-caret-blink c-bg-foreground c-duration-1000" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
InputOTPSlot.displayName = "InputOTPSlot"
|
||||||
|
|
||||||
|
const InputOTPSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ ...props }, ref) => (
|
||||||
|
<div ref={ref} role="separator" {...props}>
|
||||||
|
<Dot />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
InputOTPSeparator.displayName = "InputOTPSeparator"
|
||||||
|
|
||||||
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
||||||
Loading…
Reference in New Issue