Skip to content

Commit

Permalink
Merge pull request #18 from anday013/feat/prop-on-filled-callback
Browse files Browse the repository at this point in the history
feat: add `onFilled` callback to props
  • Loading branch information
anday013 authored Oct 23, 2023
2 parents 5c6aff9 + 843c464 commit 7da2ad1
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 75 deletions.
20 changes: 12 additions & 8 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
- Supports OTP input with a fixed number of digits.
- Highly customizable appearance and styling.
- Supports autofill
- Fully covered with unit tests.
- Seamless integration with React Native and Expo applications.
- Fully typed with TypeScript.

## Installation

Expand Down Expand Up @@ -50,6 +52,7 @@ yarn add react-native-otp-entry
focusColor="green"
focusStickBlinkingDuration={500}
onTextChange={(text) => console.log(text)}
onFilled={(text) => console.log(`OTP is ${text}`)}
theme={{
containerStyle={styles.container},
inputsContainerStyle={styles.inputsContainer},
Expand All @@ -64,14 +67,15 @@ yarn add react-native-otp-entry

The `react-native-otp-entry` component accepts the following props:

| Prop | Type | Description |
| ---------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `numberOfDigits` | number | The number of digits to be displayed in the OTP entry. |
| `focusColor` | ColorValue | The color of the input field border and stick when it is focused. |
| `onTextChange` | (text: string) => void | A callback function that is invoked when the OTP text changes. It receives the updated text as an argument. |
| `hideStick` | boolean | Hide cursor of the focused input. |
| `theme` | Theme | Custom styles for each element. |
| `focusStickBlinkingDuration` | number | The duration (in milliseconds) for the focus stick to blink. |
| Prop | Type | Description |
| ---------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `numberOfDigits` | number | The number of digits to be displayed in the OTP entry. |
| `focusColor` | ColorValue | The color of the input field border and stick when it is focused. |
| `onTextChange` | (text: string) => void | A callback function is invoked when the OTP text changes. It receives the updated text as an argument. |
| `onFilled` | (text: string) => void | A callback function is invoked when the OTP input is fully filled. It receives a full otp code as an argument. |
| `hideStick` | boolean | Hide cursor of the focused input. |
| `theme` | Theme | Custom styles for each element. |
| `focusStickBlinkingDuration` | number | The duration (in milliseconds) for the focus stick to blink. |

| Theme | Type | Description |
| ----------------------- | --------- | ---------------------------------------------------------------------------------- |
Expand Down
4 changes: 2 additions & 2 deletions dist/src/OtpInput/OtpInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const OtpInput_styles_1 = require("./OtpInput.styles");
const VerticalStick_1 = require("./VerticalStick");
const useOtpInput_1 = require("./useOtpInput");
exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setText }, } = (0, useOtpInput_1.useOtpInput)(props);
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setTextWithRef } } = (0, useOtpInput_1.useOtpInput)(props);
const { numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, theme = {}, } = props;
const { containerStyle, inputsContainerStyle, pinCodeContainerStyle, pinCodeTextStyle, focusStickStyle, } = theme;
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setText }));
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setTextWithRef }));
return (<react_native_1.View style={[OtpInput_styles_1.styles.container, containerStyle]}>
<react_native_1.View style={[OtpInput_styles_1.styles.inputsContainer, inputsContainerStyle]}>
{Array(numberOfDigits)
Expand Down
22 changes: 22 additions & 0 deletions dist/src/OtpInput/OtpInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ describe("OtpInput", () => {
const stick = react_native_1.screen.getByTestId("otp-input-stick");
expect(stick).toBeTruthy();
});
test("focusColor should not be overridden by theme", () => {
renderOtpInput({
focusColor: "#000",
theme: { pinCodeContainerStyle: { borderColor: "#fff" } },
});
const inputs = react_native_1.screen.getAllByTestId("otp-input");
expect(inputs[0]).toHaveStyle({ borderColor: "#000" });
});
// Test if the number of rendered inputs is equal to the number of digits
test.each([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])("should render the correct number of inputs: %i", (numberOfDigits) => {
renderOtpInput({ numberOfDigits: numberOfDigits });
Expand Down Expand Up @@ -58,5 +66,19 @@ describe("OtpInput", () => {
});
expect(react_native_1.screen.getByText("1")).toBeTruthy();
});
test('ref setValue() should set only the first "numberOfDigits" characters', () => {
const ref = React.createRef();
(0, react_native_1.render)(<OtpInput_1.OtpInput ref={ref} numberOfDigits={4}/>);
const otp = "123456";
(0, react_native_1.act)(() => {
ref.current?.setValue(otp);
});
expect(react_native_1.screen.getByText("1")).toBeTruthy();
expect(react_native_1.screen.getByText("2")).toBeTruthy();
expect(react_native_1.screen.getByText("3")).toBeTruthy();
expect(react_native_1.screen.getByText("4")).toBeTruthy();
expect(react_native_1.screen.queryByText("5")).toBeFalsy();
expect(react_native_1.screen.queryByText("6")).toBeFalsy();
});
});
});
1 change: 1 addition & 0 deletions dist/src/OtpInput/OtpInput.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface OtpInputProps {
numberOfDigits: number;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
onFilled?: (text: string) => void;
hideStick?: boolean;
focusStickBlinkingDuration?: number;
theme?: Theme;
Expand Down
3 changes: 2 additions & 1 deletion dist/src/OtpInput/useOtpInput.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="react" />
import { TextInput } from "react-native";
import { OtpInputProps } from "./OtpInput.types";
export declare const useOtpInput: ({ onTextChange }: OtpInputProps) => {
export declare const useOtpInput: ({ onTextChange, onFilled, numberOfDigits }: OtpInputProps) => {
models: {
text: string;
inputRef: import("react").RefObject<TextInput>;
Expand All @@ -14,5 +14,6 @@ export declare const useOtpInput: ({ onTextChange }: OtpInputProps) => {
};
forms: {
setText: import("react").Dispatch<import("react").SetStateAction<string>>;
setTextWithRef: (value: string) => void;
};
};
11 changes: 9 additions & 2 deletions dist/src/OtpInput/useOtpInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.useOtpInput = void 0;
const react_1 = require("react");
const react_native_1 = require("react-native");
const useOtpInput = ({ onTextChange }) => {
const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }) => {
const [text, setText] = (0, react_1.useState)("");
const inputRef = (0, react_1.useRef)(null);
const focusedInputIndex = text.length;
Expand All @@ -17,14 +17,21 @@ const useOtpInput = ({ onTextChange }) => {
const handleTextChange = (value) => {
setText(value);
onTextChange?.(value);
if (value.length === numberOfDigits) {
onFilled?.(value);
}
};
const setTextWithRef = (value) => {
const normalizedValue = value.length > numberOfDigits ? value.slice(0, numberOfDigits) : value;
handleTextChange(normalizedValue);
};
const clear = () => {
setText("");
};
return {
models: { text, inputRef, focusedInputIndex },
actions: { handlePress, handleTextChange, clear },
forms: { setText },
forms: { setText, setTextWithRef },
};
};
exports.useOtpInput = useOtpInput;
27 changes: 26 additions & 1 deletion dist/src/OtpInput/useOtpInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ describe("useOtpInput", () => {
const { result } = renderUseOtInput();
result.current.actions.clear();
(0, react_native_1.act)(() => {
expect(result.current.models.text).toBe("");
expect(result.current.forms.setText).toHaveBeenCalledWith("");
});
});
test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
const { result } = renderUseOtInput();
result.current.forms.setTextWithRef("123456789");
(0, react_native_1.act)(() => {
expect(result.current.forms.setText).toHaveBeenCalledWith("123456");
});
});
test("handlePress() should dismiss Keyboard if it's visible", () => {
jest.spyOn(react_native_2.Keyboard, "dismiss");
jest.spyOn(react_native_2.Keyboard, "isVisible").mockReturnValue(false);
Expand Down Expand Up @@ -61,4 +68,22 @@ describe("useOtpInput", () => {
expect(mockOnTextChange).toHaveBeenCalledWith(value);
});
});
test("onFilled() should be called when the input filled", () => {
const value = "123456";
const mockOnFilled = jest.fn();
const { result } = renderUseOtInput({ onFilled: mockOnFilled });
result.current.actions.handleTextChange(value);
(0, react_native_1.act)(() => {
expect(mockOnFilled).toHaveBeenCalledWith(value);
});
});
test("onFilled() should NOT be called when the input is NOT filled", () => {
const value = "12345";
const mockOnFilled = jest.fn();
const { result } = renderUseOtInput({ onFilled: mockOnFilled });
result.current.actions.handleTextChange(value);
(0, react_native_1.act)(() => {
expect(mockOnFilled).not.toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 7da2ad1

Please sign in to comment.