Skip to content

Commit

Permalink
Merge pull request #28 from GoldAndLink/feature/add-focus-and-autofocus
Browse files Browse the repository at this point in the history
Feature/add focus and autofocus
  • Loading branch information
anday013 authored Dec 15, 2023
2 parents e467112 + 195831f commit ce14be1
Show file tree
Hide file tree
Showing 13 changed files with 79 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ 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. |
| `autoFocus` | boolean | Set autofocus. |
| `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. |
Expand All @@ -96,6 +97,7 @@ The `react-native-otp-entry` component exposes these functions with `ref`:
| Prop | Type | Description |
| ---------- | ------------------------ | ---------------------------------- |
| `clear` | () => void; | Clears the value of the OTP input. |
| `focus` | () => void; | Focus of the OTP input. |
| `setValue` | (value: string) => void; | Sets the value of the OTP input. |

## License
Expand Down
8 changes: 4 additions & 4 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: { setTextWithRef }, } = (0, useOtpInput_1.useOtpInput)(props);
const { numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange, focus }, forms: { setTextWithRef }, } = (0, useOtpInput_1.useOtpInput)(props);
const { numberOfDigits, autoFocus = true, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
const { containerStyle, inputsContainerStyle, pinCodeContainerStyle, pinCodeTextStyle, focusStickStyle, focusedPinCodeContainerStyle, } = theme;
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setTextWithRef }));
(0, react_1.useImperativeHandle)(ref, () => ({ clear, focus, 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 All @@ -32,6 +32,6 @@ exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
</react_native_1.Pressable>);
})}
</react_native_1.View>
<react_native_1.TextInput value={text} onChangeText={handleTextChange} maxLength={numberOfDigits} inputMode="numeric" ref={inputRef} autoFocus style={OtpInput_styles_1.styles.hiddenInput} secureTextEntry={secureTextEntry} testID="otp-input-hidden"/>
<react_native_1.TextInput value={text} onChangeText={handleTextChange} maxLength={numberOfDigits} inputMode="numeric" ref={inputRef} autoFocus={autoFocus} style={OtpInput_styles_1.styles.hiddenInput} secureTextEntry={secureTextEntry} testID="otp-input-hidden"/>
</react_native_1.View>);
});
10 changes: 10 additions & 0 deletions dist/src/OtpInput/OtpInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ describe("OtpInput", () => {
expect(input).toHaveTextContent("•");
});
});
test("should autoFocused by default", () => {
renderOtpInput();
const input = react_native_1.screen.getByTestId("otp-input-hidden");
expect(input.props.autoFocus).toBe(true);
});
test('should not focus if "autoFocus" is false', () => {
renderOtpInput({ autoFocus: false });
const input = react_native_1.screen.getByTestId("otp-input-hidden");
expect(input.props.autoFocus).toBe(false);
});
test("focusColor should not be overridden by theme", () => {
renderOtpInput({
focusColor: "#000",
Expand Down
2 changes: 2 additions & 0 deletions dist/src/OtpInput/OtpInput.types.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ColorValue, TextStyle, ViewStyle } from "react-native";
export interface OtpInputProps {
numberOfDigits: number;
autoFocus?: boolean;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
onFilled?: (text: string) => void;
Expand All @@ -11,6 +12,7 @@ export interface OtpInputProps {
}
export interface OtpInputRef {
clear: () => void;
focus: () => void;
setValue: (value: string) => void;
}
export interface Theme {
Expand Down
1 change: 1 addition & 0 deletions dist/src/OtpInput/useOtpInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export declare const useOtpInput: ({ onTextChange, onFilled, numberOfDigits }: O
handlePress: () => void;
handleTextChange: (value: string) => void;
clear: () => void;
focus: () => void;
};
forms: {
setText: import("react").Dispatch<import("react").SetStateAction<string>>;
Expand Down
5 changes: 4 additions & 1 deletion dist/src/OtpInput/useOtpInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }) => {
const clear = () => {
setText("");
};
const focus = () => {
inputRef.current?.focus();
};
return {
models: { text, inputRef, focusedInputIndex },
actions: { handlePress, handleTextChange, clear },
actions: { handlePress, handleTextChange, clear, focus },
forms: { setText, setTextWithRef },
};
};
Expand Down
8 changes: 8 additions & 0 deletions dist/src/OtpInput/useOtpInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ describe("useOtpInput", () => {
expect(result.current.forms.setText).toHaveBeenCalledWith("");
});
});
test("focus() should focus on input", () => {
jest.spyOn(React, "useRef").mockReturnValue({ current: { focus: jest.fn() } });
const { result } = renderUseOtInput();
result.current.actions.focus();
(0, react_native_1.act)(() => {
expect(result.current.models.inputRef.current?.focus).toHaveBeenCalled();
});
});
test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
const { result } = renderUseOtInput();
Expand Down
16 changes: 16 additions & 0 deletions src/OtpInput/OtpInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ describe("OtpInput", () => {
});
});

test("should autoFocused by default", () => {
renderOtpInput();

const input = screen.getByTestId("otp-input-hidden");

expect(input.props.autoFocus).toBe(true);
});

test('should not focus if "autoFocus" is false', () => {
renderOtpInput({ autoFocus: false });

const input = screen.getByTestId("otp-input-hidden");

expect(input.props.autoFocus).toBe(false);
});

test("focusColor should not be overridden by theme", () => {
renderOtpInput({
focusColor: "#000",
Expand Down
7 changes: 4 additions & 3 deletions src/OtpInput/OtpInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { useOtpInput } from "./useOtpInput";
export const OtpInput = forwardRef<OtpInputRef, OtpInputProps>((props, ref) => {
const {
models: { text, inputRef, focusedInputIndex },
actions: { clear, handlePress, handleTextChange },
actions: { clear, handlePress, handleTextChange, focus },
forms: { setTextWithRef },
} = useOtpInput(props);
const {
numberOfDigits,
autoFocus = true,
hideStick,
focusColor = "#A4D0A4",
focusStickBlinkingDuration,
Expand All @@ -28,7 +29,7 @@ export const OtpInput = forwardRef<OtpInputRef, OtpInputProps>((props, ref) => {
focusedPinCodeContainerStyle,
} = theme;

useImperativeHandle(ref, () => ({ clear, setValue: setTextWithRef }));
useImperativeHandle(ref, () => ({ clear, focus, setValue: setTextWithRef }));

return (
<View style={[styles.container, containerStyle]}>
Expand Down Expand Up @@ -74,7 +75,7 @@ export const OtpInput = forwardRef<OtpInputRef, OtpInputProps>((props, ref) => {
maxLength={numberOfDigits}
inputMode="numeric"
ref={inputRef}
autoFocus
autoFocus={autoFocus}
style={styles.hiddenInput}
secureTextEntry={secureTextEntry}
testID="otp-input-hidden"
Expand Down
2 changes: 2 additions & 0 deletions src/OtpInput/OtpInput.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ColorValue, TextStyle, ViewStyle } from "react-native";

export interface OtpInputProps {
numberOfDigits: number;
autoFocus?: boolean;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
onFilled?: (text: string) => void;
Expand All @@ -13,6 +14,7 @@ export interface OtpInputProps {

export interface OtpInputRef {
clear: () => void;
focus: () => void;
setValue: (value: string) => void;
}

Expand Down
11 changes: 11 additions & 0 deletions src/OtpInput/useOtpInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ describe("useOtpInput", () => {
});
});

test("focus() should focus on input", () => {
jest.spyOn(React, "useRef").mockReturnValue({ current: { focus: jest.fn() } } as any);

const { result } = renderUseOtInput();
result.current.actions.focus();

act(() => {
expect(result.current.models.inputRef.current?.focus).toHaveBeenCalled();
});
});

test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
const { result } = renderUseOtInput();
Expand Down
6 changes: 5 additions & 1 deletion src/OtpInput/useOtpInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ export const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }: OtpInput
setText("");
};

const focus = () => {
inputRef.current?.focus();
};

return {
models: { text, inputRef, focusedInputIndex },
actions: { handlePress, handleTextChange, clear },
actions: { handlePress, handleTextChange, clear, focus },
forms: { setText, setTextWithRef },
};
};
10 changes: 10 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ declare module "OTPInput" {
import { ColorValue, TextStyle, ViewStyle } from "react-native";

export interface OtpEntryProps {
/**
* Autofocus.
*/
autoFocus: boolean;

/**
* The number of digits to be displayed in the OTP entry.
*/
Expand Down Expand Up @@ -35,6 +40,11 @@ declare module "OTPInput" {
*/
clear: () => void;

/**
* Focus of the OTP input.
*/
focus: () => void;

/**
* Sets the value of the OTP input.
* @param value - The value to be set.
Expand Down

0 comments on commit ce14be1

Please sign in to comment.