diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-06-22 10:02:50 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-06-22 10:02:50 +0200 |
| commit | 87a83a5604b20550430ef4eefbcd573d309694e2 (patch) | |
| tree | 7ed86f00b8414cb6543f5c5ac98ea6e9abd2e10f /apps/web/src/components/ui/input-otp.tsx | |
| download | opencut-87a83a5604b20550430ef4eefbcd573d309694e2.tar.gz | |
Initial commit
Diffstat (limited to 'apps/web/src/components/ui/input-otp.tsx')
| -rw-r--r-- | apps/web/src/components/ui/input-otp.tsx | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/apps/web/src/components/ui/input-otp.tsx b/apps/web/src/components/ui/input-otp.tsx new file mode 100644 index 00000000..1bffc451 --- /dev/null +++ b/apps/web/src/components/ui/input-otp.tsx @@ -0,0 +1,75 @@ +"use client"; + +import * as React from "react"; +import { OTPInput, OTPInputContext } from "input-otp"; +import { Minus } from "lucide-react"; + +import { cn } from "../../lib/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 justify-between gap-2 w-full has-[:disabled]:opacity-50", + containerClassName + )} + className={cn("disabled: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("flex items-center flex-1 gap-2", 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( + "relative flex aspect-square flex-1 min-w-[36px] items-center justify-center border border-input text-lg shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md", + isActive && "z-10 ring-1 ring-ring", + 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> + ); +}); +InputOTPSlot.displayName = "InputOTPSlot"; + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( + <div ref={ref} role="separator" {...props}> + <Minus /> + </div> +)); +InputOTPSeparator.displayName = "InputOTPSeparator"; + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; |
