From f36d77fba681c4ed4aa939c50c703c0aff9bcff6 Mon Sep 17 00:00:00 2001 From: aidanCQ Date: Wed, 11 Dec 2024 13:12:01 +0000 Subject: [PATCH] Add input otp. --- package-lock.json | 10 ++++ package.json | 1 + src/index.ts | 1 + src/shadcn/ui/input-otp.tsx | 71 ++++++++++++++++++++++++++++ stories/shadcn/input-otp.stories.tsx | 39 +++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 src/shadcn/ui/input-otp.tsx create mode 100644 stories/shadcn/input-otp.stories.tsx diff --git a/package-lock.json b/package-lock.json index af72cf4..dc6c9b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "clsx": "^2.0.0", "cmdk": "^0.2.0", "date-fns": "^3.6.0", + "input-otp": "^1.4.1", "lucide-react": "^0.298.0", "react-day-picker": "^8.10.0", "react-icons": "^5.3.0", @@ -13191,6 +13192,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/input-otp": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz", + "integrity": "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/internal-slot": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", diff --git a/package.json b/package.json index c9e0992..8a1873a 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "clsx": "^2.0.0", "cmdk": "^0.2.0", "date-fns": "^3.6.0", + "input-otp": "^1.4.1", "lucide-react": "^0.298.0", "react-day-picker": "^8.10.0", "react-icons": "^5.3.0", diff --git a/src/index.ts b/src/index.ts index ae7d438..95f40e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export * from "./shadcn/ui/dropdown-menu"; export * from "./shadcn/ui/form"; export * from "./shadcn/ui/hover-card"; export * from "./shadcn/ui/input"; +export * from "./shadcn/ui/input-otp"; export * from "./shadcn/ui/label"; export * from "./shadcn/ui/menubar"; export * from "./shadcn/ui/multi-select"; diff --git a/src/shadcn/ui/input-otp.tsx b/src/shadcn/ui/input-otp.tsx new file mode 100644 index 0000000..c37a6a3 --- /dev/null +++ b/src/shadcn/ui/input-otp.tsx @@ -0,0 +1,71 @@ +"use client" + +import * as React from "react" +import { OTPInput, OTPInputContext } from "input-otp" +import { Minus } from "lucide-react" + +import { cn } from "src" + +const InputOTP = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)) +InputOTP.displayName = "InputOTP" + +const InputOTPGroup = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => ( +
+)) +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 ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ) +}) +InputOTPSlot.displayName = "InputOTPSlot" + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( +
+ +
+)) +InputOTPSeparator.displayName = "InputOTPSeparator" + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } diff --git a/stories/shadcn/input-otp.stories.tsx b/stories/shadcn/input-otp.stories.tsx new file mode 100644 index 0000000..e8e308f --- /dev/null +++ b/stories/shadcn/input-otp.stories.tsx @@ -0,0 +1,39 @@ +import { + InputOTP, + InputOTPGroup, + InputOTPSeparator, + InputOTPSlot, + } from "src" + + import type { Meta, StoryObj } from "@storybook/react"; + + + +const InputOTPDemo = () => { + return ( + + + + + + + + + + + + + + + ); +}; +const meta: Meta = { + component: InputOTPDemo, + +}; + +export default meta; + +export const Default: StoryObj = { + args: {}, +};