shadimage

Input Otp

One-time password input component with customizable slots

registry/preskok/ui/preskok-ui/input-otp.tsx

"use client"

import React, { use } from "react"
import { twMerge } from "cn"
import { OTPInput, OTPInputContext } from "input-otp"
import { DotIcon } from "lucide-react"

type InputOTOPProps = React.ComponentProps<typeof OTPInput>
const InputOTP = ({
  className,
  autoFocus = true,
  containerClassName,
  ref,
  ...props
}: InputOTOPProps) => (
  <OTPInput
    data-1p-ignore
    ref={ref}
    autoFocus={autoFocus}
    containerClassName={twMerge(
      "flex items-center gap-2 has-disabled:opacity-50",
      containerClassName
    )}
    className={twMerge(
      "bg-destructive mt-auto h-[2.5rem] disabled:cursor-not-allowed",
      className
    )}
    {...props}
  />
)

type InputOTPGroupProps = React.ComponentProps<"div">
const InputOTPGroup = ({ className, ref, ...props }: InputOTPGroupProps) => (
  <div
    ref={ref}
    className={twMerge("flex items-center gap-x-1.5", className)}
    {...props}
  />
)

interface InputOTPSlotProps extends React.ComponentProps<"div"> {
  index: number
}

const InputOTPSlot = ({
  index,
  className,
  ref,
  ...props
}: InputOTPSlotProps) => {
  const inputOTPContext = use(OTPInputContext)
  const slot = inputOTPContext.slots[index] as
    | (typeof inputOTPContext.slots)[number]
    | undefined

  if (!slot) {
    throw new Error("Slot not found")
  }

  const { char, hasFakeCaret, isActive } = slot

  return (
    <div
      ref={ref}
      className={twMerge(
        "border-input relative flex size-10 items-center justify-center rounded-md border text-sm tabular-nums transition-all",
        isActive && "border-ring/70 ring-ring/20 z-10 ring-3",
        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>
  )
}

type InputOTPSeparatorProps = React.ComponentProps<"div">
const InputOTPSeparator = ({ ref, ...props }: InputOTPSeparatorProps) => (
  <div ref={ref} {...props}>
    <DotIcon className="size-2" />
  </div>
)

InputOTP.Group = InputOTPGroup
InputOTP.Slot = InputOTPSlot
InputOTP.Separator = InputOTPSeparator

export { InputOTP }
export type {
  InputOTOPProps,
  InputOTPGroupProps,
  InputOTPSeparatorProps,
  InputOTPSlotProps,
}

No selection tags yet — empty means no evidence, not lack of support.

DependenciesBundle weight · light

icons
lucide-react