├── .npmrc ├── hooks ├── useColorScheme.ts ├── useColorScheme.web.ts ├── useGradualAnimation.ts └── useThemeColor.ts ├── bun.lockb ├── assets ├── images │ ├── icon.png │ ├── favicon.png │ ├── splash.png │ ├── react-logo.png │ ├── adaptive-icon.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ └── partial-react-logo.png └── fonts │ └── SpaceMono-Regular.ttf ├── expo-env.d.ts ├── babel.config.js ├── tsconfig.json ├── components ├── __tests__ │ ├── ThemedText-test.tsx │ └── __snapshots__ │ │ └── ThemedText-test.tsx.snap ├── navigation │ └── TabBarIcon.tsx ├── ThemedView.tsx ├── ExternalLink.tsx ├── HelloWave.tsx ├── Collapsible.tsx ├── ThemedText.tsx ├── MessageItem.tsx └── ParallaxScrollView.tsx ├── .gitignore ├── constants └── Colors.ts ├── app ├── +not-found.tsx ├── index.tsx ├── _layout.tsx ├── +html.tsx ├── advanced.tsx ├── advanced-toolbar.tsx ├── view-avoiding.tsx └── basic.tsx ├── app.json ├── package.json ├── README.md ├── scripts └── reset-project.js └── messages.ts /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from 'react-native'; 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/bun.lockb -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/react-logo.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /expo-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be in your git ignore -------------------------------------------------------------------------------- /assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/keyboard-guide/HEAD/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /components/__tests__/ThemedText-test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import { ThemedText } from '../ThemedText'; 5 | 6 | it(`renders correctly`, () => { 7 | const tree = renderer.create(Snapshot test!).toJSON(); 8 | 9 | expect(tree).toMatchSnapshot(); 10 | }); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | /ios 14 | /android 15 | 16 | # macOS 17 | .DS_Store 18 | 19 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 20 | # The following patterns were generated by expo-cli 21 | 22 | expo-env.d.ts 23 | # @end expo-cli -------------------------------------------------------------------------------- /components/__tests__/__snapshots__/ThemedText-test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 22 | Snapshot test! 23 | 24 | `; 25 | -------------------------------------------------------------------------------- /components/navigation/TabBarIcon.tsx: -------------------------------------------------------------------------------- 1 | // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ 2 | 3 | import Ionicons from '@expo/vector-icons/Ionicons'; 4 | import { type IconProps } from '@expo/vector-icons/build/createIconSet'; 5 | import { type ComponentProps } from 'react'; 6 | 7 | export function TabBarIcon({ style, ...rest }: IconProps['name']>) { 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | // NOTE: The default React Native styling doesn't support server rendering. 2 | // Server rendered styles should not change between the first render of the HTML 3 | // and the first render on the client. Typically, web developers will use CSS media queries 4 | // to render different styles on the client and server, these aren't directly supported in React Native 5 | // but can be achieved using a styling library like Nativewind. 6 | export function useColorScheme() { 7 | return 'light'; 8 | } 9 | -------------------------------------------------------------------------------- /components/ThemedView.tsx: -------------------------------------------------------------------------------- 1 | import { View, type ViewProps } from 'react-native'; 2 | 3 | import { useThemeColor } from '@/hooks/useThemeColor'; 4 | 5 | export type ThemedViewProps = ViewProps & { 6 | lightColor?: string; 7 | darkColor?: string; 8 | }; 9 | 10 | export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { 11 | const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); 12 | 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /hooks/useGradualAnimation.ts: -------------------------------------------------------------------------------- 1 | import { useKeyboardHandler } from "react-native-keyboard-controller"; 2 | import { useSharedValue } from "react-native-reanimated"; 3 | 4 | const OFFSET = 42; 5 | 6 | export const useGradualAnimation = () => { 7 | const totalOffset = OFFSET; 8 | 9 | const height = useSharedValue(totalOffset); 10 | 11 | useKeyboardHandler( 12 | { 13 | onMove: (e) => { 14 | "worklet"; 15 | height.value = 16 | e.height > 0 ? Math.max(e.height + OFFSET, totalOffset) : totalOffset; 17 | }, 18 | }, 19 | [] 20 | ); 21 | return { height }; 22 | }; 23 | -------------------------------------------------------------------------------- /hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { useColorScheme } from 'react-native'; 7 | 8 | import { Colors } from '@/constants/Colors'; 9 | 10 | export function useThemeColor( 11 | props: { light?: string; dark?: string }, 12 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark 13 | ) { 14 | const theme = useColorScheme() ?? 'light'; 15 | const colorFromProps = props[theme]; 16 | 17 | if (colorFromProps) { 18 | return colorFromProps; 19 | } else { 20 | return Colors[theme][colorName]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'expo-router'; 2 | import { openBrowserAsync } from 'expo-web-browser'; 3 | import { type ComponentProps } from 'react'; 4 | import { Platform } from 'react-native'; 5 | 6 | type Props = Omit, 'href'> & { href: string }; 7 | 8 | export function ExternalLink({ href, ...rest }: Props) { 9 | return ( 10 | { 15 | if (Platform.OS !== 'web') { 16 | // Prevent the default behavior of linking to the default browser on native. 17 | event.preventDefault(); 18 | // Open the link in an in-app browser. 19 | await openBrowserAsync(href); 20 | } 21 | }} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = '#0a7ea4'; 7 | const tintColorDark = '#fff'; 8 | 9 | export const Colors = { 10 | light: { 11 | text: '#11181C', 12 | background: '#fff', 13 | tint: tintColorLight, 14 | icon: '#687076', 15 | tabIconDefault: '#687076', 16 | tabIconSelected: tintColorLight, 17 | }, 18 | dark: { 19 | text: '#ECEDEE', 20 | background: '#151718', 21 | tint: tintColorDark, 22 | icon: '#9BA1A6', 23 | tabIconDefault: '#9BA1A6', 24 | tabIconSelected: tintColorDark, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from 'expo-router'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | import { ThemedText } from '@/components/ThemedText'; 5 | import { ThemedView } from '@/components/ThemedView'; 6 | 7 | export default function NotFoundScreen() { 8 | return ( 9 | <> 10 | 11 | 12 | This screen doesn't exist. 13 | 14 | Go to home screen! 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | container: { 23 | flex: 1, 24 | alignItems: 'center', 25 | justifyContent: 'center', 26 | padding: 20, 27 | }, 28 | link: { 29 | marginTop: 15, 30 | paddingVertical: 15, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /components/HelloWave.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import Animated, { 3 | useSharedValue, 4 | useAnimatedStyle, 5 | withTiming, 6 | withRepeat, 7 | withSequence, 8 | } from 'react-native-reanimated'; 9 | 10 | import { ThemedText } from '@/components/ThemedText'; 11 | 12 | export function HelloWave() { 13 | const rotationAnimation = useSharedValue(0); 14 | 15 | rotationAnimation.value = withRepeat( 16 | withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), 17 | 4 // Run the animation 4 times 18 | ); 19 | 20 | const animatedStyle = useAnimatedStyle(() => ({ 21 | transform: [{ rotate: `${rotationAnimation.value}deg` }], 22 | })); 23 | 24 | return ( 25 | 26 | 👋 27 | 28 | ); 29 | } 30 | 31 | const styles = StyleSheet.create({ 32 | text: { 33 | fontSize: 28, 34 | lineHeight: 32, 35 | marginTop: -6, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "keyboard-handling", 4 | "slug": "keyboard-handling", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "newArchEnabled": true, 11 | "splash": { 12 | "image": "./assets/images/splash.png", 13 | "resizeMode": "contain", 14 | "backgroundColor": "#ffffff" 15 | }, 16 | "ios": { 17 | "supportsTablet": true, 18 | "bundleIdentifier": "dev.expo.keyboard.guide" 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/images/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | }, 25 | "softwareKeyboardLayoutMode": "pan", 26 | "package": "dev.expo.keyboard.guide" 27 | }, 28 | "web": { 29 | "bundler": "metro", 30 | "output": "static", 31 | "favicon": "./assets/images/favicon.png" 32 | }, 33 | "plugins": ["expo-router", "expo-font", "expo-web-browser"], 34 | "experiments": { 35 | "typedRoutes": true 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, SafeAreaView, Button } from "react-native"; 2 | 3 | import { Link } from "expo-router"; 4 | 5 | export default function IndexScreen() { 6 | return ( 7 | 8 | 9 |