├── 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 | 
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
89 |
90 | ),
91 | }}
92 | />
93 | (
103 |
112 | ),
113 | tabBarLabel: () => (
114 | Settings
115 | ),
116 | }}
117 | />
118 |
119 | );
120 | };
121 |
--------------------------------------------------------------------------------