diff --git a/apps/www/content/docs/components/input-otp.mdx b/apps/www/content/docs/components/input-otp.mdx index 0e31d1b9b1d..3bd96c36726 100644 --- a/apps/www/content/docs/components/input-otp.mdx +++ b/apps/www/content/docs/components/input-otp.mdx @@ -114,24 +114,19 @@ import { ``` ```tsx - ( - <> - - {slots.slice(0, 3).map((slot, index) => ( - - ))}{" "} - - - - {slots.slice(3).map((slot, index) => ( - - ))} - - - )} -/> + + + + + + + + + + + + + ``` ## Examples @@ -150,14 +145,12 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp" ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} -/> +> + + + {/* ... */} + + ``` ### Separator @@ -166,29 +159,27 @@ You can use the `` component to add a separator between the -```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" ... - ( - - {slots.map((slot, index) => ( - - - {index !== slots.length - 1 && } - - ))}{" "} - - )} -/> + + + + + + + + + + + ``` ### Controlled @@ -200,3 +191,80 @@ You can use the `value` and `onChange` props to control the input value. ### 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. + + + **Note:** You are not required to update your code if you are using the + `render` prop. It is still supported. + + + + +Update to the latest version of `input-otp`. + +```bash +npm install input-otp@latest +``` + +Update `input-otp.tsx` + +```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] +``` + +Then replace the `render` prop in your code. + +```diff showLineNumbers {2-12} + + + + + + + + + + + + + +``` + + + +### 2024-03-19 Disabled + +To add a disabled state to the input, update `` as follows: + +```tsx showLineNumbers title="input-otp.tsx" {4,7-11} +const InputOTP = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)) +InputOTP.displayName = "InputOTP" +``` diff --git a/apps/www/package.json b/apps/www/package.json index 38c212ce85a..6d65f3c0e92 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -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", diff --git a/apps/www/public/registry/styles/default/input-otp.json b/apps/www/public/registry/styles/default/input-otp.json index 6be1abeabc9..648ea71421c 100644 --- a/apps/www/public/registry/styles/default/input-otp.json +++ b/apps/www/public/registry/styles/default/input-otp.json @@ -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,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n
\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 \n {char}\n {hasFakeCaret && (\n
\n
\n
\n )}\n
\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n
\n \n
\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,\n React.ComponentPropsWithoutRef\n>(({ className, containerClassName, ...props }, ref) => (\n \n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n
\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 \n {char}\n {hasFakeCaret && (\n
\n
\n
\n )}\n
\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n
\n \n
\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/input-otp.json b/apps/www/public/registry/styles/new-york/input-otp.json index 8bdb700821b..15b25c76b74 100644 --- a/apps/www/public/registry/styles/new-york/input-otp.json +++ b/apps/www/public/registry/styles/new-york/input-otp.json @@ -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,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n
\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 \n {char}\n {hasFakeCaret && (\n
\n
\n
\n )}\n
\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n
\n \n
\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,\n React.ComponentPropsWithoutRef\n>(({ className, containerClassName, ...props }, ref) => (\n \n))\nInputOTP.displayName = \"InputOTP\"\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n
\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 \n {char}\n {hasFakeCaret && (\n
\n
\n
\n )}\n
\n )\n})\nInputOTPSlot.displayName = \"InputOTPSlot\"\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n
\n \n
\n))\nInputOTPSeparator.displayName = \"InputOTPSeparator\"\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/input-otp-controlled.tsx b/apps/www/registry/default/example/input-otp-controlled.tsx index 264b212cc63..c5f95cfbfca 100644 --- a/apps/www/registry/default/example/input-otp-controlled.tsx +++ b/apps/www/registry/default/example/input-otp-controlled.tsx @@ -17,14 +17,16 @@ export default function InputOTPControlled() { maxLength={6} value={value} onChange={(value) => setValue(value)} - render={({ slots }) => ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - /> + > + + + + + + + + +
{value === "" ? ( <>Enter your one-time password. diff --git a/apps/www/registry/default/example/input-otp-demo.tsx b/apps/www/registry/default/example/input-otp-demo.tsx index f2b8d7fb542..ec19b7db0fa 100644 --- a/apps/www/registry/default/example/input-otp-demo.tsx +++ b/apps/www/registry/default/example/input-otp-demo.tsx @@ -7,23 +7,18 @@ import { export default function InputOTPDemo() { return ( - ( - <> - - {slots.slice(0, 3).map((slot, index) => ( - - ))}{" "} - - - - {slots.slice(3).map((slot, index) => ( - - ))} - - - )} - /> + + + + + + + + + + + + + ) } diff --git a/apps/www/registry/default/example/input-otp-form.tsx b/apps/www/registry/default/example/input-otp-form.tsx index 66b18702143..cc6922f52df 100644 --- a/apps/www/registry/default/example/input-otp-form.tsx +++ b/apps/www/registry/default/example/input-otp-form.tsx @@ -56,17 +56,16 @@ export default function InputOTPForm() { One-Time Password - ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - {...field} - /> + + + + + + + + + + Please enter the one-time password sent to your phone. diff --git a/apps/www/registry/default/example/input-otp-pattern.tsx b/apps/www/registry/default/example/input-otp-pattern.tsx index a9454b0a0ab..87d711b0ecd 100644 --- a/apps/www/registry/default/example/input-otp-pattern.tsx +++ b/apps/www/registry/default/example/input-otp-pattern.tsx @@ -8,16 +8,15 @@ import { export default function InputOTPPattern() { return ( - ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - /> + + + + + + + + + + ) } diff --git a/apps/www/registry/default/example/input-otp-separator.tsx b/apps/www/registry/default/example/input-otp-separator.tsx index fd8f12c1153..e6e5a2a6fa2 100644 --- a/apps/www/registry/default/example/input-otp-separator.tsx +++ b/apps/www/registry/default/example/input-otp-separator.tsx @@ -9,18 +9,21 @@ import { export default function InputOTPWithSeparator() { return ( - ( - - {slots.map((slot, index) => ( - - - {index !== slots.length - 1 && } - - ))}{" "} - - )} - /> + + + + + + + + + + + + + + + + ) } diff --git a/apps/www/registry/default/ui/input-otp.tsx b/apps/www/registry/default/ui/input-otp.tsx index a42ca9a1c92..f66fcfa0ddb 100644 --- a/apps/www/registry/default/ui/input-otp.tsx +++ b/apps/www/registry/default/ui/input-otp.tsx @@ -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, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( +>(({ className, containerClassName, ...props }, ref) => ( )) @@ -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 (
-
+
)}
diff --git a/apps/www/registry/new-york/example/input-otp-controlled.tsx b/apps/www/registry/new-york/example/input-otp-controlled.tsx index 0293a90a095..9b4eb7330f8 100644 --- a/apps/www/registry/new-york/example/input-otp-controlled.tsx +++ b/apps/www/registry/new-york/example/input-otp-controlled.tsx @@ -17,14 +17,16 @@ export default function InputOTPControlled() { maxLength={6} value={value} onChange={(value) => setValue(value)} - render={({ slots }) => ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - /> + > + + + + + + + + +
{value === "" ? ( <>Enter your one-time password. diff --git a/apps/www/registry/new-york/example/input-otp-demo.tsx b/apps/www/registry/new-york/example/input-otp-demo.tsx index bfa24c031a3..bc63ca1c162 100644 --- a/apps/www/registry/new-york/example/input-otp-demo.tsx +++ b/apps/www/registry/new-york/example/input-otp-demo.tsx @@ -7,23 +7,18 @@ import { export default function InputOTPDemo() { return ( - ( - <> - - {slots.slice(0, 3).map((slot, index) => ( - - ))}{" "} - - - - {slots.slice(3).map((slot, index) => ( - - ))} - - - )} - /> + + + + + + + + + + + + + ) } diff --git a/apps/www/registry/new-york/example/input-otp-form.tsx b/apps/www/registry/new-york/example/input-otp-form.tsx index 432216a7a56..2ab26e4739f 100644 --- a/apps/www/registry/new-york/example/input-otp-form.tsx +++ b/apps/www/registry/new-york/example/input-otp-form.tsx @@ -56,17 +56,16 @@ export default function InputOTPForm() { One-Time Password - ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - {...field} - /> + + + + + + + + + + Please enter the one-time password sent to your phone. diff --git a/apps/www/registry/new-york/example/input-otp-pattern.tsx b/apps/www/registry/new-york/example/input-otp-pattern.tsx index 48eb4f5630a..9dc46cab92d 100644 --- a/apps/www/registry/new-york/example/input-otp-pattern.tsx +++ b/apps/www/registry/new-york/example/input-otp-pattern.tsx @@ -8,16 +8,15 @@ import { export default function InputOTPPattern() { return ( - ( - - {slots.map((slot, index) => ( - - ))}{" "} - - )} - /> + + + + + + + + + + ) } diff --git a/apps/www/registry/new-york/example/input-otp-separator.tsx b/apps/www/registry/new-york/example/input-otp-separator.tsx index c789f155413..dd652966353 100644 --- a/apps/www/registry/new-york/example/input-otp-separator.tsx +++ b/apps/www/registry/new-york/example/input-otp-separator.tsx @@ -9,18 +9,21 @@ import { export default function InputOTPWithSeparator() { return ( - ( - - {slots.map((slot, index) => ( - - - {index !== slots.length - 1 && } - - ))}{" "} - - )} - /> + + + + + + + + + + + + + + + + ) } diff --git a/apps/www/registry/new-york/ui/input-otp.tsx b/apps/www/registry/new-york/ui/input-otp.tsx index 6229e3cb792..84d07209056 100644 --- a/apps/www/registry/new-york/ui/input-otp.tsx +++ b/apps/www/registry/new-york/ui/input-otp.tsx @@ -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, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( +>(({ className, containerClassName, ...props }, ref) => ( )) @@ -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 (
-
+
)}
diff --git a/apps/www/styles/mdx.css b/apps/www/styles/mdx.css index 8f7ba69222c..e84b16a56db 100644 --- a/apps/www/styles/mdx.css +++ b/apps/www/styles/mdx.css @@ -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; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f112a67368..a443cde9866 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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