Skip to content

Commit a1a2315

Browse files
authored
Merge pull request #36 from CQCL/shadcn-input-otp
2 parents fc63b33 + f36d77f commit a1a2315

File tree

5 files changed

+122
-0
lines changed

5 files changed

+122
-0
lines changed

package-lock.json

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"clsx": "^2.0.0",
121121
"cmdk": "^0.2.0",
122122
"date-fns": "^3.6.0",
123+
"input-otp": "^1.4.1",
123124
"lucide-react": "^0.298.0",
124125
"react-day-picker": "^8.10.0",
125126
"react-icons": "^5.3.0",

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export * from "./shadcn/ui/dropdown-menu";
1919
export * from "./shadcn/ui/form";
2020
export * from "./shadcn/ui/hover-card";
2121
export * from "./shadcn/ui/input";
22+
export * from "./shadcn/ui/input-otp";
2223
export * from "./shadcn/ui/label";
2324
export * from "./shadcn/ui/menubar";
2425
export * from "./shadcn/ui/multi-select";

src/shadcn/ui/input-otp.tsx

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import { OTPInput, OTPInputContext } from "input-otp"
5+
import { Minus } from "lucide-react"
6+
7+
import { cn } from "src"
8+
9+
const InputOTP = React.forwardRef<
10+
React.ElementRef<typeof OTPInput>,
11+
React.ComponentPropsWithoutRef<typeof OTPInput>
12+
>(({ className, containerClassName, ...props }, ref) => (
13+
<OTPInput
14+
ref={ref}
15+
containerClassName={cn(
16+
"flex items-center gap-2 has-[:disabled]:opacity-50",
17+
containerClassName
18+
)}
19+
className={cn("disabled:cursor-not-allowed", className)}
20+
{...props}
21+
/>
22+
))
23+
InputOTP.displayName = "InputOTP"
24+
25+
const InputOTPGroup = React.forwardRef<
26+
React.ElementRef<"div">,
27+
React.ComponentPropsWithoutRef<"div">
28+
>(({ className, ...props }, ref) => (
29+
<div ref={ref} className={cn("flex items-center", className)} {...props} />
30+
))
31+
InputOTPGroup.displayName = "InputOTPGroup"
32+
33+
const InputOTPSlot = React.forwardRef<
34+
React.ElementRef<"div">,
35+
React.ComponentPropsWithoutRef<"div"> & { index: number }
36+
>(({ index, className, ...props }, ref) => {
37+
const inputOTPContext = React.useContext(OTPInputContext)
38+
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
39+
40+
return (
41+
<div
42+
ref={ref}
43+
className={cn(
44+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
45+
isActive && "z-10 ring-1 ring-ring",
46+
className
47+
)}
48+
{...props}
49+
>
50+
{char}
51+
{hasFakeCaret && (
52+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
53+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
54+
</div>
55+
)}
56+
</div>
57+
)
58+
})
59+
InputOTPSlot.displayName = "InputOTPSlot"
60+
61+
const InputOTPSeparator = React.forwardRef<
62+
React.ElementRef<"div">,
63+
React.ComponentPropsWithoutRef<"div">
64+
>(({ ...props }, ref) => (
65+
<div ref={ref} role="separator" {...props}>
66+
<Minus />
67+
</div>
68+
))
69+
InputOTPSeparator.displayName = "InputOTPSeparator"
70+
71+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

stories/shadcn/input-otp.stories.tsx

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
InputOTP,
3+
InputOTPGroup,
4+
InputOTPSeparator,
5+
InputOTPSlot,
6+
} from "src"
7+
8+
import type { Meta, StoryObj } from "@storybook/react";
9+
10+
11+
12+
const InputOTPDemo = () => {
13+
return (
14+
<InputOTP maxLength={6}>
15+
<InputOTPGroup>
16+
<InputOTPSlot index={0} />
17+
<InputOTPSlot index={1} />
18+
<InputOTPSlot index={2} />
19+
</InputOTPGroup>
20+
<InputOTPSeparator />
21+
<InputOTPGroup>
22+
<InputOTPSlot index={3} />
23+
<InputOTPSlot index={4} />
24+
<InputOTPSlot index={5} />
25+
</InputOTPGroup>
26+
</InputOTP>
27+
28+
);
29+
};
30+
const meta: Meta<typeof InputOTPDemo> = {
31+
component: InputOTPDemo,
32+
33+
};
34+
35+
export default meta;
36+
37+
export const Default: StoryObj<typeof InputOTPDemo> = {
38+
args: {},
39+
};

0 commit comments

Comments
 (0)