Skip to content

Commit

Permalink
feat(input-otp): introduce input OTP component (#4052)
Browse files Browse the repository at this point in the history
* feat(input-otp): adding the functionality

* fix(input-otp): making the use of input-otp library

* Update .changeset/spotty-flies-jump.md

* chore(input-otp): nits

* feat: improvements and fixes added

* refactor: input-otp docs improvements

* fix: changeset

* fix: build

---------

Co-authored-by: Maharshi Alpesh <maharshialpesh@Maharshi-Book.local>
Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 93f1c6f commit 1d5b2b6
Show file tree
Hide file tree
Showing 61 changed files with 2,522 additions and 100 deletions.
7 changes: 7 additions & 0 deletions .changeset/spotty-flies-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nextui-org/input-otp": patch
"@nextui-org/theme": patch
"@nextui-org/react": patch
---

Adding new input-otp component.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ packages/**/*.backup.ts

# ignore sitemap
apps/**/sitemap.xml
apps/**/sitemap-0.xml

# turbo
.turbo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {clsx} from "@nextui-org/shared-utils";
import * as Components from "@nextui-org/react";
import * as intlDateUtils from "@internationalized/date";
import * as reactAriaI18n from "@react-aria/i18n";
import * as reactHookForm from "react-hook-form";

import {BgGridContainer} from "@/components/bg-grid-container";
import {GradientBox, GradientBoxProps} from "@/components/gradient-box";
Expand All @@ -23,6 +24,7 @@ export const scope = {
...Components,
...intlDateUtils,
...reactAriaI18n,
...reactHookForm,
} as Record<string, unknown>;

export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
Expand Down
7 changes: 7 additions & 0 deletions apps/docs/config/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@
"path": "/docs/components/input.mdx",
"updated": true
},
{
"key": "input-otp",
"title": "Input OTP",
"keywords": "input, otp, auth, verification code",
"path": "/docs/components/input-otp.mdx",
"newPost": true
},
{
"key": "kbd",
"title": "Kbd",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./user";
export * from "./skeleton";
export * from "./snippet";
export * from "./input";
export * from "./input-otp";
export * from "./textarea";
export * from "./image";
export * from "./radio-group";
Expand Down
25 changes: 25 additions & 0 deletions apps/docs/content/components/input-otp/allowed-keys.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
const allowedKeysConfig = [
{
name: "Only lowercase letters (a-z):",
value: "^[a-z]*$",
},
{
name: "Only uppercase letters (A-Z):",
value: "^[A-Z]*$",
},
];

return (
<div className="w-full flex flex-wrap gap-6">
{allowedKeysConfig.map((config, idx) => (
<div key={idx}>
<div className="text-default-500">{config.name}</div>
<InputOtp allowedKeys={config.value} length={4} />
</div>
))}
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/allowed-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./allowed-keys.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
16 changes: 16 additions & 0 deletions apps/docs/content/components/input-otp/colors.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
const colors = ["default", "primary", "secondary", "success", "warning", "danger"];

return (
<div className="w-full flex flex-wrap gap-4">
{colors.map((color) => (
<div key={color}>
<div className="text-default-500">color: {color}</div>
<InputOtp color={color} length={4} />
</div>
))}
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./colors.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
13 changes: 13 additions & 0 deletions apps/docs/content/components/input-otp/controlled.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {InputOtp} from "@nextui-org/react";
import React from "react";

export default function App() {
const [value, setValue] = React.useState("");

return (
<div className="w-full flex flex-col gap-2 max-w-[240px]">
<InputOtp length={4} value={value} onValueChange={setValue} />
<p className="text-default-500 text-small">value: {value}</p>
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/controlled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./controlled.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
33 changes: 33 additions & 0 deletions apps/docs/content/components/input-otp/custom-styles.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<InputOtp
classNames={{
segmentWrapper: "gap-x-0",
segment: [
"relative",
"h-10",
"w-10",
"border-y",
"border-r",
"first:rounded-l-md",
"first:border-l",
"last:rounded-r-md",
"border-default-200",
"data-[active=true]:border",
"data-[active=true]:z-20",
"data-[active=true]:ring-2",
"data-[active=true]:ring-offset-2",
"data-[active=true]:ring-offset-background",
"data-[active=true]:ring-foreground",
],
}}
description="Enter the 4 digit code sent to your email"
length={4}
radius="none"
/>
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/custom-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./custom-styles.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/description.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<InputOtp description="This is description to the OTP component." length={4} />
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/description.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./description.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/disabled.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<InputOtp isDisabled defaultValue="1234" length={4} />
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./disabled.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/error-message.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {InputOtp} from "@nextui-org/react";

export default function App() {
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<InputOtp isInvalid errorMessage="Invalid OTP code" length={4} />
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/input-otp/error-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./error-message.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
46 changes: 46 additions & 0 deletions apps/docs/content/components/input-otp/form.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {InputOtp} from "@nextui-org/react";
import {useForm, Controller} from "react-hook-form";
import {Button} from "@nextui-org/react";

export default function App() {
const {
handleSubmit,
control,
formState: {errors},
} = useForm({
defaultValues: {
otp: "",
},
});

const onSubmit = (data) => {
alert(JSON.stringify(data));
};

return (
<form className="flex flex-col gap-4 w-full max-w-[300px]" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="otp"
render={({field}) => (
<InputOtp
{...field}
errorMessage={errors.otp && errors.otp.message}
isInvalid={!!errors.otp}
length={4}
/>
)}
rules={{
required: "OTP is required",
minLength: {
value: 4,
message: "Please enter a valid OTP",
},
}}
/>
<Button className="max-w-fit" type="submit" variant="flat">
Verify OTP
</Button>
</form>
);
}
50 changes: 50 additions & 0 deletions apps/docs/content/components/input-otp/form.raw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {InputOtp} from "@nextui-org/react";
import {useForm, Controller, SubmitHandler} from "react-hook-form";
import {Button} from "@nextui-org/react";

interface FormValues {
otp: string;
}

export default function App() {
const {
handleSubmit,
control,
formState: {errors},
} = useForm<FormValues>({
defaultValues: {
otp: "",
},
});

const onSubmit: SubmitHandler<FormValues> = (data) => {
alert(JSON.stringify(data));
};

return (
<form className="flex flex-col gap-4 w-full max-w-[300px]" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="otp"
render={({field}) => (
<InputOtp
{...field}
errorMessage={errors.otp?.message}
isInvalid={!!errors.otp}
length={4}
/>
)}
rules={{
required: "OTP is required",
minLength: {
value: 4,
message: "Please enter a valid OTP",
},
}}
/>
<Button className="max-w-fit" type="submit" variant="flat">
Verify OTP
</Button>
</form>
);
}
15 changes: 15 additions & 0 deletions apps/docs/content/components/input-otp/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import App from "./form.raw.jsx?raw";
import AppTs from "./form.raw.tsx?raw";

const react = {
"/App.jsx": App,
};

const reactTs = {
"/App.tsx": AppTs,
};

export default {
...react,
...reactTs,
};
35 changes: 35 additions & 0 deletions apps/docs/content/components/input-otp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import usage from "./usage";
import disabled from "./disabled";
import readonly from "./readonly";
import required from "./required";
import sizes from "./sizes";
import colors from "./colors";
import variants from "./variants";
import radius from "./radius";
import description from "./description";
import errorMessage from "./error-message";
import allowedKeys from "./allowed-keys";
import controlled from "./controlled";
import password from "./password";
import form from "./form";
import customStyles from "./custom-styles";
import lengths from "./lengths";

export const inputOtpContent = {
usage,
disabled,
readonly,
required,
sizes,
colors,
variants,
radius,
description,
errorMessage,
allowedKeys,
controlled,
password,
form,
customStyles,
lengths,
};
Loading

0 comments on commit 1d5b2b6

Please sign in to comment.