├── .gitignore ├── App.tsx ├── README.md ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── example.gif ├── package.json ├── src ├── components │ ├── ConnectionItem.tsx │ ├── ConnectionList.tsx │ ├── Header.tsx │ ├── HeaderOverlay.tsx │ └── TabBar.tsx ├── hooks │ └── useScrollSync.ts ├── mocks │ └── connections.ts ├── screens │ └── Profile.tsx └── types │ ├── Connection.ts │ ├── HeaderConfig.ts │ ├── ScrollPair.ts │ └── Visibility.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .expo 3 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationContainer } from "@react-navigation/native"; 2 | import React, { FC } from "react"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider } from "react-native-safe-area-context"; 5 | import Connection from "./src/screens/Profile"; 6 | 7 | const App: FC = () => ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | 15 | const styles = StyleSheet.create({ 16 | container: { 17 | flex: 1, 18 | backgroundColor: "white", 19 | }, 20 | }); 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-collapsing-tab-header 2 | 3 | Implementation of a Twitter/Instagram-like profile screen featuring swipable tabs and collapsing header. 4 | 5 | ### Created using: 6 | 7 | - React Native 8 | - React Navigation 9 | - React Native Reanimated 10 | 11 | ![](example.gif) 12 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "tab-header-animation-workshop", 4 | "slug": "tab-header-animation-workshop", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "android": { 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#FFFFFF" 26 | } 27 | }, 28 | "web": { 29 | "favicon": "./assets/favicon.png" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stormotion-Mobile/react-native-collapsing-tab-header/a7a059d11f874a8a8958ba9f552684e582d3d38c/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stormotion-Mobile/react-native-collapsing-tab-header/a7a059d11f874a8a8958ba9f552684e582d3d38c/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stormotion-Mobile/react-native-collapsing-tab-header/a7a059d11f874a8a8958ba9f552684e582d3d38c/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stormotion-Mobile/react-native-collapsing-tab-header/a7a059d11f874a8a8958ba9f552684e582d3d38c/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['react-native-reanimated/plugin'], 6 | }; 7 | }; -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stormotion-Mobile/react-native-collapsing-tab-header/a7a059d11f874a8a8958ba9f552684e582d3d38c/example.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@react-native-community/masked-view": "0.1.10", 12 | "@react-navigation/material-top-tabs": "^5.3.13", 13 | "@react-navigation/native": "^5.9.2", 14 | "expo": "~40.0.0", 15 | "expo-status-bar": "~1.0.3", 16 | "react": "16.13.1", 17 | "react-dom": "16.13.1", 18 | "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz", 19 | "react-native-gesture-handler": "~1.8.0", 20 | "react-native-reanimated": "2.0.0-rc.0", 21 | "react-native-safe-area-context": "3.1.9", 22 | "react-native-screens": "~2.15.2", 23 | "react-native-tab-view": "^2.15.2", 24 | "react-native-web": "~0.13.12" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "~7.9.0", 28 | "@types/react": "~16.9.35", 29 | "@types/react-dom": "~16.9.8", 30 | "@types/react-native": "~0.63.2", 31 | "typescript": "~4.0.0" 32 | }, 33 | "private": true 34 | } 35 | -------------------------------------------------------------------------------- /src/components/ConnectionItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, memo, useMemo } from "react"; 2 | import { Image, StyleSheet, Text, View, ViewProps } from "react-native"; 3 | import { Connection } from "../types/Connection"; 4 | 5 | export const PHOTO_SIZE = 40; 6 | 7 | type Props = Pick & { 8 | connection: Connection; 9 | }; 10 | 11 | const ConnectionItem: FC = ({ style, connection }) => { 12 | const { photo, name } = connection; 13 | 14 | const mergedStyle = useMemo(() => [styles.container, style], [style]); 15 | 16 | return ( 17 | 18 | 19 | {name} 20 | 21 | ); 22 | }; 23 | 24 | const styles = StyleSheet.create({ 25 | container: { alignItems: "center", flexDirection: "row", padding: 16 }, 26 | image: { 27 | height: PHOTO_SIZE, 28 | width: PHOTO_SIZE, 29 | borderRadius: PHOTO_SIZE / 2, 30 | }, 31 | name: { 32 | marginLeft: 8, 33 | fontSize: 15, 34 | fontWeight: "500", 35 | }, 36 | }); 37 | 38 | export default memo(ConnectionItem); 39 | -------------------------------------------------------------------------------- /src/components/ConnectionList.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, memo, useCallback } from "react"; 2 | import { 3 | FlatList, 4 | FlatListProps, 5 | ListRenderItem, 6 | StyleSheet, 7 | } from "react-native"; 8 | import Animated from "react-native-reanimated"; 9 | import ConnectionItem from "./ConnectionItem"; 10 | import { Connection } from "../types/Connection"; 11 | 12 | export const AnimatedFlatList: typeof FlatList = Animated.createAnimatedComponent( 13 | FlatList 14 | ); 15 | 16 | type Props = Omit, "renderItem">; 17 | 18 | const ConnectionList = forwardRef((props, ref) => { 19 | const keyExtractor = useCallback((_, index) => index.toString(), []); 20 | 21 | const renderItem = useCallback>( 22 | ({ item }) => , 23 | [] 24 | ); 25 | 26 | return ( 27 | 34 | ); 35 | }); 36 | 37 | const styles = StyleSheet.create({ 38 | container: { 39 | backgroundColor: "white", 40 | flex: 1, 41 | }, 42 | }); 43 | 44 | export default memo(ConnectionList); 45 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, memo, useMemo } from "react"; 2 | import { 3 | Image, 4 | ImageProps, 5 | StyleSheet, 6 | Text, 7 | View, 8 | ViewProps, 9 | } from "react-native"; 10 | 11 | export const PHOTO_SIZE = 120; 12 | 13 | type Props = Pick & { 14 | photo: string; 15 | name: string; 16 | bio: string; 17 | }; 18 | 19 | const Header: FC = ({ style, name, photo, bio }) => { 20 | const containerStyle = useMemo(() => [styles.container, style], []); 21 | 22 | const photoSource = useMemo(() => ({ uri: photo }), []); 23 | 24 | return ( 25 | 26 | 27 | 28 | {name} 29 | {bio} 30 | 31 | 32 | ); 33 | }; 34 | 35 | const styles = StyleSheet.create({ 36 | textContainer: { marginLeft: 24, justifyContent: "center", flex: 1 }, 37 | name: { fontSize: 24, fontWeight: "700" }, 38 | bio: { fontSize: 15, marginTop: 4 }, 39 | photo: { 40 | height: PHOTO_SIZE, 41 | width: PHOTO_SIZE, 42 | borderRadius: PHOTO_SIZE / 2, 43 | }, 44 | container: { 45 | flexDirection: "row", 46 | backgroundColor: "white", 47 | padding: 24, 48 | }, 49 | }); 50 | 51 | export default memo(Header); 52 | -------------------------------------------------------------------------------- /src/components/HeaderOverlay.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, memo, useMemo } from "react"; 2 | import { StyleSheet, Text, View, ViewProps } from "react-native"; 3 | 4 | type Props = Pick & { name: string }; 5 | 6 | const HeaderOverlay: FC = ({ style, name }) => { 7 | const containerStyle = useMemo(() => [styles.container, style], [style]); 8 | 9 | return ( 10 | 11 | {name} 12 | 13 | ); 14 | }; 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | alignItems: "center", 19 | }, 20 | title: { 21 | fontSize: 24, 22 | }, 23 | }); 24 | 25 | export default memo(HeaderOverlay); 26 | -------------------------------------------------------------------------------- /src/components/TabBar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | MaterialTopTabBar, 3 | MaterialTopTabBarProps, 4 | } from "@react-navigation/material-top-tabs"; 5 | import React, { FC, useEffect } from "react"; 6 | 7 | type Props = MaterialTopTabBarProps & { 8 | onIndexChange?: (index: number) => void; 9 | }; 10 | 11 | const TabBar: FC = ({ onIndexChange, ...props }) => { 12 | const { index } = props.state; 13 | 14 | useEffect(() => { 15 | onIndexChange?.(index); 16 | }, [onIndexChange, index]); 17 | 18 | return ; 19 | }; 20 | 21 | export default TabBar; 22 | -------------------------------------------------------------------------------- /src/hooks/useScrollSync.ts: -------------------------------------------------------------------------------- 1 | import { FlatListProps } from "react-native"; 2 | import { HeaderConfig } from "../types/HeaderConfig"; 3 | import { ScrollPair } from "../types/ScrollPair"; 4 | 5 | const useScrollSync = ( 6 | scrollPairs: ScrollPair[], 7 | headerConfig: HeaderConfig 8 | ) => { 9 | const sync: NonNullable["onMomentumScrollEnd"]> = ( 10 | event 11 | ) => { 12 | const { y } = event.nativeEvent.contentOffset; 13 | 14 | const { heightCollapsed, heightExpanded } = headerConfig; 15 | 16 | const headerDiff = heightExpanded - heightCollapsed; 17 | 18 | for (const { list, position } of scrollPairs) { 19 | const scrollPosition = position.value ?? 0; 20 | 21 | if (scrollPosition > headerDiff && y > headerDiff) { 22 | continue; 23 | } 24 | 25 | list.current?.scrollToOffset({ 26 | offset: Math.min(y, headerDiff), 27 | animated: false, 28 | }); 29 | } 30 | }; 31 | 32 | return { sync }; 33 | }; 34 | 35 | export default useScrollSync; 36 | -------------------------------------------------------------------------------- /src/mocks/connections.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from "../types/Connection"; 2 | 3 | export const FRIENDS: Connection[] = [ 4 | { 5 | name: "Sophie Brown", 6 | photo: "https://randomuser.me/api/portraits/women/1.jpg", 7 | }, 8 | { 9 | name: "William Garcia", 10 | photo: "https://randomuser.me/api/portraits/men/1.jpg", 11 | }, 12 | ]; 13 | 14 | export const SUGGESTIONS: Connection[] = [ 15 | { 16 | name: "Charlotte Jones", 17 | photo: "https://randomuser.me/api/portraits/women/2.jpg", 18 | }, 19 | { 20 | name: "Oliver Brown", 21 | photo: "https://randomuser.me/api/portraits/men/2.jpg", 22 | }, 23 | { 24 | name: "Jessica Miller", 25 | photo: "https://randomuser.me/api/portraits/women/3.jpg", 26 | }, 27 | { 28 | name: "Samuel Johnson", 29 | photo: "https://randomuser.me/api/portraits/men/3.jpg", 30 | }, 31 | { 32 | name: "Olivia Martinez", 33 | photo: "https://randomuser.me/api/portraits/women/4.jpg", 34 | }, 35 | { 36 | name: "Joshua Miller", 37 | photo: "https://randomuser.me/api/portraits/men/4.jpg", 38 | }, 39 | { 40 | name: "Katie Williams", 41 | photo: "https://randomuser.me/api/portraits/women/5.jpg", 42 | }, 43 | { 44 | name: "Jack Jones", 45 | photo: "https://randomuser.me/api/portraits/men/5.jpg", 46 | }, 47 | { 48 | name: "Amy Johnson", 49 | photo: "https://randomuser.me/api/portraits/women/6.jpg", 50 | }, 51 | { 52 | name: "Thomas Williams", 53 | photo: "https://randomuser.me/api/portraits/men/6.jpg", 54 | }, 55 | { 56 | name: "Abigail Hernandez", 57 | photo: "https://randomuser.me/api/portraits/women/7.jpg", 58 | }, 59 | { 60 | name: "Matthew Taylor", 61 | photo: "https://randomuser.me/api/portraits/men/7.jpg", 62 | }, 63 | { 64 | name: "Poppy Jackson", 65 | photo: "https://randomuser.me/api/portraits/women/8.jpg", 66 | }, 67 | { 68 | name: "Mohammed Lopez", 69 | photo: "https://randomuser.me/api/portraits/men/8.jpg", 70 | }, 71 | ]; 72 | -------------------------------------------------------------------------------- /src/screens/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createMaterialTopTabNavigator, 3 | MaterialTopTabBarProps, 4 | } from "@react-navigation/material-top-tabs"; 5 | import React, { FC, memo, useCallback, useMemo, useRef, useState } from "react"; 6 | import { 7 | FlatList, 8 | FlatListProps, 9 | StyleProp, 10 | StyleSheet, 11 | View, 12 | ViewProps, 13 | ViewStyle, 14 | Text, 15 | useWindowDimensions, 16 | } from "react-native"; 17 | import Animated, { 18 | interpolate, 19 | useAnimatedScrollHandler, 20 | useAnimatedStyle, 21 | useDerivedValue, 22 | useSharedValue, 23 | } from "react-native-reanimated"; 24 | import Header from "../components/Header"; 25 | import TabBar from "../components/TabBar"; 26 | import useScrollSync from "../hooks/useScrollSync"; 27 | import ConnectionList from "../components/ConnectionList"; 28 | import { Connection } from "../types/Connection"; 29 | import { ScrollPair } from "../types/ScrollPair"; 30 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 31 | import { FRIENDS, SUGGESTIONS } from "../mocks/connections"; 32 | import { HeaderConfig } from "../types/HeaderConfig"; 33 | import { Visibility } from "../types/Visibility"; 34 | import HeaderOverlay from "../components/HeaderOverlay"; 35 | 36 | const TAB_BAR_HEIGHT = 48; 37 | const HEADER_HEIGHT = 48; 38 | 39 | const OVERLAY_VISIBILITY_OFFSET = 32; 40 | 41 | const Tab = createMaterialTopTabNavigator(); 42 | 43 | const Profile: FC = () => { 44 | const { top, bottom } = useSafeAreaInsets(); 45 | 46 | const { height: screenHeight } = useWindowDimensions(); 47 | 48 | const friendsRef = useRef(null); 49 | const suggestionsRef = useRef(null); 50 | 51 | const [tabIndex, setTabIndex] = useState(0); 52 | 53 | const [headerHeight, setHeaderHeight] = useState(0); 54 | 55 | const defaultHeaderHeight = top + HEADER_HEIGHT; 56 | 57 | const headerConfig = useMemo( 58 | () => ({ 59 | heightCollapsed: defaultHeaderHeight, 60 | heightExpanded: headerHeight, 61 | }), 62 | [defaultHeaderHeight, headerHeight] 63 | ); 64 | 65 | const { heightCollapsed, heightExpanded } = headerConfig; 66 | 67 | const headerDiff = heightExpanded - heightCollapsed; 68 | 69 | const rendered = headerHeight > 0; 70 | 71 | const handleHeaderLayout = useCallback>( 72 | (event) => setHeaderHeight(event.nativeEvent.layout.height), 73 | [] 74 | ); 75 | 76 | const friendsScrollValue = useSharedValue(0); 77 | 78 | const friendsScrollHandler = useAnimatedScrollHandler( 79 | (event) => (friendsScrollValue.value = event.contentOffset.y) 80 | ); 81 | 82 | const suggestionsScrollValue = useSharedValue(0); 83 | 84 | const suggestionsScrollHandler = useAnimatedScrollHandler( 85 | (event) => (suggestionsScrollValue.value = event.contentOffset.y) 86 | ); 87 | 88 | const scrollPairs = useMemo( 89 | () => [ 90 | { list: friendsRef, position: friendsScrollValue }, 91 | { list: suggestionsRef, position: suggestionsScrollValue }, 92 | ], 93 | [friendsRef, friendsScrollValue, suggestionsRef, suggestionsScrollValue] 94 | ); 95 | 96 | const { sync } = useScrollSync(scrollPairs, headerConfig); 97 | 98 | const сurrentScrollValue = useDerivedValue( 99 | () => 100 | tabIndex === 0 ? friendsScrollValue.value : suggestionsScrollValue.value, 101 | [tabIndex, friendsScrollValue, suggestionsScrollValue] 102 | ); 103 | 104 | const translateY = useDerivedValue( 105 | () => -Math.min(сurrentScrollValue.value, headerDiff) 106 | ); 107 | 108 | const tabBarAnimatedStyle = useAnimatedStyle(() => ({ 109 | transform: [{ translateY: translateY.value }], 110 | })); 111 | 112 | const headerAnimatedStyle = useAnimatedStyle(() => ({ 113 | transform: [{ translateY: translateY.value }], 114 | opacity: interpolate( 115 | translateY.value, 116 | [-headerDiff, 0], 117 | [Visibility.Hidden, Visibility.Visible] 118 | ), 119 | })); 120 | 121 | const contentContainerStyle = useMemo>( 122 | () => ({ 123 | paddingTop: rendered ? headerHeight + TAB_BAR_HEIGHT : 0, 124 | paddingBottom: bottom, 125 | minHeight: screenHeight + headerDiff, 126 | }), 127 | [rendered, headerHeight, bottom, screenHeight, headerDiff] 128 | ); 129 | 130 | const sharedProps = useMemo>>( 131 | () => ({ 132 | contentContainerStyle, 133 | onMomentumScrollEnd: sync, 134 | onScrollEndDrag: sync, 135 | scrollEventThrottle: 16, 136 | scrollIndicatorInsets: { top: heightExpanded }, 137 | }), 138 | [contentContainerStyle, sync, heightExpanded] 139 | ); 140 | 141 | const renderFriends = useCallback( 142 | () => ( 143 | 149 | ), 150 | [friendsRef, friendsScrollHandler, sharedProps] 151 | ); 152 | 153 | const renderSuggestions = useCallback( 154 | () => ( 155 | 161 | ), 162 | [suggestionsRef, suggestionsScrollHandler, sharedProps] 163 | ); 164 | 165 | const tabBarStyle = useMemo>( 166 | () => [ 167 | rendered ? styles.tabBarContainer : undefined, 168 | { top: rendered ? headerHeight : undefined }, 169 | tabBarAnimatedStyle, 170 | ], 171 | [rendered, headerHeight, tabBarAnimatedStyle] 172 | ); 173 | 174 | const renderTabBar = useCallback< 175 | (props: MaterialTopTabBarProps) => React.ReactElement 176 | >( 177 | (props) => ( 178 | 179 | 180 | 181 | ), 182 | [tabBarStyle] 183 | ); 184 | 185 | const headerContainerStyle = useMemo>( 186 | () => [ 187 | rendered ? styles.headerContainer : undefined, 188 | { paddingTop: top }, 189 | headerAnimatedStyle, 190 | ], 191 | 192 | [rendered, top, headerAnimatedStyle] 193 | ); 194 | 195 | const collapsedOverlayAnimatedStyle = useAnimatedStyle(() => ({ 196 | opacity: interpolate( 197 | translateY.value, 198 | [-headerDiff, OVERLAY_VISIBILITY_OFFSET - headerDiff, 0], 199 | [Visibility.Visible, Visibility.Hidden, Visibility.Hidden] 200 | ), 201 | })); 202 | 203 | const collapsedOverlayStyle = useMemo>( 204 | () => [ 205 | styles.collapsedOvarlay, 206 | collapsedOverlayAnimatedStyle, 207 | { height: heightCollapsed, paddingTop: top }, 208 | ], 209 | [collapsedOverlayAnimatedStyle, heightCollapsed, top] 210 | ); 211 | 212 | return ( 213 | 214 | 215 |
220 | 221 | 222 | 223 | 224 | 225 | {renderFriends} 226 | {renderSuggestions} 227 | 228 | 229 | ); 230 | }; 231 | 232 | const styles = StyleSheet.create({ 233 | container: { 234 | flex: 1, 235 | backgroundColor: "white", 236 | }, 237 | tabBarContainer: { 238 | top: 0, 239 | left: 0, 240 | right: 0, 241 | position: "absolute", 242 | zIndex: 1, 243 | }, 244 | overlayName: { 245 | fontSize: 24, 246 | }, 247 | collapsedOvarlay: { 248 | position: "absolute", 249 | top: 0, 250 | left: 0, 251 | right: 0, 252 | backgroundColor: "white", 253 | justifyContent: "center", 254 | zIndex: 2, 255 | }, 256 | headerContainer: { 257 | top: 0, 258 | left: 0, 259 | right: 0, 260 | position: "absolute", 261 | zIndex: 1, 262 | }, 263 | }); 264 | 265 | export default memo(Profile); 266 | -------------------------------------------------------------------------------- /src/types/Connection.ts: -------------------------------------------------------------------------------- 1 | export type Connection = { 2 | photo: string; 3 | name: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/HeaderConfig.ts: -------------------------------------------------------------------------------- 1 | export type HeaderConfig = { 2 | heightExpanded: number; 3 | heightCollapsed: number; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/ScrollPair.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from "react"; 2 | import { FlatList } from "react-native"; 3 | import Animated from "react-native-reanimated"; 4 | 5 | export type ScrollPair = { 6 | list: RefObject; 7 | position: Animated.SharedValue; 8 | }; 9 | -------------------------------------------------------------------------------- /src/types/Visibility.ts: -------------------------------------------------------------------------------- 1 | export enum Visibility { 2 | Hidden = 0, 3 | Visible = 1, 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "strict": true 11 | } 12 | } 13 | --------------------------------------------------------------------------------