Skip to content

Commit f217e68

Browse files
feat: created otp input component
1 parent 8a9bf6c commit f217e68

File tree

8 files changed

+226
-26
lines changed

8 files changed

+226
-26
lines changed

example/app/_layout.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React from 'react';
2+
import { PaperProvider } from 'react-native-paper';
23
import App from '../src/App';
34
export default function RootLayout() {
4-
return <App />;
5+
return (
6+
<PaperProvider>
7+
<App />
8+
</PaperProvider>
9+
);
510
}

example/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
"react-dom": "18.2.0",
2626
"react-native": "0.74.1",
2727
"react-native-gesture-handler": "~2.16.1",
28+
"react-native-paper": "^5.12.3",
2829
"react-native-reanimated": "~3.10.1",
2930
"react-native-safe-area-context": "4.10.1",
3031
"react-native-screens": "3.31.1",
32+
"react-native-vector-icons": "^10.1.0",
3133
"react-native-web": "~0.19.10"
3234
},
3335
"devDependencies": {

example/src/App.tsx

+15-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import * as React from 'react';
22

3-
import { StyleSheet, View, Text } from 'react-native';
4-
import { multiply } from 'react-native-paper-otp-input';
3+
import { StyleSheet, View } from 'react-native';
4+
import { ScrollView } from 'react-native';
5+
import { PaperOtpInput } from 'react-native-paper-otp-input';
56

67
export default function App() {
7-
const [result, setResult] = React.useState<number | undefined>();
8-
9-
React.useEffect(() => {
10-
multiply(3, 7).then(setResult);
11-
}, []);
12-
138
return (
149
<View style={styles.container}>
15-
<Text>Result: {result}</Text>
10+
<ScrollView contentContainerStyle={styles.scrollViewContainer}>
11+
<PaperOtpInput
12+
autoFocus={false}
13+
onPinReady={(pin) => {
14+
console.log('Pin is ready:', pin);
15+
}}
16+
maxLength={4}
17+
/>
18+
</ScrollView>
1619
</View>
1720
);
1821
}
@@ -23,9 +26,8 @@ const styles = StyleSheet.create({
2326
alignItems: 'center',
2427
justifyContent: 'center',
2528
},
26-
box: {
27-
width: 60,
28-
height: 60,
29-
marginVertical: 20,
29+
scrollViewContainer: {
30+
flexGrow: 1,
31+
justifyContent: 'center',
3032
},
3133
});

example/tsconfig.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
22
"extends": "../tsconfig",
3-
"compilerOptions": {
4-
// Avoid expo-cli auto-generating a tsconfig
5-
}
3+
"compilerOptions": {},
4+
"include": [
5+
"**/*.ts",
6+
"**/*.tsx",
7+
".expo/types/**/*.ts",
8+
"expo-env.d.ts"
9+
]
610
}

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@
7777
},
7878
"peerDependencies": {
7979
"react": "*",
80-
"react-native": "*"
80+
"react-native": "*",
81+
"react-native-paper": "*",
82+
"react-native-safe-area-context": "*"
8183
},
8284
"workspaces": [
8385
"example"

src/components/OtpInput.tsx

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from 'react';
2+
import {
3+
View,
4+
StyleSheet,
5+
Pressable,
6+
TextInput as RNTextInput,
7+
} from 'react-native';
8+
import { TextInput, Text } from 'react-native-paper';
9+
10+
type OtpInputProps = {
11+
maxLength: number;
12+
autoFocus?: boolean;
13+
onPinReady?: (pin: string) => void;
14+
};
15+
16+
export default function OtpInput({
17+
maxLength,
18+
onPinReady,
19+
autoFocus = true,
20+
}: OtpInputProps) {
21+
const [isInputBoxFocused, setIsInputBoxFocused] = React.useState(autoFocus);
22+
const [otp, setOtp] = React.useState('');
23+
const [isPinReady, setIsPinReady] = React.useState(false);
24+
const ref = React.useRef<RNTextInput>(null);
25+
26+
React.useEffect(() => {
27+
setIsPinReady(otp.length === maxLength);
28+
return () => {
29+
setIsPinReady(false);
30+
};
31+
}, [maxLength, otp, setIsPinReady]);
32+
33+
React.useEffect(() => {
34+
if (isPinReady) {
35+
onPinReady && onPinReady(otp);
36+
}
37+
}, [isPinReady, onPinReady, otp]);
38+
39+
const boxArray = new Array(maxLength).fill(0);
40+
const handleOnPress = () => {
41+
setIsInputBoxFocused(true);
42+
ref?.current?.focus();
43+
};
44+
45+
const handleOnBlur = () => {
46+
setIsInputBoxFocused(false);
47+
};
48+
return (
49+
<View style={styles.container}>
50+
<TextInput
51+
mode="outlined"
52+
style={styles.textInput}
53+
theme={{
54+
roundness: 10,
55+
}}
56+
value={otp}
57+
onChangeText={setOtp}
58+
maxLength={maxLength}
59+
ref={ref}
60+
onBlur={handleOnBlur}
61+
keyboardType="numeric"
62+
autoFocus={autoFocus}
63+
/>
64+
<Pressable style={styles.otpContainer} onPress={handleOnPress}>
65+
{boxArray.map((_, index) => {
66+
const isCurrentValue = index === otp.length;
67+
const isLastValue = index === maxLength - 1;
68+
const isCodeComplete = otp.length === maxLength;
69+
70+
const isValueFocused =
71+
isCurrentValue || (isLastValue && isCodeComplete);
72+
return (
73+
<View
74+
key={index}
75+
style={{
76+
...styles.otpBox,
77+
borderColor:
78+
isInputBoxFocused && isValueFocused ? '#6200EE' : '#F6F6F6',
79+
}}
80+
>
81+
<Text style={styles.otpText}>{otp[index] || ''}</Text>
82+
</View>
83+
);
84+
})}
85+
</Pressable>
86+
</View>
87+
);
88+
}
89+
90+
const styles = StyleSheet.create({
91+
container: {
92+
justifyContent: 'center',
93+
alignItems: 'center',
94+
paddingHorizontal: 0,
95+
},
96+
textInput: {
97+
position: 'absolute',
98+
opacity: 0,
99+
},
100+
otpContainer: {
101+
width: '100%',
102+
flexDirection: 'row',
103+
justifyContent: 'space-evenly',
104+
},
105+
otpBox: {
106+
backgroundColor: '#F6F6F6',
107+
borderWidth: 2,
108+
borderRadius: 5,
109+
padding: 12,
110+
maxWidth: 45,
111+
minWidth: 45,
112+
maxHeight: 45,
113+
minHeight: 45,
114+
justifyContent: 'center',
115+
},
116+
otpText: {
117+
fontSize: 15,
118+
color: 'black',
119+
textAlign: 'center',
120+
},
121+
});

src/index.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export function multiply(a: number, b: number): Promise<number> {
2-
return Promise.resolve(a * b);
3-
}
1+
import PaperOtpInput from './components/OtpInput';
2+
3+
export { PaperOtpInput };

yarn.lock

+69-5
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,18 @@ __metadata:
17391739
languageName: node
17401740
linkType: hard
17411741

1742+
"@callstack/react-theme-provider@npm:^3.0.9":
1743+
version: 3.0.9
1744+
resolution: "@callstack/react-theme-provider@npm:3.0.9"
1745+
dependencies:
1746+
deepmerge: ^3.2.0
1747+
hoist-non-react-statics: ^3.3.0
1748+
peerDependencies:
1749+
react: ">=16.3.0"
1750+
checksum: f888ef0b8f993d393a9d49864f75b04b5cf7259e72f290ec570fc5e314315f75c979b03b391243a72578ab55db4ffcdd4dc9dc1a2f8ec2b516cabb958971a85e
1751+
languageName: node
1752+
linkType: hard
1753+
17421754
"@commitlint/cli@npm:^17.8.1":
17431755
version: 17.8.1
17441756
resolution: "@commitlint/cli@npm:17.8.1"
@@ -6263,7 +6275,7 @@ __metadata:
62636275
languageName: node
62646276
linkType: hard
62656277

6266-
"color-convert@npm:^1.9.0":
6278+
"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3":
62676279
version: 1.9.3
62686280
resolution: "color-convert@npm:1.9.3"
62696281
dependencies:
@@ -6295,7 +6307,7 @@ __metadata:
62956307
languageName: node
62966308
linkType: hard
62976309

6298-
"color-string@npm:^1.9.0":
6310+
"color-string@npm:^1.6.0, color-string@npm:^1.9.0":
62996311
version: 1.9.1
63006312
resolution: "color-string@npm:1.9.1"
63016313
dependencies:
@@ -6305,6 +6317,16 @@ __metadata:
63056317
languageName: node
63066318
linkType: hard
63076319

6320+
"color@npm:^3.1.2":
6321+
version: 3.2.1
6322+
resolution: "color@npm:3.2.1"
6323+
dependencies:
6324+
color-convert: ^1.9.3
6325+
color-string: ^1.6.0
6326+
checksum: f81220e8b774d35865c2561be921f5652117638dcda7ca4029262046e37fc2444ac7bbfdd110cf1fd9c074a4ee5eda8f85944ffbdda26186b602dd9bb05f6400
6327+
languageName: node
6328+
linkType: hard
6329+
63086330
"color@npm:^4.2.3":
63096331
version: 4.2.3
63106332
resolution: "color@npm:4.2.3"
@@ -7381,6 +7403,13 @@ __metadata:
73817403
languageName: node
73827404
linkType: hard
73837405

7406+
"deepmerge@npm:^3.2.0":
7407+
version: 3.3.0
7408+
resolution: "deepmerge@npm:3.3.0"
7409+
checksum: 4322195389e0170a0443c07b36add19b90249802c4b47b96265fdc5f5d8beddf491d5e50cbc5bfd65f85ccf76598173083863c202f5463b3b667aca8be75d5ac
7410+
languageName: node
7411+
linkType: hard
7412+
73847413
"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0":
73857414
version: 4.3.1
73867415
resolution: "deepmerge@npm:4.3.1"
@@ -15460,9 +15489,11 @@ __metadata:
1546015489
react-dom: 18.2.0
1546115490
react-native: 0.74.1
1546215491
react-native-gesture-handler: ~2.16.1
15492+
react-native-paper: ^5.12.3
1546315493
react-native-reanimated: ~3.10.1
1546415494
react-native-safe-area-context: 4.10.1
1546515495
react-native-screens: 3.31.1
15496+
react-native-vector-icons: ^10.1.0
1546615497
react-native-web: ~0.19.10
1546715498
languageName: unknown
1546815499
linkType: soft
@@ -15487,6 +15518,8 @@ __metadata:
1548715518
react: 18.2.0
1548815519
react-native: 0.74.1
1548915520
react-native-builder-bob: ^0.20.0
15521+
react-native-paper: ^5.12.3
15522+
react-native-safe-area-context: ^4.10.1
1549015523
release-it: ^15.0.0
1549115524
typescript: ^5.2.2
1549215525
peerDependencies:
@@ -15495,6 +15528,22 @@ __metadata:
1549515528
languageName: unknown
1549615529
linkType: soft
1549715530

15531+
"react-native-paper@npm:^5.12.3":
15532+
version: 5.12.3
15533+
resolution: "react-native-paper@npm:5.12.3"
15534+
dependencies:
15535+
"@callstack/react-theme-provider": ^3.0.9
15536+
color: ^3.1.2
15537+
use-latest-callback: ^0.1.5
15538+
peerDependencies:
15539+
react: "*"
15540+
react-native: "*"
15541+
react-native-safe-area-context: "*"
15542+
react-native-vector-icons: "*"
15543+
checksum: 927a8948d220fb59cb4dae2f12f3b4dc2173b9cd42e38dc74a0f25584a69978555a260c140db973f8401f7fef394dee3ec42c90b6134deeb3aa462291fd0ce5f
15544+
languageName: node
15545+
linkType: hard
15546+
1549815547
"react-native-reanimated@npm:~3.10.1":
1549915548
version: 3.10.1
1550015549
resolution: "react-native-reanimated@npm:3.10.1"
@@ -15515,7 +15564,7 @@ __metadata:
1551515564
languageName: node
1551615565
linkType: hard
1551715566

15518-
"react-native-safe-area-context@npm:4.10.1":
15567+
"react-native-safe-area-context@npm:4.10.1, react-native-safe-area-context@npm:^4.10.1":
1551915568
version: 4.10.1
1552015569
resolution: "react-native-safe-area-context@npm:4.10.1"
1552115570
peerDependencies:
@@ -15538,6 +15587,21 @@ __metadata:
1553815587
languageName: node
1553915588
linkType: hard
1554015589

15590+
"react-native-vector-icons@npm:^10.1.0":
15591+
version: 10.1.0
15592+
resolution: "react-native-vector-icons@npm:10.1.0"
15593+
dependencies:
15594+
prop-types: ^15.7.2
15595+
yargs: ^16.1.1
15596+
bin:
15597+
fa-upgrade.sh: bin/fa-upgrade.sh
15598+
fa5-upgrade: bin/fa5-upgrade.sh
15599+
fa6-upgrade: bin/fa6-upgrade.sh
15600+
generate-icon: bin/generate-icon.js
15601+
checksum: c1e1d7c7ccc110e99caaed4d57324d5331dfa91817183350d2fd4df16d2302e7363da1f7a2b87011da3936b77aac3a7bf8ee11844dd8786b8b7dbc6553494866
15602+
languageName: node
15603+
linkType: hard
15604+
1554115605
"react-native-web@npm:~0.19.10":
1554215606
version: 0.19.11
1554315607
resolution: "react-native-web@npm:0.19.11"
@@ -18371,7 +18435,7 @@ __metadata:
1837118435
languageName: node
1837218436
linkType: hard
1837318437

18374-
"use-latest-callback@npm:^0.1.9":
18438+
"use-latest-callback@npm:^0.1.5, use-latest-callback@npm:^0.1.9":
1837518439
version: 0.1.9
1837618440
resolution: "use-latest-callback@npm:0.1.9"
1837718441
peerDependencies:
@@ -19215,7 +19279,7 @@ __metadata:
1921519279
languageName: node
1921619280
linkType: hard
1921719281

19218-
"yargs@npm:^16.2.0":
19282+
"yargs@npm:^16.1.1, yargs@npm:^16.2.0":
1921919283
version: 16.2.0
1922019284
resolution: "yargs@npm:16.2.0"
1922119285
dependencies:

0 commit comments

Comments
 (0)