ข้ามไปเนื้อหา
WhereUI

ค้นหาในเอกสาร

ค้นหาชื่อ component หรือหน้าเอกสาร

Input OTP

จาก shadcn

ช่องกรอกรหัสยืนยันแบบทีละหลัก พร้อมตัวคั่นกลุ่มตัวเลข เช่น รหัส 6 หลักจาก SMS ก่อนเข้าถึงคำขอกู้

รหัสหมดอายุใน 5 นาที ส่งถึงมือถือที่ลงทะเบียนไว้

4
2
1

ติดตั้ง

Terminal
pnpm dlx shadcn@latest add @whereui/input-otp

ต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก

การใช้งาน

import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"<InputOTP maxLength={6}>  <InputOTPGroup>    <InputOTPSlot index={0} />    <InputOTPSlot index={1} />    <InputOTPSlot index={2} />  </InputOTPGroup></InputOTP>

ข้อควรรู้

  • maxLength ต้องเท่ากับจำนวน InputOTPSlot ทั้งหมด และ index เรียงต่อเนื่องจาก 0
  • วาง InputOTPSeparator ระหว่างกลุ่มเพื่อแยกหลัก เช่น 3-3 สำหรับรหัส 6 หลัก
  • รองรับวางรหัสทั้งกลุ่มจากคลิปบอร์ดได้ทันที

ซอร์สโค้ด

components/ui/input-otp.tsx
"use client"import * as React from "react"import { cn } from "cn"import { OTPInput, OTPInputContext } from "input-otp"import { MinusIcon } from "lucide-react"function InputOTP({  className,  containerClassName,  ...props}: React.ComponentProps<typeof OTPInput> & {  containerClassName?: string}) {  return (    <OTPInput      data-slot="input-otp"      containerClassName={cn(        "cn-input-otp flex items-center has-disabled:opacity-50",        containerClassName      )}      spellCheck={false}      className={cn("disabled:cursor-not-allowed", className)}      {...props}    />  )}function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="input-otp-group"      className={cn(        "flex items-center rounded-lg has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40",        className      )}      {...props}    />  )}function InputOTPSlot({  index,  className,  ...props}: React.ComponentProps<"div"> & {  index: number}) {  const inputOTPContext = React.useContext(OTPInputContext)  const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}  return (    <div      data-slot="input-otp-slot"      data-active={isActive}      className={cn(        "relative flex size-8 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-lg first:border-l last:rounded-r-lg aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-3 data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",        className      )}      {...props}    >      {char}      {hasFakeCaret && (        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">          <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />        </div>      )}    </div>  )}function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="input-otp-separator"      className="flex items-center [&_svg:not([class*='size-'])]:size-4"      role="separator"      {...props}    >      <MinusIcon      />    </div>  )}export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }