Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions docs/src/content/docs/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,47 @@ We create a few examples that you can copy paste and use in your project.

## SlotProps

| Prop | Type | Description |
| ----------------- | -------------- | -------------------------- |
| `char` | string \| null | Character in the slot |
| `isActive` | boolean | Whether the slot is active |
| `hasFakeCaret` | boolean | Whether to show fake caret |
| `placeholderChar` | string \| null | Placeholder character |
| Prop | Type | Description |
| ----------------- | -------------- | ------------------------------------------------------------------------- |
| `char` | string \| null | Character in the slot |
| `isActive` | boolean | Whether the slot is active |
| `hasFakeCaret` | boolean | Whether to show fake caret |
| `placeholderChar` | string \| null | Placeholder character |
| `focus` | () => void | Focuses the input at this slot's position, suppressing iOS clear behavior |

Each slot exposes a `focus()` method — no ref required. Pass it to `onPress` on a wrapping `Pressable` to let users tap any slot and resume typing from there:

```tsx
<OTPInput
maxLength={6}
render={({ slots }) => (
<View style={{ flexDirection: 'row', gap: 8 }}>
{slots.map((slot, index) => (
<Pressable key={index} onPress={slot.focus}>
<Slot {...slot} />
</Pressable>
))}
</View>
)}
/>
```

## OTPInputRef

Use a `ref` to call imperative methods on the input:

```tsx
const ref = useRef<OTPInputRef>(null);
<OTPInput ref={ref} ... />
```

| Method | Description |
| -------------------------- | --------------------------------------------------------------------------------------------- |
| `focus()` | Focus the input |
| `blur()` | Blur the input |
| `clear()` | Clear all digits |
| `setValue(value: string)` | Set the current value programmatically |
| `focusSlot(index: number)` | Truncate the value to `index` characters and focus — making slot `index` the new active slot |

## Web support

Expand Down
22 changes: 15 additions & 7 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import AppleNativewind from './examples/apple-nativewind';
import DashedNativewind from './examples/dashed-nativewind';
import AnimatedDashedNativewind from './examples/animated-dashed-nativewind';
import AnimatedStripeOTPInput from './examples/animated-stripe-nativewind';
import FocusSlotExample from './examples/focus-slot';

type TabType = 'regular' | 'nativewind';

Expand All @@ -30,6 +31,13 @@ export default function App() {
nativewind: StripeNativewind,
color: '#6772E5',
},
{
title: 'Animated Stripe',
description: 'Stripe style with slide-in animation',
regular: AnimatedStripeOTPInput,
nativewind: AnimatedStripeOTPInput,
color: '#8B5CF6',
},
{
title: 'Apple',
description: 'iOS-style with rounded corners and shadows',
Expand Down Expand Up @@ -59,11 +67,11 @@ export default function App() {
color: '#8B5CF6',
},
{
title: 'Animated Stripe',
description: 'Stripe style with slide-in animation',
regular: AnimatedStripeOTPInput,
nativewind: AnimatedStripeOTPInput,
color: '#8B5CF6',
title: 'Focus Slot',
description: 'Tap any slot to focus it using slot.focus()',
regular: FocusSlotExample,
nativewind: FocusSlotExample,
color: '#10B981',
},
];

Expand Down Expand Up @@ -116,14 +124,14 @@ export default function App() {
className="mx-5 mt-5 bg-white rounded-2xl shadow-sm"
>
<View className="flex-row items-center px-4 py-2 border-b border-slate-200">
<View
{/* <View
className="w-10 h-10 rounded-full items-center justify-center mr-3"
style={{ backgroundColor: example.color }}
>
<Text className="text-white text-lg font-bold">
{example.title[0]}
</Text>
</View>
</View> */}
<View className="flex-1">
<Text className="text-lg font-semibold text-slate-800 mb-1">
{example.title}
Expand Down
11 changes: 7 additions & 4 deletions example/src/examples/animated-dashed-nativewind.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Text, Alert } from 'react-native';
import { View, Text, Alert, Pressable } from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand Down Expand Up @@ -39,9 +39,12 @@ export default function AnimatedDashedOTPInput() {
);
}

function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
function Slot({ char, isActive, hasFakeCaret, focus }: SlotProps) {
return (
<View className="w-12 h-12 mx-2 items-center justify-center">
<Pressable
onPress={focus}
className="w-12 h-12 mx-2 items-center justify-center"
>
{char !== null && (
<Animated.View
entering={FadeInDown.springify()
Expand All @@ -59,7 +62,7 @@ function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
'bg-gray-900 h-0.5': isActive,
})}
/>
</View>
</Pressable>
);
}

Expand Down
50 changes: 25 additions & 25 deletions example/src/examples/animated-stripe-nativewind.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Text, Alert } from 'react-native';
import { View, Text, Alert, Pressable } from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand All @@ -18,46 +18,46 @@ export default function AnimatedStripeOTPInput() {
const ref = useRef<OTPInputRef>(null);
const onComplete = (code: string) => {
Alert.alert('Completed with code:', code);
ref.current?.clear();
// ref.current?.clear();
};

return (
<View>
<OTPInput
ref={ref}
onComplete={onComplete}
maxLength={6}
render={({ slots }) => (
<View className="flex-1 flex-row items-center justify-center my-4">
<View className="flex-row">
{slots.slice(0, 3).map((slot, idx) => (
<Slot key={idx} {...slot} index={idx} />
))}
</View>
<FakeDash />
<View className="flex-row">
{slots.slice(3).map((slot, idx) => (
<Slot key={idx} {...slot} index={idx + 3} />
))}
</View>
<OTPInput
ref={ref}
onComplete={onComplete}
maxLength={6}
render={({ slots }) => (
<View className="flex-1 flex-row items-center justify-center my-4">
<View className="flex-row">
{slots.slice(0, 3).map((slot, idx) => (
<Slot key={idx} {...slot} index={idx} />
))}
</View>
)}
/>
</View>
<FakeDash />
<View className="flex-row">
{slots.slice(3).map((slot, idx) => (
<Slot key={idx} {...slot} index={idx + 3} />
))}
</View>
</View>
)}
/>
);
}

function Slot({
char,
isActive,
hasFakeCaret,
focus,
index,
}: SlotProps & { index: number }) {
const isFirst = index === 0;
const isLast = index === 2 || index === 5;

return (
<View
<Pressable
onPress={focus}
className={cn(
`w-12 h-16 items-center justify-center bg-gray-50 relative`,
'border border-gray-200',
Expand All @@ -80,7 +80,7 @@ function Slot({
</Animated.View>
)}
{hasFakeCaret && <FakeCaret />}
</View>
</Pressable>
);
}

Expand Down
9 changes: 5 additions & 4 deletions example/src/examples/apple-nativewind.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Text, Alert } from 'react-native';
import { View, Text, Alert, Pressable } from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand Down Expand Up @@ -36,9 +36,10 @@ export default function AppleOTPInput() {
);
}

function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
function Slot({ char, isActive, hasFakeCaret, focus }: SlotProps) {
return (
<View
<Pressable
onPress={focus}
className={cn(
'w-[50px] h-[50px] items-center justify-center border border-gray-200 rounded-lg bg-white',
{
Expand All @@ -50,7 +51,7 @@ function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
<Text className="text-2xl font-medium text-gray-900">{char}</Text>
)}
{hasFakeCaret && <FakeCaret />}
</View>
</Pressable>
);
}

Expand Down
18 changes: 14 additions & 4 deletions example/src/examples/apple.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { View, Text, StyleSheet, type ViewStyle, Alert } from 'react-native';
import {
View,
Text,
StyleSheet,
type ViewStyle,
Alert,
Pressable,
} from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand Down Expand Up @@ -38,12 +45,15 @@ export default function AppleOTPInput() {
);
}

function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
function Slot({ char, isActive, hasFakeCaret, focus }: SlotProps) {
return (
<View style={[styles.slot, isActive && styles.activeSlot]}>
<Pressable
onPress={focus}
style={[styles.slot, isActive && styles.activeSlot]}
>
{char !== null && <Text style={styles.char}>{char}</Text>}
{hasFakeCaret && <FakeCaret />}
</View>
</Pressable>
);
}

Expand Down
11 changes: 7 additions & 4 deletions example/src/examples/dashed-nativewind.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Text, Alert } from 'react-native';
import { View, Text, Alert, Pressable } from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand Down Expand Up @@ -38,9 +38,12 @@ export default function DashedOTPInput() {
);
}

function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
function Slot({ char, isActive, hasFakeCaret, focus }: SlotProps) {
return (
<View className="w-12 h-12 mx-2 items-center justify-center">
<Pressable
onPress={focus}
className="w-12 h-12 mx-2 items-center justify-center"
>
{char !== null && (
<Text className="text-3xl font-medium text-gray-900">{char}</Text>
)}
Expand All @@ -50,7 +53,7 @@ function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
'bg-gray-900 h-0.5': isActive,
})}
/>
</View>
</Pressable>
);
}

Expand Down
15 changes: 11 additions & 4 deletions example/src/examples/dashed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { View, Text, StyleSheet, type ViewStyle, Alert } from 'react-native';
import {
View,
Text,
StyleSheet,
type ViewStyle,
Alert,
Pressable,
} from 'react-native';
import { OTPInput, type SlotProps } from 'input-otp-native';
import type { OTPInputRef } from 'input-otp-native';
import { useRef } from 'react';
Expand Down Expand Up @@ -38,13 +45,13 @@ export default function DashedOTPInput() {
);
}

function Slot({ char, isActive, hasFakeCaret }: SlotProps) {
function Slot({ char, isActive, hasFakeCaret, focus }: SlotProps) {
return (
<View style={styles.slot}>
<Pressable onPress={focus} style={styles.slot}>
{char !== null && <Text style={styles.char}>{char}</Text>}
{hasFakeCaret && <FakeCaret />}
<View style={[styles.underline, isActive && styles.activeUnderline]} />
</View>
</Pressable>
);
}

Expand Down
Loading