damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs | README | LICENSE

commit 05dc2565972ada733a8d57102d551739d0f10a18
parent 639e92f19de6e3efa3fa782637632d2e0f45d865
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Mon,  1 Apr 2024 14:21:27 +0000

Setup OTP input element

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mpackage-lock.json | 10++++++++++
Mpackage.json | 1+
Asrc/components/ui/InputOTP.tsx | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtailwind.config.ts | 5+++++
4 files changed, 105 insertions(+), 0 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -16,6 +16,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "framer-motion": "^10.16.4", + "input-otp": "^1.2.3", "lnmessage": "^0.2.7", "lucide-react": "^0.287.0", "next": "13.5.4", @@ -3163,6 +3164,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/input-otp": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.3.tgz", + "integrity": "sha512-pxYvgnihL9KAdpcShX2+iKctdMRbDs36bIqd8uIsN3e5vv9VjMv2bhO3S5Bl1PjcDPsA/OXZe5R71n8oVtucfQ==", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", diff --git a/package.json b/package.json @@ -22,6 +22,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "framer-motion": "^10.16.4", + "input-otp": "^1.2.3", "lnmessage": "^0.2.7", "lucide-react": "^0.287.0", "next": "13.5.4", diff --git a/src/components/ui/InputOTP.tsx b/src/components/ui/InputOTP.tsx @@ -0,0 +1,89 @@ +"use client" + +import * as React from "react" +import { OTPInput, OTPInputContext } from "input-otp" +import { Dot } 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 gap-2 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", 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 h-10 w-10 items-center justify-center border-y border-r border-purple-200/50 text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md text-white", + isActive && "z-10 ring-2 ring-ring ring-offset-background", + 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-white 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 className="text-purple-200/50" /> + </div> +)) +InputOTPSeparator.displayName = "InputOTPSeparator" + +function InputOTP6Digits({ ...props }) { + return ( + <InputOTP maxLength={6} {...props}> + <InputOTPGroup> + <InputOTPSlot index={0} /> + <InputOTPSlot index={1} /> + <InputOTPSlot index={2} /> + </InputOTPGroup> + <InputOTPSeparator /> + <InputOTPGroup> + <InputOTPSlot index={3} /> + <InputOTPSlot index={4} /> + <InputOTPSlot index={5} /> + </InputOTPGroup> + </InputOTP> + ) +} + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator, InputOTP6Digits } diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -23,10 +23,15 @@ const config: Config = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, + "caret-blink": { + "0%,70%,100%": { opacity: "1" }, + "20%,50%": { opacity: "0" }, + }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + "caret-blink": "caret-blink 1.25s ease-out infinite", }, }, colors: {