This commit is contained in:
Rizky 2024-12-01 12:00:31 +00:00
parent 1453a545ab
commit 42eace5e4f
2 changed files with 105 additions and 71 deletions

View File

@ -2,6 +2,8 @@ import { useLocal } from "lib/utils/use-local";
import { FC, useEffect } from "react";
import { FieldLocal, FieldProp, FMLocal } from "../../typings";
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<{
digit: number;
@ -11,81 +13,44 @@ export const FieldOTP: FC<{
arg: FieldProp;
}> = ({ digit, fm, field }) => {
const local = useLocal({
otp: [] as string[],
otp: "",
ref: [] as HTMLInputElement[],
});
if (local.otp.length === 0 && digit) {
for (let i = 0; i < digit; i++) {
local.otp.push("");
}
}
return (
<div className="c-flex-1 c-flex c-justify-center c-items-center">
{local.otp.map((item, idx) => (
<input
key={idx}
className={cx(
"c-rounded-md c-text-center",
css`
margin: 3px;
font-size: 3em;
padding: 0px 10px;
width: 60px;
height: 100px;
border: 1px solid #ddd;
background: white;
`
)}
inputMode="decimal"
pattern="[0-9]*"
value={item}
ref={(ref) => {
if (ref) local.ref[idx] = ref;
}}
onPaste={(e) => {
e.preventDefault();
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
className={cx(
"c-flex-1 c-flex c-justify-center c-items-center",
css`
height: 100px;
.otp-single {
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"
>
<InputOTPGroup>
<InputOTPSlot index={0} className="otp-single" />
<InputOTPSlot index={1} className="otp-single" />
<InputOTPSlot index={2} className="otp-single" />
<InputOTPSlot index={3} className="otp-single" />
</InputOTPGroup>
</InputOTP>
</div>
);
};

69
comps/ui/input-otp.tsx Executable file
View File

@ -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 }