mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-02 17:08:39 +00:00
feat: switch input-otp to composition (#3052)
* feat: switch input-otp to composition * feat: add disabled
This commit is contained in:
@@ -114,24 +114,19 @@ import {
|
||||
```
|
||||
|
||||
```tsx
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<>
|
||||
<InputOTPGroup>
|
||||
{slots.slice(0, 3).map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
{slots.slice(3).map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}
|
||||
</InputOTPGroup>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -150,14 +145,12 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
{/* ... */}
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
```
|
||||
|
||||
### Separator
|
||||
@@ -166,29 +159,27 @@ You can use the `<InputOTPSeparator />` component to add a separator between the
|
||||
|
||||
<ComponentPreview name="input-otp-separator" />
|
||||
|
||||
```tsx showLineNumbers {4,17}
|
||||
```tsx showLineNumbers {4,15}
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSeparator,
|
||||
InputOTPSlot,
|
||||
} from "@/registry/new-york/ui/input-otp"
|
||||
} from "@/components/ui/input-otp"
|
||||
|
||||
...
|
||||
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup className="gap-2">
|
||||
{slots.map((slot, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<InputOTPSlot className="rounded-md border" {...slot} />
|
||||
{index !== slots.length - 1 && <InputOTPSeparator />}
|
||||
</React.Fragment>
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={4}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
```
|
||||
|
||||
### Controlled
|
||||
@@ -200,3 +191,80 @@ You can use the `value` and `onChange` props to control the input value.
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="input-otp-form" />
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2024-03-19 Composition
|
||||
|
||||
We've made some updates and replaced the render props pattern with composition. Here's how to update your code if you prefer the composition pattern.
|
||||
|
||||
<Callout className="mt-6">
|
||||
**Note:** You are not required to update your code if you are using the
|
||||
`render` prop. It is still supported.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Update to the latest version of `input-otp`.</Step>
|
||||
|
||||
```bash
|
||||
npm install input-otp@latest
|
||||
```
|
||||
|
||||
<Step>Update `input-otp.tsx`</Step>
|
||||
|
||||
```diff showLineNumbers title="input-otp.tsx" {2,8-11}
|
||||
- import { OTPInput, SlotProps } from "input-otp"
|
||||
+ import { OTPInput, OTPInputContext } from "input-otp"
|
||||
|
||||
const InputOTPSlot = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
- SlotProps & React.ComponentPropsWithoutRef<"div">
|
||||
- >(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
|
||||
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
|
||||
+ >(({ index, className, ...props }, ref) => {
|
||||
+ const inputOTPContext = React.useContext(OTPInputContext)
|
||||
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
|
||||
```
|
||||
|
||||
<Step>Then replace the `render` prop in your code.</Step>
|
||||
|
||||
```diff showLineNumbers {2-12}
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
### 2024-03-19 Disabled
|
||||
|
||||
To add a disabled state to the input, update `<InputOTP />` as follows:
|
||||
|
||||
```tsx showLineNumbers title="input-otp.tsx" {4,7-11}
|
||||
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"
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"embla-carousel-autoplay": "8.0.0-rc15",
|
||||
"embla-carousel-react": "8.0.0-rc15",
|
||||
"geist": "^1.1.0",
|
||||
"input-otp": "^1.0.1",
|
||||
"input-otp": "^1.2.2",
|
||||
"jotai": "^2.1.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"lucide-react": "0.288.0",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"files": [
|
||||
{
|
||||
"name": "input-otp.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, SlotProps } from \"input-otp\"\nimport { Dot } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst InputOTP = React.forwardRef<\n React.ElementRef<typeof OTPInput>,\n React.ComponentPropsWithoutRef<typeof OTPInput>\n>(({ className, ...props }, ref) => (\n <OTPInput\n ref={ref}\n containerClassName={cn(\"flex items-center gap-2\", className)}\n {...props}\n />\n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-center\", className)} {...props} />\n))\nInputOTPGroup.displayName = \"InputOTPGroup\"\n\nconst InputOTPSlot = React.forwardRef<\n React.ElementRef<\"div\">,\n SlotProps & React.ComponentPropsWithoutRef<\"div\">\n>(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n \"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md\",\n isActive && \"z-10 ring-2 ring-offset-background ring-ring\",\n className\n )}\n {...props}\n >\n {char}\n {hasFakeCaret && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"animate-caret-blink h-4 w-px bg-foreground duration-1000\" />\n </div>\n )}\n </div>\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n <div ref={ref} role=\"separator\" {...props}>\n <Dot />\n </div>\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\nimport { Dot } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst InputOTP = React.forwardRef<\n React.ElementRef<typeof OTPInput>,\n React.ComponentPropsWithoutRef<typeof OTPInput>\n>(({ className, containerClassName, ...props }, ref) => (\n <OTPInput\n ref={ref}\n containerClassName={cn(\n \"flex items-center gap-2 has-[:disabled]:opacity-50\",\n containerClassName\n )}\n className={cn(\"disabled:cursor-not-allowed\", className)}\n {...props}\n />\n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-center\", className)} {...props} />\n))\nInputOTPGroup.displayName = \"InputOTPGroup\"\n\nconst InputOTPSlot = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\"> & { index: number }\n>(({ index, className, ...props }, ref) => {\n const inputOTPContext = React.useContext(OTPInputContext)\n const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]\n\n return (\n <div\n ref={ref}\n className={cn(\n \"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md\",\n isActive && \"z-10 ring-2 ring-ring ring-offset-background\",\n className\n )}\n {...props}\n >\n {char}\n {hasFakeCaret && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"h-4 w-px animate-caret-blink bg-foreground duration-1000\" />\n </div>\n )}\n </div>\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n <div ref={ref} role=\"separator\" {...props}>\n <Dot />\n </div>\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
|
||||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"files": [
|
||||
{
|
||||
"name": "input-otp.tsx",
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DashIcon } from \"@radix-ui/react-icons\"\nimport { OTPInput, SlotProps } from \"input-otp\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst InputOTP = React.forwardRef<\n React.ElementRef<typeof OTPInput>,\n React.ComponentPropsWithoutRef<typeof OTPInput>\n>(({ className, ...props }, ref) => (\n <OTPInput\n ref={ref}\n containerClassName={cn(\"flex items-center gap-2\", className)}\n {...props}\n />\n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-center\", className)} {...props} />\n))\nInputOTPGroup.displayName = \"InputOTPGroup\"\n\nconst InputOTPSlot = React.forwardRef<\n React.ElementRef<\"div\">,\n SlotProps & React.ComponentPropsWithoutRef<\"div\">\n>(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n \"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\",\n isActive && \"z-10 ring-1 ring-ring\",\n className\n )}\n {...props}\n >\n {char}\n {hasFakeCaret && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"animate-caret-blink h-4 w-px bg-foreground duration-1000\" />\n </div>\n )}\n </div>\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n <div ref={ref} role=\"separator\" {...props}>\n <DashIcon />\n </div>\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
|
||||
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DashIcon } from \"@radix-ui/react-icons\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst InputOTP = React.forwardRef<\n React.ElementRef<typeof OTPInput>,\n React.ComponentPropsWithoutRef<typeof OTPInput>\n>(({ className, containerClassName, ...props }, ref) => (\n <OTPInput\n ref={ref}\n containerClassName={cn(\n \"flex items-center gap-2 has-[:disabled]:opacity-50\",\n containerClassName\n )}\n className={cn(\"disabled:cursor-not-allowed\", className)}\n {...props}\n />\n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-center\", className)} {...props} />\n))\nInputOTPGroup.displayName = \"InputOTPGroup\"\n\nconst InputOTPSlot = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\"> & { index: number }\n>(({ index, className, ...props }, ref) => {\n const inputOTPContext = React.useContext(OTPInputContext)\n const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]\n\n return (\n <div\n ref={ref}\n className={cn(\n \"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\",\n isActive && \"z-10 ring-1 ring-ring\",\n className\n )}\n {...props}\n >\n {char}\n {hasFakeCaret && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"h-4 w-px animate-caret-blink bg-foreground duration-1000\" />\n </div>\n )}\n </div>\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n <div ref={ref} role=\"separator\" {...props}>\n <DashIcon />\n </div>\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
|
||||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
|
||||
@@ -17,14 +17,16 @@ export default function InputOTPControlled() {
|
||||
maxLength={6}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
<div className="text-center text-sm">
|
||||
{value === "" ? (
|
||||
<>Enter your one-time password.</>
|
||||
|
||||
@@ -7,23 +7,18 @@ import {
|
||||
|
||||
export default function InputOTPDemo() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<>
|
||||
<InputOTPGroup>
|
||||
{slots.slice(0, 3).map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
{slots.slice(3).map((slot, index) => (
|
||||
<InputOTPSlot key={index + 3} {...slot} />
|
||||
))}
|
||||
</InputOTPGroup>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,17 +56,16 @@ export default function InputOTPForm() {
|
||||
<FormItem>
|
||||
<FormLabel>One-Time Password</FormLabel>
|
||||
<FormControl>
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
<InputOTP maxLength={6} {...field}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Please enter the one-time password sent to your phone.
|
||||
|
||||
@@ -8,16 +8,15 @@ import {
|
||||
|
||||
export default function InputOTPPattern() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,18 +9,21 @@ import {
|
||||
|
||||
export default function InputOTPWithSeparator() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup className="gap-2">
|
||||
{slots.map((slot, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<InputOTPSlot className="rounded-md border" {...slot} />
|
||||
{index !== slots.length - 1 && <InputOTPSeparator />}
|
||||
</React.Fragment>
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { OTPInput, SlotProps } from "input-otp"
|
||||
import { OTPInput, OTPInputContext } from "input-otp"
|
||||
import { Dot } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -9,10 +9,14 @@ import { cn } from "@/lib/utils"
|
||||
const InputOTP = React.forwardRef<
|
||||
React.ElementRef<typeof OTPInput>,
|
||||
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({ className, containerClassName, ...props }, ref) => (
|
||||
<OTPInput
|
||||
ref={ref}
|
||||
containerClassName={cn("flex items-center gap-2", className)}
|
||||
containerClassName={cn(
|
||||
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
||||
containerClassName
|
||||
)}
|
||||
className={cn("disabled:cursor-not-allowed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -28,14 +32,17 @@ InputOTPGroup.displayName = "InputOTPGroup"
|
||||
|
||||
const InputOTPSlot = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
SlotProps & React.ComponentPropsWithoutRef<"div">
|
||||
>(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
|
||||
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-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
||||
isActive && "z-10 ring-2 ring-offset-background ring-ring",
|
||||
isActive && "z-10 ring-2 ring-ring ring-offset-background",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -43,7 +50,7 @@ const InputOTPSlot = React.forwardRef<
|
||||
{char}
|
||||
{hasFakeCaret && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
|
||||
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -17,14 +17,16 @@ export default function InputOTPControlled() {
|
||||
maxLength={6}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
<div className="text-center text-sm">
|
||||
{value === "" ? (
|
||||
<>Enter your one-time password.</>
|
||||
|
||||
@@ -7,23 +7,18 @@ import {
|
||||
|
||||
export default function InputOTPDemo() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<>
|
||||
<InputOTPGroup>
|
||||
{slots.slice(0, 3).map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
{slots.slice(3).map((slot, index) => (
|
||||
<InputOTPSlot key={index + 3} {...slot} />
|
||||
))}
|
||||
</InputOTPGroup>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,17 +56,16 @@ export default function InputOTPForm() {
|
||||
<FormItem>
|
||||
<FormLabel>One-Time Password</FormLabel>
|
||||
<FormControl>
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
<InputOTP maxLength={6} {...field}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Please enter the one-time password sent to your phone.
|
||||
|
||||
@@ -8,16 +8,15 @@ import {
|
||||
|
||||
export default function InputOTPPattern() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup>
|
||||
{slots.map((slot, index) => (
|
||||
<InputOTPSlot key={index} {...slot} />
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,18 +9,21 @@ import {
|
||||
|
||||
export default function InputOTPWithSeparator() {
|
||||
return (
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
render={({ slots }) => (
|
||||
<InputOTPGroup className="gap-2">
|
||||
{slots.map((slot, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<InputOTPSlot className="rounded-md border" {...slot} />
|
||||
{index !== slots.length - 1 && <InputOTPSeparator />}
|
||||
</React.Fragment>
|
||||
))}{" "}
|
||||
</InputOTPGroup>
|
||||
)}
|
||||
/>
|
||||
<InputOTP maxLength={6}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { DashIcon } from "@radix-ui/react-icons"
|
||||
import { OTPInput, SlotProps } from "input-otp"
|
||||
import { OTPInput, OTPInputContext } from "input-otp"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const InputOTP = React.forwardRef<
|
||||
React.ElementRef<typeof OTPInput>,
|
||||
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({ className, containerClassName, ...props }, ref) => (
|
||||
<OTPInput
|
||||
ref={ref}
|
||||
containerClassName={cn("flex items-center gap-2", className)}
|
||||
containerClassName={cn(
|
||||
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
||||
containerClassName
|
||||
)}
|
||||
className={cn("disabled:cursor-not-allowed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -28,8 +32,11 @@ InputOTPGroup.displayName = "InputOTPGroup"
|
||||
|
||||
const InputOTPSlot = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
SlotProps & React.ComponentPropsWithoutRef<"div">
|
||||
>(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
|
||||
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}
|
||||
@@ -43,7 +50,7 @@ const InputOTPSlot = React.forwardRef<
|
||||
{char}
|
||||
{hasFakeCaret && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
|
||||
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -69,3 +69,7 @@
|
||||
.mdx > .steps:first-child > h3:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.steps > h3 {
|
||||
@apply mt-8 mb-4 text-base font-semibold;
|
||||
}
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -231,8 +231,8 @@ importers:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(next@14.0.4)
|
||||
input-otp:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(react-dom@18.2.0)(react@18.2.0)
|
||||
jotai:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(react@18.2.0)
|
||||
@@ -7615,8 +7615,8 @@ packages:
|
||||
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
|
||||
dev: false
|
||||
|
||||
/input-otp@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-AFMGRsOwXcH7koO+8nnVcJFYEe92tNmRlb2TUKbj9Bpdyc44GaS3LfJam3MdoXQv1jejpMS0+fxJFSCsEDHd9A==}
|
||||
/input-otp@1.2.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-9x6UurPuc9Tb+ywWFcFrG4ryvScSmfLyj8D35dl/HNpSr9jZNtWiXufU65kaDHD/KYUop7hDFH+caZCUKdYNsg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
|
||||
Reference in New Issue
Block a user