├── assets ├── icon.png ├── test.png ├── splash.png ├── favicon.png └── adaptive-icon.png ├── babel.config.js ├── .gitignore ├── src ├── screens │ ├── Profile │ │ └── index.jsx │ ├── Settings │ │ └── index.jsx │ └── Home │ │ └── index.jsx └── navigator │ └── tabbar │ ├── curve.js │ └── index.jsx ├── App.js ├── package.json ├── app.json └── README.md /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/HEAD/assets/test.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.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 | # macOS 14 | .DS_Store 15 | 16 | # Temporary files created by Metro to check the health of the file watcher 17 | .metro-health-check* 18 | -------------------------------------------------------------------------------- /src/screens/Profile/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, View, StyleSheet } from "react-native"; 3 | 4 | function Profile() { 5 | return ( 6 | 7 | Profile 8 | 9 | ); 10 | } 11 | 12 | export default Profile; 13 | const styles = StyleSheet.create({ 14 | container: { 15 | flex: 1, 16 | backgroundColor: "#818CF8", 17 | alignItems: "center", 18 | justifyContent: "center", 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/screens/Settings/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, View, StyleSheet } from "react-native"; 3 | 4 | function ProfileSettings() { 5 | return ( 6 | 7 | Setting 8 | 9 | ); 10 | } 11 | 12 | export default ProfileSettings; 13 | const styles = StyleSheet.create({ 14 | container: { 15 | flex: 1, 16 | backgroundColor: "#34D399", 17 | alignItems: "center", 18 | justifyContent: "center", 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { SafeAreaView, StyleSheet } from "react-native"; 2 | import { NavigationContainer } from "@react-navigation/native"; 3 | import { BottomTabNavigator } from "./src/navigator/tabbar"; 4 | 5 | export default function App() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | const styles = StyleSheet.create({ 16 | droidSafeArea: { 17 | flex: 1, 18 | backgroundColor: "white", 19 | paddingTop: Platform.OS === "android" ? 35 : 0, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-curved-bottom-bar", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@react-navigation/bottom-tabs": "^6.5.7", 13 | "@react-navigation/native": "^6.1.6", 14 | "d3-shape": "^3.2.0", 15 | "expo": "~48.0.10", 16 | "expo-status-bar": "~1.4.4", 17 | "react": "18.2.0", 18 | "react-native": "0.71.6", 19 | "react-native-size-scaling": "^0.5.1", 20 | "react-native-svg": "13.4.0" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.20.0" 24 | }, 25 | "private": true 26 | } 27 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-curved-bottom-bar", 4 | "slug": "react-native-curved-bottom-bar", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Curved Bottom Tab Bar` 2 | 3 | ![image](https://raw.githubusercontent.com/alperbayram/react-native-curved-bottom-bar/main/assets/test.png?token=GHSAT0AAAAAABZ6P726CSX4F35Y7CLSPVOOZBR5CEA) 4 | 5 |

6 | 7 | 8 | Supports Expo iOS 9 | 10 | 11 | 12 | Supports Expo Android 13 | 14 | 15 | 16 | Supports Expo Web 17 | 18 |

19 | 20 | 21 | ## 🚀 How to use 22 | 23 | - Run `npm install` 24 | - Run `npm run start` 25 | 26 | ## 📝 Medium 27 | 28 | - [React Nativede Curved Bottom Tab Bar Nasıl Yapılır ?][rne] 29 | 30 | [rne]: https://alper-bayram.medium.com/react-nativede-curved-bottom-tab-bar-nas%C4%B1l-yap%C4%B1l%C4%B1r-1db66142842f 31 | -------------------------------------------------------------------------------- /src/screens/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, View, StyleSheet, SectionList } from "react-native"; 3 | const DATA = [ 4 | { 5 | title: "Main dishes", 6 | data: ["Pizza", "Burger", "Risotto"], 7 | }, 8 | { 9 | title: "Sides", 10 | data: ["French Fries", "Onion Rings", "Fried Shrimps"], 11 | }, 12 | { 13 | title: "Drinks", 14 | data: ["Water", "Coke", "Beer"], 15 | }, 16 | { 17 | title: "Desserts", 18 | data: ["Cheese Cake", "Ice Cream"], 19 | }, 20 | ]; 21 | 22 | function Home() { 23 | return ( 24 | 25 | item + index} 28 | renderItem={({ item }) => ( 29 | 30 | {item} 31 | 32 | )} 33 | renderSectionHeader={({ section: { title } }) => ( 34 | {title} 35 | )} 36 | /> 37 | 38 | ); 39 | } 40 | 41 | export default Home; 42 | 43 | const styles = StyleSheet.create({ 44 | container: { 45 | flex: 1, 46 | backgroundColor: "white", 47 | }, 48 | item: { 49 | backgroundColor: "#22D3EE", 50 | padding: 20, 51 | marginVertical: 8, 52 | }, 53 | header: { 54 | fontSize: 32, 55 | backgroundColor: "#fff", 56 | }, 57 | title: { 58 | fontSize: 24, 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /src/navigator/tabbar/curve.js: -------------------------------------------------------------------------------- 1 | import * as shape from "d3-shape"; 2 | import { scale } from "react-native-size-scaling"; 3 | 4 | //** Path Line */ 5 | const line = (width, height) => { 6 | const path = shape 7 | .line() 8 | .x((d) => d.x) 9 | .y((d) => d.y)([ 10 | { x: width / 2, y: 0 }, 11 | { x: width, y: 0 }, 12 | { x: width, y: height }, 13 | { x: 0, y: height }, 14 | { x: 0, y: 0 }, 15 | { x: width / 2, y: 0 }, 16 | ]); 17 | 18 | return path; 19 | }; 20 | 21 | //** Path Curved*/ 22 | const lineCurvedDown = (iPosition, height, circle) => { 23 | const position = iPosition; 24 | const circleWidth = circle + position; 25 | const trim = (position + circleWidth) / 2; 26 | 27 | const curved = shape 28 | .line() 29 | .x((d) => d.x) 30 | .y((d) => d.y) 31 | .curve(shape.curveBasis)([ 32 | { x: position - scale(20), y: 0 }, // border center left 33 | { x: position - scale(10), y: scale(2) }, 34 | { x: position - scale(2), y: scale(10) }, 35 | { x: position, y: scale(17) }, 36 | 37 | { x: trim - scale(25), y: height / 2 + scale(2) }, 38 | { x: trim - scale(10), y: height / 2 + scale(10) }, 39 | { x: trim, y: height / 2 + scale(10) }, 40 | { x: trim + scale(10), y: height / 2 + scale(10) }, 41 | { x: trim + scale(25), y: height / 2 + scale(2) }, 42 | 43 | { x: circleWidth, y: scale(17) }, // border center right 44 | { x: circleWidth + scale(2), y: scale(10) }, 45 | { x: circleWidth + scale(10), y: 0 }, 46 | { x: circleWidth + scale(20), y: 0 }, 47 | ]); 48 | 49 | return curved; 50 | }; 51 | 52 | export const getPathDown = (width, iHeight, centerWidth) => { 53 | const height = scale(iHeight); 54 | const circleWidth = scale(centerWidth) + scale(16); 55 | return `${line(width, height)} ${lineCurvedDown( 56 | width / 2 - circleWidth / 2, 57 | height, 58 | circleWidth 59 | )}`; 60 | }; 61 | -------------------------------------------------------------------------------- /src/navigator/tabbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; 3 | import { Text, View, Dimensions, Image } from "react-native"; 4 | import Home from "../../screens/Home"; 5 | import Profile from "../../screens/Profile"; 6 | import ProfileSettings from "../../screens/Settings"; 7 | import { getPathDown } from "./curve"; 8 | import { Svg, Path } from "react-native-svg"; 9 | import { scale } from "react-native-size-scaling"; 10 | 11 | const Tab = createBottomTabNavigator(); 12 | export const BottomTabNavigator = () => { 13 | const [maxWidth, setMaxWidth] = useState(Dimensions.get("window").width); 14 | const returnpathDown = getPathDown(maxWidth, 60, 50); 15 | return ( 16 | 26 | ( 36 | 45 | ), 46 | tabBarLabel: () => ( 47 | Profile 48 | ), 49 | }} 50 | /> 51 | ( 62 | 73 | 82 | 83 | ), 84 | tabBarLabel: () => ( 85 | 86 | 87 | 88 | 89 | 90 | ), 91 | }} 92 | /> 93 | ( 103 | 112 | ), 113 | tabBarLabel: () => ( 114 | Settings 115 | ), 116 | }} 117 | /> 118 | 119 | ); 120 | }; 121 | --------------------------------------------------------------------------------