diff --git a/apps/rebreak-native/app/(auth)/confirm-otp.tsx b/apps/rebreak-native/app/(auth)/confirm-otp.tsx
index 0b84132..d959567 100644
--- a/apps/rebreak-native/app/(auth)/confirm-otp.tsx
+++ b/apps/rebreak-native/app/(auth)/confirm-otp.tsx
@@ -4,14 +4,13 @@ import {
Text,
TextInput,
TouchableOpacity,
- KeyboardAvoidingView,
- Platform,
ActivityIndicator,
} from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../stores/auth';
+import { KeyboardAwareScreen } from '../../components/KeyboardAwareScreen';
const OTP_LENGTH = 6;
@@ -111,11 +110,7 @@ export default function ConfirmOtpScreen() {
return (
-
-
+
{/* Header */}
@@ -203,8 +198,7 @@ export default function ConfirmOtpScreen() {
>
{t('auth.backToSignup')}
-
-
+
);
}
diff --git a/apps/rebreak-native/app/(auth)/forgot-password.tsx b/apps/rebreak-native/app/(auth)/forgot-password.tsx
index da69940..6055c43 100644
--- a/apps/rebreak-native/app/(auth)/forgot-password.tsx
+++ b/apps/rebreak-native/app/(auth)/forgot-password.tsx
@@ -4,14 +4,13 @@ import {
Text,
TextInput,
TouchableOpacity,
- KeyboardAvoidingView,
- Platform,
ActivityIndicator,
} from 'react-native';
import { useRouter } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../stores/auth';
+import { KeyboardAwareScreen } from '../../components/KeyboardAwareScreen';
const INPUT_STYLE = {
fontSize: 16,
@@ -47,11 +46,7 @@ export default function ForgotPasswordScreen() {
return (
-
-
+
{t('auth.resetPasswordTitle')}
{t('auth.resetPasswordSubtitle')}
@@ -108,8 +103,7 @@ export default function ForgotPasswordScreen() {
>
{t('auth.backToLogin')}
-
-
+
);
}
diff --git a/apps/rebreak-native/app/(auth)/signin.tsx b/apps/rebreak-native/app/(auth)/signin.tsx
index 23b5eea..d8b7d32 100644
--- a/apps/rebreak-native/app/(auth)/signin.tsx
+++ b/apps/rebreak-native/app/(auth)/signin.tsx
@@ -4,9 +4,6 @@ import {
Text,
TextInput,
TouchableOpacity,
- KeyboardAvoidingView,
- Platform,
- ScrollView,
ActivityIndicator,
} from 'react-native';
import { useRouter } from 'expo-router';
@@ -14,6 +11,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Path } from 'react-native-svg';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../stores/auth';
+import { KeyboardAwareScreen } from '../../components/KeyboardAwareScreen';
function GoogleIcon() {
return (
@@ -85,15 +83,10 @@ export default function SignInScreen() {
return (
-
-
{t('auth.welcomeBack')}
{t('auth.signinSubtitle')}
@@ -197,8 +190,7 @@ export default function SignInScreen() {
{t('auth.signup')}
-
-
+
);
}
diff --git a/apps/rebreak-native/app/(auth)/signup.tsx b/apps/rebreak-native/app/(auth)/signup.tsx
index 111bd42..45d5428 100644
--- a/apps/rebreak-native/app/(auth)/signup.tsx
+++ b/apps/rebreak-native/app/(auth)/signup.tsx
@@ -4,9 +4,6 @@ import {
Text,
TextInput,
TouchableOpacity,
- KeyboardAvoidingView,
- Platform,
- ScrollView,
Image,
ActivityIndicator,
} from 'react-native';
@@ -16,6 +13,7 @@ import Svg, { Path } from 'react-native-svg';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../../stores/auth';
import { HERO_AVATARS, getAvatarUrl } from '../../lib/avatars';
+import { KeyboardAwareScreen } from '../../components/KeyboardAwareScreen';
function GoogleIcon() {
return (
@@ -109,15 +107,10 @@ export default function SignUpScreen() {
return (
-
-
{t('auth.signupTitle')}
{t('auth.signupSubtitle')}
@@ -300,8 +293,7 @@ export default function SignUpScreen() {
{t('auth.signin')}
-
-
+
);
}
diff --git a/apps/rebreak-native/app/profile/edit.tsx b/apps/rebreak-native/app/profile/edit.tsx
index ca83526..3b868ac 100644
--- a/apps/rebreak-native/app/profile/edit.tsx
+++ b/apps/rebreak-native/app/profile/edit.tsx
@@ -7,8 +7,6 @@ import {
ScrollView,
Image,
ActivityIndicator,
- KeyboardAvoidingView,
- Platform,
Alert,
} from 'react-native';
import { useRouter } from 'expo-router';
@@ -24,6 +22,7 @@ import { resolveAvatar } from '../../lib/resolveAvatar';
import { apiFetch } from '../../lib/api';
import { useMe } from '../../hooks/useMe';
import { AvatarCropSheet } from '../../components/profile/AvatarCropSheet';
+import { KeyboardAwareScreen } from '../../components/KeyboardAwareScreen';
export default function ProfileEditScreen() {
const router = useRouter();
@@ -133,10 +132,7 @@ export default function ProfileEditScreen() {
avatarId !== me?.avatar;
return (
-
+
-
+
);
}
diff --git a/apps/rebreak-native/components/KeyboardAwareScreen.tsx b/apps/rebreak-native/components/KeyboardAwareScreen.tsx
new file mode 100644
index 0000000..410dcf0
--- /dev/null
+++ b/apps/rebreak-native/components/KeyboardAwareScreen.tsx
@@ -0,0 +1,65 @@
+import { ReactNode } from 'react';
+import {
+ Keyboard,
+ KeyboardAvoidingView,
+ Platform,
+ ScrollView,
+ StyleProp,
+ TouchableWithoutFeedback,
+ View,
+ ViewStyle,
+} from 'react-native';
+
+export interface KeyboardAwareScreenProps {
+ children: ReactNode;
+ /**
+ * Extra offset for `keyboardVerticalOffset` on iOS. For screens wrapped in
+ * `` this should be 0 (default) — the SafeAreaView already
+ * absorbs the top inset. For screens that own their header with
+ * `paddingTop: insets.top` baked in (e.g. profile/edit), pass the full
+ * header height so iOS computes the correct push distance.
+ */
+ headerOffset?: number;
+ /**
+ * When true, wraps children in a `ScrollView`. Use for long forms (sign-up,
+ * profile-edit). When false (default), a plain `View` fills the remaining
+ * space — tap anywhere outside an input dismisses the keyboard.
+ */
+ scrollable?: boolean;
+ /** Style applied to the outer KeyboardAvoidingView. */
+ style?: StyleProp;
+ /** Style applied to the inner container (ScrollView or View). */
+ contentContainerStyle?: StyleProp;
+}
+
+export function KeyboardAwareScreen({
+ children,
+ headerOffset = 0,
+ scrollable = false,
+ style,
+ contentContainerStyle,
+}: KeyboardAwareScreenProps) {
+ return (
+
+ {scrollable ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ )}
+
+ );
+}