Skip to content

Commit 0ddfae5

Browse files
committed
feat: add example
1 parent d7f32f3 commit 0ddfae5

18 files changed

+1140
-261
lines changed

example/babel.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ module.exports = function (api) {
99

1010
return getConfig(
1111
{
12-
presets: ['babel-preset-expo'],
12+
presets: [
13+
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
14+
'nativewind/babel',
15+
],
1316
},
1417
{ root, pkg }
1518
);

example/global.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;

example/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { registerRootComponent } from 'expo';
2+
import './global.css';
23

34
import App from './src/App';
45

example/metro.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
const path = require('path');
22
const { getDefaultConfig } = require('@expo/metro-config');
33
const { getConfig } = require('react-native-builder-bob/metro-config');
4+
const { withNativeWind } = require('nativewind/metro');
5+
46
const pkg = require('../package.json');
57

68
const root = path.resolve(__dirname, '..');
79

10+
// nativewind config
11+
const config = getDefaultConfig(__dirname);
12+
const nativewindConfig = withNativeWind(config, { input: './global.css' });
813
/**
914
* Metro configuration
1015
* https://facebook.github.io/metro/docs/configuration
1116
*
1217
* @type {import('metro-config').MetroConfig}
1318
*/
14-
module.exports = getConfig(getDefaultConfig(__dirname), {
19+
module.exports = getConfig(nativewindConfig, {
1520
root,
1621
pkg,
1722
project: __dirname,

example/nativewind-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="nativewind/types" />

example/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
},
1111
"dependencies": {
1212
"@expo/metro-runtime": "~4.0.0",
13+
"clsx": "^2.1.1",
1314
"expo": "~52.0.20",
1415
"expo-status-bar": "~2.0.0",
16+
"nativewind": "^4.1.23",
1517
"react": "18.3.1",
1618
"react-dom": "18.3.1",
1719
"react-native": "0.76.5",
1820
"react-native-reanimated": "^3.16.6",
19-
"react-native-web": "~0.19.13"
21+
"react-native-web": "~0.19.13",
22+
"tailwind-merge": "^2.6.0",
23+
"tailwindcss": "^3.4.17"
2024
},
2125
"devDependencies": {
2226
"@babel/core": "^7.20.0",

example/src/App.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
1-
import { Text, View, StyleSheet } from 'react-native';
2-
import StripeOTPInput from './stripe';
1+
import { View, StyleSheet, ScrollView } from 'react-native';
2+
import StripeOTPInput from './examples/stripe';
33
import { Title } from './title';
4+
import AppleOTPInput from './examples/apple';
5+
import RevoltOTPInput from './examples/revolt';
6+
import DashedOTPInput from './examples/dashed';
7+
import StripeNativewind from './examples/stripe-nativewind';
48

59
export default function App() {
610
return (
7-
<View style={styles.container}>
8-
<Text>Hello World</Text>
11+
<ScrollView style={styles.container}>
912
<View style={styles.exampleContainer}>
1013
<Title>Stripe OTP Input</Title>
1114
<StripeOTPInput />
15+
<StripeNativewind />
1216
</View>
13-
</View>
17+
<View style={styles.exampleContainer}>
18+
<Title>Apple OTP Input</Title>
19+
<AppleOTPInput />
20+
</View>
21+
<View style={styles.exampleContainer}>
22+
<Title>Revolt OTP Input</Title>
23+
<RevoltOTPInput />
24+
</View>
25+
<View style={styles.exampleContainer}>
26+
<Title>Dashed OTP Input</Title>
27+
<DashedOTPInput />
28+
</View>
29+
</ScrollView>
1430
);
1531
}
1632

1733
const styles = StyleSheet.create({
1834
container: {
1935
flex: 1,
20-
alignItems: 'center',
21-
justifyContent: 'center',
36+
// justifyContent: 'center',
2237
},
2338
exampleContainer: {
24-
marginTop: 16,
39+
paddingVertical: 16,
40+
borderBottomWidth: 1,
41+
borderColor: '#E5E7EB',
42+
borderRadius: 8,
2543
},
2644
});

example/src/examples/apple.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { View, Text, StyleSheet, type ViewStyle } from 'react-native';
2+
import { OTPInput, type SlotProps } from 'input-otp-native';
3+
import type { OTPInputRef } from 'input-otp-native';
4+
import { useRef } from 'react';
5+
6+
import Animated, {
7+
useAnimatedStyle,
8+
withRepeat,
9+
withTiming,
10+
withSequence,
11+
useSharedValue,
12+
} from 'react-native-reanimated';
13+
import { useEffect } from 'react';
14+
15+
export default function AppleOTPInput() {
16+
const ref = useRef<OTPInputRef>(null);
17+
18+
return (
19+
<View>
20+
<OTPInput
21+
ref={ref}
22+
containerStyle={styles.container}
23+
maxLength={5}
24+
render={({ slots }) => (
25+
<View style={styles.slotsContainer}>
26+
{slots.map((slot, idx) => (
27+
<Slot key={idx} {...slot} />
28+
))}
29+
</View>
30+
)}
31+
/>
32+
</View>
33+
);
34+
}
35+
36+
function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
37+
return (
38+
<View style={[styles.slot, isActive && styles.activeSlot]}>
39+
{char !== null && <Text style={styles.char}>{char}</Text>}
40+
{hasFakeCaret && <FakeCaret />}
41+
</View>
42+
);
43+
}
44+
45+
function FakeCaret({ style }: { style?: ViewStyle }) {
46+
const opacity = useSharedValue(1);
47+
48+
useEffect(() => {
49+
opacity.value = withRepeat(
50+
withSequence(
51+
withTiming(0, { duration: 500 }),
52+
withTiming(1, { duration: 500 })
53+
),
54+
-1,
55+
true
56+
);
57+
}, [opacity]);
58+
59+
const animatedStyle = useAnimatedStyle(() => ({
60+
opacity: opacity.value,
61+
}));
62+
63+
return (
64+
<View style={styles.fakeCaretContainer}>
65+
<Animated.View style={[styles.fakeCaret, style, animatedStyle]} />
66+
</View>
67+
);
68+
}
69+
70+
const styles = StyleSheet.create({
71+
container: {
72+
flexDirection: 'row',
73+
alignItems: 'center',
74+
justifyContent: 'center',
75+
},
76+
slotsContainer: {
77+
flexDirection: 'row',
78+
gap: 8,
79+
},
80+
slot: {
81+
width: 50,
82+
height: 50,
83+
alignItems: 'center',
84+
justifyContent: 'center',
85+
borderWidth: 1,
86+
borderColor: '#E5E7EB',
87+
borderRadius: 8,
88+
backgroundColor: '#FFFFFF',
89+
},
90+
activeSlot: {
91+
borderColor: '#000000',
92+
borderWidth: 2,
93+
},
94+
char: {
95+
fontSize: 24,
96+
fontWeight: '500',
97+
color: '#111827',
98+
},
99+
/* Caret */
100+
fakeCaretContainer: {
101+
position: 'absolute',
102+
width: '100%',
103+
height: '100%',
104+
alignItems: 'center',
105+
justifyContent: 'center',
106+
},
107+
fakeCaret: {
108+
width: 2,
109+
height: 28,
110+
backgroundColor: '#000',
111+
borderRadius: 1,
112+
},
113+
});

example/src/examples/dashed.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { View, Text, StyleSheet, type ViewStyle } from 'react-native';
2+
import { OTPInput, type SlotProps } from 'input-otp-native';
3+
import type { OTPInputRef } from 'input-otp-native';
4+
import { useRef } from 'react';
5+
6+
import Animated, {
7+
useAnimatedStyle,
8+
withRepeat,
9+
withTiming,
10+
withSequence,
11+
useSharedValue,
12+
} from 'react-native-reanimated';
13+
import { useEffect } from 'react';
14+
15+
export default function DashedOTPInput() {
16+
const ref = useRef<OTPInputRef>(null);
17+
18+
return (
19+
<View>
20+
<OTPInput
21+
ref={ref}
22+
containerStyle={styles.container}
23+
maxLength={5}
24+
render={({ slots }) => (
25+
<View style={styles.slotsContainer}>
26+
{slots.map((slot, idx) => (
27+
<Slot key={idx} {...slot} />
28+
))}
29+
</View>
30+
)}
31+
/>
32+
</View>
33+
);
34+
}
35+
36+
function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
37+
return (
38+
<View style={styles.slot}>
39+
{char !== null && <Text style={styles.char}>{char}</Text>}
40+
{hasFakeCaret && <FakeCaret />}
41+
<View style={[styles.underline, isActive && styles.activeUnderline]} />
42+
</View>
43+
);
44+
}
45+
46+
function FakeCaret({ style }: { style?: ViewStyle }) {
47+
const opacity = useSharedValue(1);
48+
49+
useEffect(() => {
50+
opacity.value = withRepeat(
51+
withSequence(
52+
withTiming(0, { duration: 500 }),
53+
withTiming(1, { duration: 500 })
54+
),
55+
-1,
56+
true
57+
);
58+
}, [opacity]);
59+
60+
const animatedStyle = useAnimatedStyle(() => ({
61+
opacity: opacity.value,
62+
}));
63+
64+
return (
65+
<View style={styles.fakeCaretContainer}>
66+
<Animated.View style={[styles.fakeCaret, style, animatedStyle]} />
67+
</View>
68+
);
69+
}
70+
71+
const styles = StyleSheet.create({
72+
container: {
73+
flexDirection: 'row',
74+
alignItems: 'center',
75+
justifyContent: 'center',
76+
},
77+
slotsContainer: {
78+
flexDirection: 'row',
79+
alignItems: 'center',
80+
justifyContent: 'center',
81+
},
82+
slot: {
83+
width: 40,
84+
height: 40,
85+
marginHorizontal: 8,
86+
alignItems: 'center',
87+
justifyContent: 'center',
88+
},
89+
char: {
90+
fontSize: 24,
91+
fontWeight: '500',
92+
color: '#111827',
93+
},
94+
underline: {
95+
position: 'absolute',
96+
bottom: 0,
97+
width: '100%',
98+
height: 1,
99+
backgroundColor: '#E5E7EB',
100+
},
101+
activeUnderline: {
102+
backgroundColor: '#111827',
103+
height: 2,
104+
},
105+
fakeCaretContainer: {
106+
position: 'absolute',
107+
width: '100%',
108+
height: '100%',
109+
alignItems: 'center',
110+
justifyContent: 'center',
111+
},
112+
fakeCaret: {
113+
width: 2,
114+
height: 24,
115+
backgroundColor: '#111827',
116+
borderRadius: 1,
117+
},
118+
});

0 commit comments

Comments
 (0)