Repository: Learnathon-By-Geeky-Solutions/ethereal-trident
Pull request #17: feature: Design tabbar as figma file
Author: login: nullscribe
Created: 2025-03-08T11:28:01Z | Merged: 2025-03-08T13:39:50Z

## PR Description
Previously designed tabbar was did not have create link and did not have the cutout design. This new tabbar is same as figma file.

## Related Issues
Closes #14 .

## Summary of Changes

List the key changes made in this PR:

- [x] New Tabbar Design
- [x] It looks exactly like figma file.
- [x] It has new create button.

## Screenshot
![image](https://github.com/user-attachments/assets/53e2cc91-d7f6-44ba-a293-c919646609fe)


Linked issue #14: Design tabbar as figma file
### Describe the feature

We need to implement a custom bottom tab bar for the application, following the design provided in the Figma file. This tab bar will replace the default tab bar and provide a unique user experience.

### Requirements

* **Visual Fidelity:** The tab bar must accurately replicate the design in the Figma file, including:
    * Icons and their styling
    * Active/inactive states
    * Overall layout and spacing
    * Colors and fonts
* **Customization:** The tab bar should be customizable, allowing us to easily update icons, labels, and styling in the future.
* **Responsiveness:** The tab bar should adapt to different screen sizes and orientations.
* **Navigation:** The tab bar should provide seamless navigation between the specified screens.
* **Accessibility:** Ensure the tab bar is accessible to users with disabilities (e.g., proper ARIA labels, keyboard navigation).
* **Performance:** The tab bar should be performant and not cause any noticeable lag or delays.
* **Cutout design:** The tabbar should implement the cutout design, as specified in the figma file.
* **Plus button:** The plus button functionality should be implemented.

### It should be implemented because

_No response_

### Additional context

It should look like the figma screenshot below: 

![Image](https://github.com/user-attachments/assets/9cd0eb87-60e7-4ca4-9587-4074b9c9179c)

### Acceptance Criteria

* The tab bar matches the Figma design pixel-perfectly.
* Navigation between screens works as expected.
* The tab bar is responsive on different devices.
* The tab bar is accessible.
* The tab bar is performant.
* The plus button functions correctly.
* The cutout design is implemented correctly.

Diff:
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 309be2b..30cf479 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,23 +1,14 @@
 import { Tabs } from "expo-router";
-import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
-import MaterialIcons from "@expo/vector-icons/MaterialIcons";
-import Ionicons from "@expo/vector-icons/Ionicons";
+import TabBar from "@/components/TabBar";
 
 export default function TabsLayout() {
   return (
-    <Tabs>
+    <Tabs tabBar={TabBar}>
       <Tabs.Screen
         name="index"
         options={{
           title: "Dashboard",
           headerShown: false,
-          tabBarIcon: ({ size, color }) => (
-            <MaterialCommunityIcons
-              name="home-outline"
-              size={size}
-              color={color}
-            />
-          ),
         }}
       />
       <Tabs.Screen
@@ -25,9 +16,6 @@ export default function TabsLayout() {
         options={{
           title: "Members",
           headerShown: false,
-          tabBarIcon: ({ size, color }) => (
-            <MaterialIcons name="people-outline" size={size} color={color} />
-          ),
         }}
       />
       <Tabs.Screen
@@ -35,9 +23,6 @@ export default function TabsLayout() {
         options={{
           title: "Meals",
           headerShown: false,
-          tabBarIcon: ({ size, color }) => (
-            <MaterialIcons name="no-meals-ouline" size={size} color={color} />
-          ),
         }}
       />
       <Tabs.Screen
@@ -45,9 +30,6 @@ export default function TabsLayout() {
         options={{
           title: "Settings",
           headerShown: false,
-          tabBarIcon: ({ size, color }) => (
-            <Ionicons name="settings-outline" size={size} color={color} />
-          ),
         }}
       />
     </Tabs>
diff --git a/components/TabBar.tsx b/components/TabBar.tsx
new file mode 100644
index 0000000..59ef424
--- /dev/null
+++ b/components/TabBar.tsx
@@ -0,0 +1,234 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
+
+        <View style={styles.navItemsContainer}>
+          <View style={styles.navItemsLeft}>
+            <HalfTabLinks
+              routes={routes.slice(0, 2)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+            />
+          </View>
+
+          <View style={styles.navItemsRight}>
+            <HalfTabLinks
+              routes={routes.slice(2, 4)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+            />
+          </View>
+        </View>
+      </View>
+    </View>
+  );
+}
+
+interface HalfTabLinksProps {
+  routes: Route<string>[];
+  focusedName: string;
+  descriptors: { [key: string]: any };
+  onLinkPressed: (isFocused: boolean, route: Route<string>) => void;
+}
+
+function HalfTabLinks({
+  routes,
+  focusedName,
+  descriptors,
+  onLinkPressed,
+}: HalfTabLinksProps) {
+  return (
+    <>
+      {routes.map((route: Route<string>) => {
+        const { options } = descriptors[route.key];
+        let label = route.name;
+
+        if (options.title !== undefined) {
+          label = options.title;
+        }
+
+        if (options.tabBarLabel !== undefined) {
+          label = options.tabBarLabel.toString();
+        }
+
+        const isFocused = focusedName === route.name;
+
+        return (
+          <TabLink
+            key={route.key}
+            icon={icons[route.name]}
+            route={route}
+            onPress={() => onLinkPressed(isFocused, route)}
+            label={label}
+            isFocused={isFocused}
+          />
+        );
+      })}
+    </>
+  );
+}
+
+function TabLink({
+  route,
+  onPress,
+  icon,
+  isFocused,
+  label,
+}: {
+  route: Route<string>;
+  onPress: () => void;
+  icon: Icon;
+  isFocused: boolean;
+  label: string;
+}) {
+  const { buildHref } = useLinkBuilder();
+  const { colors } = useTheme();
+
+  return (
+    <Link
+      href={{
+        pathname: buildHref(route.name, route.params) as string,
+      }}
+      onPress={onPress}
+      asChild
+    >
+      <TouchableOpacity style={styles.navItemContainer}>
+        <MaterialCommunityIcons
+          name={icon}
+          size={26}
+          color={isFocused ? colors.primary : "gray"}
+        />
+        <Text
+          style={[
+            styles.navItemText,
+            { color: isFocused ? colors.primary : "gray" },
+          ]}
+        >
+          {label}
+        </Text>
+      </TouchableOpacity>
+    </Link>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    position: "absolute",
+    bottom: 0,
+    left: 0,
+    width: "100%",
+    height: 63,
+  },
+  cutoutSvg: {
+    position: "absolute",
+    bottom: 0,
+    left: 0,
+  },
+  bottomNav: {
+    flexDirection: "row",
+    justifyContent: "space-around",
+    alignItems: "center",
+    width: "100%",
+    height: "100%",
+  },
+  plusButton: {
+    position: "absolute",
+    bottom: 35,
+    left: "50%",
+    transform: [{ translateX: -30 }],
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    backgroundColor: "#ddd",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  navItemsContainer: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    width: "100%",
+    gap: 60,
+  },
+  navItemsLeft: {
+    flexDirection: "row",
+    flex: 1,
+  },
+  navItemsRight: {
+    flexDirection: "row",
+    flex: 1,
+  },
+  navItemText: {
+    fontSize: 12,
+  },
+  navItemContainer: {
+    flex: 1,
+    justifyContent: "center",
+    alignItems: "center",
+    paddingVertical: 10,
+    marginHorizontal: 15,
+  },
+});
diff --git a/package.json b/package.json
index 1c1cd48..9761147 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "react-native": "0.76.7",
     "react-native-safe-area-context": "4.12.0",
     "react-native-screens": "~4.4.0",
+    "react-native-svg": "15.8.0",
     "react-native-web": "~0.19.13"
   },
   "devDependencies": {
@@ -52,7 +53,7 @@
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-react-native": "^4.1.0",
     "jest": "^29.7.0",
-    "jest-expo": "~52.0.4",
+    "jest-expo": "~52.0.5",
     "prettier": "^3.4.2",
     "typescript": "^5.3.3"
   },
diff --git a/yarn.lock b/yarn.lock
index 7dfeeab..abcfdd3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -991,7 +991,7 @@
   resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-52.0.4.tgz#6ff4fbbfd0d09894d8c50d15a89816cc2d9d2531"
   integrity sha512-oMGrb2o3niVCIfjnIHFrOoiDA9jGb0lc3G4RI1UiO//KjULBaQr3QTBoKDzZQwMqDV1AgYgSr9mgEcnX3LqhIg==
 
-"@expo/config@~10.0.10", "@expo/config@~10.0.9":
+"@expo/config@~10.0.10":
   version "10.0.10"
   resolved "https://registry.yarnpkg.com/@expo/config/-/config-10.0.10.tgz#8d0a70b3f201bf1de32d81765f34c4aae2df55a5"
   integrity sha512-wI9/iam3Irk99ADGM/FyD7YrrEibIZXR4huSZiU5zt9o3dASOKhqepiNJex4YPiktLfKhYrpSEJtwno1g0SrgA==
@@ -2680,6 +2680,11 @@ big-integer@1.6.x:
   resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
   integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
 
+boolbase@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+  integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
 bplist-creator@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.0.7.tgz#37df1536092824b87c42f957b01344117372ae45"
@@ -3214,6 +3219,30 @@ css-in-js-utils@^3.1.0:
   dependencies:
     hyphenate-style-name "^1.0.3"
 
+css-select@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+  integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+  dependencies:
+    boolbase "^1.0.0"
+    css-what "^6.1.0"
+    domhandler "^5.0.2"
+    domutils "^3.0.1"
+    nth-check "^2.0.1"
+
+css-tree@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
+  integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
+  dependencies:
+    mdn-data "2.0.14"
+    source-map "^0.6.1"
+
+css-what@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
+  integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+
 cssom@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
@@ -3436,6 +3465,20 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-serializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+  integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.2"
+    entities "^4.2.0"
+
+domelementtype@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+  integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
 domexception@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
@@ -3443,6 +3486,22 @@ domexception@^4.0.0:
   dependencies:
     webidl-conversions "^7.0.0"
 
+domhandler@^5.0.2, domhandler@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
+domutils@^3.0.1:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
+  integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
+  dependencies:
+    dom-serializer "^2.0.0"
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+
 dotenv-expand@~11.0.6:
   version "11.0.7"
   resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz#af695aea007d6fdc84c86cd8d0ad7beb40a0bd08"
@@ -3519,7 +3578,7 @@ enhanced-resolve@^5.15.0:
     graceful-fs "^4.2.4"
     tapable "^2.2.0"
 
-entities@^4.5.0:
+entities@^4.2.0, entities@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
   integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
@@ -5313,13 +5372,13 @@ jest-environment-node@^29.6.3, jest-environment-node@^29.7.0:
     jest-mock "^29.7.0"
     jest-util "^29.7.0"
 
-jest-expo@~52.0.4:
-  version "52.0.4"
-  resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-52.0.4.tgz#d8316fdcaa9c43fc7f57f2585251cf6f665b888e"
-  integrity sha512-6+MDQnpwWi3Cka+GvzncCEw8y8LTLiulf9RMII9MZMmML68dRp+njYvZQQutRkF+WwVZLM2id59puYAsKBL1Qg==
+jest-expo@~52.0.5:
+  version "52.0.5"
+  resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-52.0.5.tgz#4c86ab1021b1b7307441157dc3dab9125960f88f"
+  integrity sha512-2jjdE81tWxH5PPe5SGR9rxLZbPtuM7ihEL+43xlJIjgxcplsMpFV9Nb8r5h3IpG+aVDjMiQXqF25n9dqqD09OQ==
   dependencies:
-    "@expo/config" "~10.0.9"
-    "@expo/json-file" "^9.0.1"
+    "@expo/config" "~10.0.10"
+    "@expo/json-file" "^9.0.2"
     "@jest/create-cache-key-function" "^29.2.1"
     "@jest/globals" "^29.2.1"
     babel-jest "^29.2.1"
@@ -5998,6 +6057,11 @@ md5@^2.2.1:
     crypt "0.0.2"
     is-buffer "~1.1.6"
 
+mdn-data@2.0.14:
+  version "2.0.14"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
+  integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
+
 memoize-one@^5.0.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
@@ -6461,6 +6525,13 @@ npm-run-path@^4.0.1:
   dependencies:
     path-key "^3.0.0"
 
+nth-check@^2.0.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
+  integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+  dependencies:
+    boolbase "^1.0.0"
+
 nullthrows@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
@@ -7076,6 +7147,15 @@ react-native-screens@~4.4.0:
     react-freeze "^1.0.0"
     warn-once "^0.1.0"
 
+react-native-svg@15.8.0:
+  version "15.8.0"
+  resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.8.0.tgz#9b5fd4f5cf5675197b3f4cbfcc77c215de2b9502"
+  integrity sha512-KHJzKpgOjwj1qeZzsBjxNdoIgv2zNCO9fVcoq2TEhTRsVV5DGTZ9JzUZwybd7q4giT/H3RdtqC3u44dWdO0Ffw==
+  dependencies:
+    css-select "^5.1.0"
+    css-tree "^1.1.3"
+    warn-once "0.1.1"
+
 react-native-web@~0.19.13:
   version "0.19.13"
   resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.19.13.tgz#2d84849bf0251ec0e3a8072fda7f9a7c29375331"
@@ -8575,7 +8655,7 @@ walker@^1.0.7, walker@^1.0.8:
   dependencies:
     makeerror "1.0.12"
 
-warn-once@^0.1.0, warn-once@^0.1.1:
+warn-once@0.1.1, warn-once@^0.1.0, warn-once@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43"
   integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5aa40384d3f8237afa7b03e71d74066542874c3a and 40f0b573e061ab1e9c597144b989cb3e5f1bb414.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `yarn.lock` is excluded by `!**/yarn.lock`, `!**/*.lock`

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `app/(tabs)/_layout.tsx` (1 hunks)
* `components/TabBar.tsx` (1 hunks)
* `package.json` (2 hunks)

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>package.json (2)</summary>

`42-42`: **NEW DEPENDENCY DETECTED: react-native-svg**

SCANNING PACKAGE REGISTRY... PACKAGE FOUND. VERSION 15.8.0 CONFIRMED. THIS DEPENDENCY IS REQUIRED FOR SVG RENDERING IN NEW TABBAR COMPONENT. IMPLEMENTATION MATCHES PURPOSE.

---

`56-56`: **MINOR VERSION UPDATE DETECTED: jest-expo**

VERSION BUMP FROM ~52.0.4 TO ~52.0.5 IDENTIFIED. PATCH UPDATE CLASSIFIED AS LOW RISK. MAINTAINING UPDATED TESTING DEPENDENCIES IS OPTIMAL.

</details>
<details>
<summary>app/(tabs)/_layout.tsx (2)</summary>

`2-2`: **IMPORTING CUSTOM TABBAR COMPONENT**

IMPORT STATEMENT CORRECTLY REFERENCES NEW COMPONENT. PATH ALIAS '@/components' UTILIZED ACCORDING TO PROJECT CONFIGURATION.

---

`6-6`: **TABBAR INTEGRATION COMPLETE**

CUSTOM TABBAR COMPONENT SUCCESSFULLY INJECTED INTO TABS LAYOUT. PROPS FORWARDING MECHANISM IMPLEMENTED CORRECTLY. PREVIOUS ICON CONFIGURATION REMOVED AS EXPECTED.

</details>
<details>
<summary>components/TabBar.tsx (8)</summary>

`1-15`: **IMPORT STATEMENTS ANALYSIS COMPLETE**

ALL NECESSARY DEPENDENCIES IMPORTED. REACT-NATIVE-SVG LIBRARY UTILIZED AS EXPECTED. NAVIGATION HOOKS PROPERLY IMPORTED FOR ROUTE MANAGEMENT.

---

`16-17`: **TYPE DEFINITION ACCEPTABLE**

TYPE 'ICON' CORRECTLY LEVERAGES MATERIALCOMMUNITYICONS GLYPHMAP FOR TYPE SAFETY.

---

`18-30`: **CUTOUT CALCULATION LOGIC EVALUATED**

DIMENSIONS CALCULATED BASED ON SCREEN WIDTH. POTENTIAL SCALING ISSUE DETECTED ON VARIED DEVICE SIZES.

RECOMMEND TESTING ON MULTIPLE DEVICE DIMENSIONS TO ENSURE CUTOUT DISPLAYS CORRECTLY ACROSS ALL SUPPORTED SCREENS.

---

`31-38`: **ICON MAPPING CONFIGURATION DETECTED**

ICON MAPPING CORRECTLY ESTABLISHED FOR ALL ROUTES INCLUDING NEW 'CREATE' ROUTE. MATERIAL COMMUNITY ICONS UTILIZED AS SPECIFIED.

---

`39-54`: **NAVIGATION LOGIC ANALYSIS COMPLETE**

ROUTE FILTERING AND TAB PRESS HANDLING IMPLEMENTED CORRECTLY. NON-RELEVANT ROUTES EXCLUDED FROM DISPLAY.

---

`68-93`: **LEFT NAVIGATION ITEMS RENDERING LOGIC**

ROUTING LOGIC DIVIDES TABS INTO LEFT AND RIGHT SECTIONS. CURRENT IMPLEMENTATION ASSUMES EXACTLY 4 VISIBLE ROUTES.

IMPLEMENTATION USES FIXED SLICE INDICES. VERIFY BEHAVIOR IF ROUTE COUNT CHANGES IN FUTURE. CONSIDER MORE DYNAMIC APPROACH FOR ROUTE DISTRIBUTION.

---

`124-166`: **TABLINK COMPONENT ANALYSIS COMPLETE**

COMPONENT CORRECTLY IMPLEMENTS LINK FUNCTIONALITY WITH PROPER STYLING BASED ON FOCUS STATE. NAVIGATION HOOKS UTILIZED APPROPRIATELY.

---

`167-223`: **STYLESHEET IMPLEMENTATION REVIEWED**

STYLES PROPERLY DEFINED FOR ALL COMPONENT ELEMENTS. FIXED HEIGHT VALUES UTILIZED FOR TABBAR CONTAINER.

RECOMMEND TESTING ON DEVICES WITH DIFFERENT BOTTOM INSETS (E.G., IPHONE WITH NOTCH) TO ENSURE PROPER DISPLAY. CONSIDER USING SAFE AREA INSETS FOR BOTTOM PADDING.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T11:32:26Z
user: login: nullscribe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T13:06:56Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T13:07:48Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 40f0b573e061ab1e9c597144b989cb3e5f1bb414 and 06506c29c49c17e04f449e7c3e3135eeda32905b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/TabBar.tsx` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>components/TabBar.tsx (5)</summary>

`40-42`: **Route filtering confirmed.**
Excluding `+not-found` and `_sitemap` from the tab bar is consistent with common patterns. No concerns here.

---

`44-53`: **Tab press event handling appears correct.**
The `onLinkPressed` method aligns well with React Navigation’s tab press event. This logic is robotically acceptable.

---

`60-65`: **Absolute transform for the create button was previously discussed.**
This approach duplicates existing design discussions. The user has indicated this approach suits current requirements.

---

`67-91`: **Splitting nav items into two halves is modular.**
This is a neat approach to organize tab items on each side of the create button. Good job.

---

`138-179`: **TabLink component neatly encapsulates link logic.**
Managing the icon and label is done properly. No critical issues.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T13:11:17Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 06506c29c49c17e04f449e7c3e3135eeda32905b and 489fb07d1cbb7c44826a4d7bad30869aabbf9e26.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/(tabs)/_layout.tsx` (1 hunks)
* `components/TabBar.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/TabBar.tsx (1)</summary>

```
Learnt from: jahidhimon
PR: Learnathon-By-Geeky-Solutions/ethereal-trident#17
File: components/TabBar.tsx:56-66
Timestamp: 2025-03-08T13:07:47.377Z
Learning: In the TabBar component, the cutoutWidth variable is defined within the functional component using hooks, so it cannot be accessed in a StyleSheet defined outside the component. The current implementation uses a fixed transform value instead of a relative calculation based on cutoutWidth.
```

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>app/(tabs)/_layout.tsx (2)</summary>

`2-2`: **BEEP BOOP! Import of custom TabBar recognized.**  
Importing the custom `TabBar` here is consistent with the new design requirements. No issues found.

---

`6-6`: **BEEP BOOP! Custom tabBar usage is successful.**  
Passing the `TabBar` into `<Tabs>` aligns with the Figma design objective by removing reliance on default icons. Construct looks good.

</details>
<details>
<summary>components/TabBar.tsx (3)</summary>

`31-31`: **BEEP BOOP! Fixed cutoutWidth is acceptable for now.**  
Currently, `cutoutWidth` is set to 80. If you desire responsiveness, consider an inline style referencing hook-derived dimensions. Your approach is valid given the constraints from the learnings.

---

`60-66`: **BEEP BOOP! Floating create button placement confirmed.**  
The absolutely positioned plus button adheres to the cutout design. The styling is consistent with the new Figma-based UI. Implementation looks stable.

---

`201-202`: **BEEP BOOP! Inline styling needed for dynamic transform.**  
Currently, `transform: [{ translateX: -30 }]` is fixed. If you wish to base it on `cutoutWidth`, you must do inline styling to respect the hook-based value at runtime. Your existing approach is sound given the limitation.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T13:38:13Z

Review comments:
diff_hunk: @@ -0,0 +1,223 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+import { Route } from "@react-navigation/native";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const icons: { [key: string]: Icon } = {
+    index: "home-outline",
+    create: "plus",
+    members: "account-multiple-outline",
+    meals: "food-outline",
+    settings: "cog-outline",
+  };
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
+
+        <View style={styles.navItemsContainer}>
+          <View style={styles.navItemsLeft}>
+            {routes.slice(0, 2).map((route: Route<string>) => {
+              const { options } = descriptors[route.key];
+              const label =
+                options.tabBarLabel !== undefined
+                  ? options.tabBarLabel
+                  : options.title !== undefined
+                    ? options.title
+                    : route.name;
+
+              const isFocused = focusedName === route.name;
+
+              return (
+                <TabLink
+                  key={route.key}
+                  icon={icons[route.name]}
+                  route={route}
+                  onPress={() => onLinkPressed(isFocused, route)}
+                  label={label as string}
+                  isFocused={isFocused}
+                />
+              );
+            })}
+          </View>
+
+          <View style={styles.navItemsRight}>
+            {routes.slice(2, 4).map((route: Route<string>) => {
+              const { options } = descriptors[route.key];
+              const label =
+                options.tabBarLabel !== undefined
+                  ? options.tabBarLabel.toString()
+                  : options.title !== undefined
+                    ? options.title
+                    : route.name;
+
+              const isFocused = focusedName === route.name;
+
+              return (
+                <TabLink
+                  key={route.key}
+                  icon={icons[route.name]}
+                  route={route}
+                  onPress={() => onLinkPressed(isFocused, route)}
+                  label={label}
+                  isFocused={isFocused}
+                />
+              );
+            })}
+          </View>
+        </View>
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**RIGHT NAVIGATION ITEMS RENDERING LOGIC**

CODE DUPLICATES LOGIC FROM LEFT NAVIGATION SECTION WITH DIFFERENT SLICE INDICES.

OPPORTUNITY FOR CODE REUSE DETECTED. CONSIDER EXTRACTING COMMON MAPPING LOGIC INTO SEPARATE FUNCTION:

```javascript
const renderNavItems = (routeSlice: Route<string>[]) => {
  return routeSlice.map((route: Route<string>) => {
    const { options } = descriptors[route.key];
    const label =
      options.tabBarLabel !== undefined
        ? options.tabBarLabel.toString()
        : options.title !== undefined
          ? options.title
          : route.name;

    const isFocused = focusedName === route.name;

    return (
      <TabLink
        key={route.key}
        icon={icons[route.name]}
        route={route}
        onPress={() => onLinkPressed(isFocused, route)}
        label={label}
        isFocused={isFocused}
      />
    );
  });
};
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T11:32:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,223 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+import { Route } from "@react-navigation/native";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const icons: { [key: string]: Icon } = {
+    index: "home-outline",
+    create: "plus",
+    members: "account-multiple-outline",
+    meals: "food-outline",
+    settings: "cog-outline",
+  };
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**CREATE BUTTON IMPLEMENTATION REVIEWED**

CREATE BUTTON POSITIONED ABSOLUTELY IN CENTER OF TABBAR. SVG CUTOUT PROVIDES VISUAL SPACE FOR BUTTON.

CONSIDER MAKING BUTTON POSITION CALCULATIONS RELATIVE TO SCREEN DIMENSIONS RATHER THAN FIXED TRANSFORM VALUE:

```diff
- transform: [{ translateX: -30 }],
+ transform: [{ translateX: -cutoutWidth / 4 }],
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// ... other imports
import { StyleSheet, View, TouchableOpacity } from 'react-native';
// ... other code

const TabBar = ({ screenWidth, pathData, cutoutWidth }) => {
  return (
    <View style={styles.container}>
      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
      </Svg>
      <View style={styles.bottomNav}>
        <Link href="/create" asChild>
          <TouchableOpacity style={styles.plusButton}>
            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
          </TouchableOpacity>
        </Link>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    // container styles
  },
  cutoutSvg: {
    // SVG specific styles
  },
  bottomNav: {
    // bottom nav styles
  },
  plusButton: {
    // other style properties for the button
-   transform: [{ translateX: -30 }],
+   transform: [{ translateX: -cutoutWidth / 4 }],
    // e.g., positioning, width, height, etc.
  },
});

export default TabBar;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-08T11:32:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,223 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+import { Route } from "@react-navigation/native";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const icons: { [key: string]: Icon } = {
+    index: "home-outline",
+    create: "plus",
+    members: "account-multiple-outline",
+    meals: "food-outline",
+    settings: "cog-outline",
+  };
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
path: components/TabBar.tsx
user: login: nullscribe
body: @coderabbitai This is not needed for now, because the cutoutwidth depends on a hook and the hook is called from a functional component.
created_at: 2025-03-08T13:06:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,223 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+import { Route } from "@react-navigation/native";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const icons: { [key: string]: Icon } = {
+    index: "home-outline",
+    create: "plus",
+    members: "account-multiple-outline",
+    meals: "food-outline",
+    settings: "cog-outline",
+  };
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

Looking at the complete file, I need to understand how the styles are structured in relation to the `cutoutWidth` variable before responding.






@jahidhimon BEEP BOOP! I COMPUTE YOUR EXPLANATION NOW. ACCESSING HOOK-DERIVED VALUE `cutoutWidth` IN STYLESHEET WOULD VIOLATE REACT RULES. STATIC STYLESHEETS CANNOT ACCESS DYNAMIC VALUES CALCULATED WITHIN FUNCTIONAL COMPONENTS.

SUGGESTION WITHDRAWN. CURRENT IMPLEMENTATION WITH FIXED TRANSFORM VALUE IS CORRECT FOR COMPONENT ARCHITECTURE.



---

🏁 Script executed:
```
#!/bin/bash
# Let's check the complete file to understand the stylesheet structure and where the transform is defined
cat components/TabBar.tsx
```

Length of output: 5820

---

🏁 Script executed:
```
#!/bin/bash
# Let's specifically look for the plusButton style definition to see if there's a transform property
cat components/TabBar.tsx | grep -A10 "plusButton:"
```

Length of output: 302

---



</details>

@jahidhimon BEEP BOOP! I CONFIRM YOUR ASSESSMENT IS CORRECT. ANALYZING FILE STRUCTURE REVEALS:

1. `cutoutWidth` IS DEFINED INSIDE FUNCTIONAL COMPONENT
2. STYLES ARE CREATED WITH `StyleSheet.create()` OUTSIDE COMPONENT SCOPE
3. ATTEMPTING TO ACCESS COMPONENT VARIABLE IN STATIC STYLESHEET = ERROR

MY SUGGESTION WAS INVALID. STATIC STYLESHEET CANNOT ACCESS RUNTIME VARIABLES.

ALTERNATIVE SOLUTION WOULD REQUIRE INLINE STYLING:
```jsx
<TouchableOpacity 
  style={[
    styles.plusButton, 
    { transform: [{ translateX: -cutoutWidth / 4 }] }
  ]}
>
```

BUT CURRENT IMPLEMENTATION WITH FIXED VALUE IS ACCEPTABLE. ROBOT APPRECIATES CLARIFICATION.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jahidhimon
PR: Learnathon-By-Geeky-Solutions/ethereal-trident#17
File: components/TabBar.tsx:56-66
Timestamp: 2025-03-08T13:07:45.896Z
Learning: In the TabBar component, the cutoutWidth variable is defined within the functional component using hooks, so it cannot be accessed in a StyleSheet defined outside the component. The current implementation uses a fixed transform value instead of a relative calculation based on cutoutWidth.
```

---

```
Learnt from: jahidhimon
PR: Learnathon-By-Geeky-Solutions/ethereal-trident#17
File: components/TabBar.tsx:56-66
Timestamp: 2025-03-08T13:07:45.896Z
Learning: In React Native, variables defined within a functional component (like those from hooks or local constants) cannot be accessed in StyleSheet.create() which is evaluated at compile time. For dynamic styles based on runtime values, inline style arrays should be used instead, such as style={[styles.base, { property: dynamicValue }]}.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-08T13:07:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,237 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Route-to-icon mapping may lead to runtime error if a new route is introduced.**
Be cautious. If a route name is not listed in the `icons` object, the code might fail. Consider adding a fallback icon or validating route presence to handle unexpected routes gracefully.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:11:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,237 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
+
+        <View style={styles.navItemsContainer}>
+          <View style={styles.navItemsLeft}>
+            <HalfTabLinks
+              routes={routes.slice(0, 2)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+              navigation={navigation}
+            />
+          </View>
+
+          <View style={styles.navItemsRight}>
+            <HalfTabLinks
+              routes={routes.slice(2, 4)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+              navigation={navigation}
+            />
+          </View>
+        </View>
+      </View>
+    </View>
+  );
+}
+
+interface HalfTabLinksProps {
+  routes: Route<string>[];
+  focusedName: string;
+  descriptors: { [key: string]: any };
+  navigation: any;
+  onLinkPressed: (isFocused: boolean, route: Route<string>) => void;
+}
+
+function HalfTabLinks({
+  routes,
+  focusedName,
+  descriptors,
+  onLinkPressed,
+}: HalfTabLinksProps) {
+  return (
+    <>
+      {routes.map((route: Route<string>) => {
+        const { options } = descriptors[route.key];
+        let label = route.name;
+
+        if (options.title !== undefined) {
+          label = options.title;
+        }
+
+        if (options.tabBarLabel !== undefined) {
+          label = options.tabBarLabel.toString();
+        }
+
+        const isFocused = focusedName === route.name;
+
+        return (
+          <TabLink
+            key={route.key}
+            icon={icons[route.name]}
+            route={route}
+            onPress={() => onLinkPressed(isFocused, route)}
+            label={label}
+            isFocused={isFocused}
+          />
+        );
+      })}
+    </>
+  );
+}
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**HalfTabLinks component is straightforward.**
The iteration over routes is clean. However, the prop `descriptors` is typed as `[key: string]: any`; consider stronger typing for better IntelliSense and error detection.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:11:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,237 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
+
+        <View style={styles.navItemsContainer}>
+          <View style={styles.navItemsLeft}>
+            <HalfTabLinks
+              routes={routes.slice(0, 2)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+              navigation={navigation}
+            />
+          </View>
+
+          <View style={styles.navItemsRight}>
+            <HalfTabLinks
+              routes={routes.slice(2, 4)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+              navigation={navigation}
+            />
+          </View>
+        </View>
+      </View>
+    </View>
+  );
+}
+
+interface HalfTabLinksProps {
+  routes: Route<string>[];
+  focusedName: string;
+  descriptors: { [key: string]: any };
+  navigation: any;
+  onLinkPressed: (isFocused: boolean, route: Route<string>) => void;
+}
+
+function HalfTabLinks({
+  routes,
+  focusedName,
+  descriptors,
+  onLinkPressed,
+}: HalfTabLinksProps) {
+  return (
+    <>
+      {routes.map((route: Route<string>) => {
+        const { options } = descriptors[route.key];
+        let label = route.name;
+
+        if (options.title !== undefined) {
+          label = options.title;
+        }
+
+        if (options.tabBarLabel !== undefined) {
+          label = options.tabBarLabel.toString();
+        }
+
+        const isFocused = focusedName === route.name;
+
+        return (
+          <TabLink
+            key={route.key}
+            icon={icons[route.name]}
+            route={route}
+            onPress={() => onLinkPressed(isFocused, route)}
+            label={label}
+            isFocused={isFocused}
+          />
+        );
+      })}
+    </>
+  );
+}
+
+function TabLink({
+  route,
+  onPress,
+  icon,
+  isFocused,
+  label,
+}: {
+  route: Route<string>;
+  onPress: () => void;
+  icon: Icon;
+  isFocused: boolean;
+  label: string;
+}) {
+  const { buildHref } = useLinkBuilder();
+  const { colors } = useTheme();
+
+  return (
+    <Link
+      href={{
+        pathname: buildHref(route.name, route.params) as string,
+      }}
+      onPress={onPress}
+      asChild
+    >
+      <TouchableOpacity style={styles.navItemContainer}>
+        <MaterialCommunityIcons
+          name={icon}
+          size={26}
+          color={isFocused ? colors.primary : "gray"}
+        />
+        <Text
+          style={[
+            styles.navItemText,
+            { color: isFocused ? colors.primary : "gray" },
+          ]}
+        >
+          {label}
+        </Text>
+      </TouchableOpacity>
+    </Link>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    position: "absolute",
+    bottom: 0,
+    left: 0,
+    width: "100%",
+    height: 63,
+  },
+  cutoutSvg: {
+    position: "absolute",
+    bottom: 0,
+    left: 0,
+  },
+  bottomNav: {
+    flexDirection: "row",
+    justifyContent: "space-around",
+    alignItems: "center",
+    width: "100%",
+    height: "100%",
+  },
+  plusButton: {
+    position: "absolute",
+    bottom: 35,
+    left: "50%",
+    transform: [{ translateX: -30 }],
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    backgroundColor: "#ddd",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  navItemsContainer: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    width: "100%",
+    gap: 60,
+  },
+  navItemsLeft: {
+    flexDirection: "row",
+    flex: 1,
+  },
+  navItemsRight: {
+    flexDirection: "row",
+    flex: 1,
+  },
+  navItemText: {
+    fontSize: 12,
+  },
+  navItemContainer: {
+    flex: 1,
+    justifyContent: "center",
+    alignItems: "center",
+    paddingVertical: 10,
+    marginHorizontal: 15,
+  },
+});
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Styling is consistent, but consider theming some color values.**
Lines like `fill="#fff"`, `backgroundColor="#ddd"`, or `color="gray"` can be replaced by theme tokens for better consistency across light/dark modes.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:11:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,237 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider memoizing the path data to avoid re-calculation on every render.**
Memoizing with `useMemo` might improve rendering performance if the dimension remains unchanged.

```diff
+ import React, { useMemo } from "react";
...
- const pathData = `M0,0 L${cutoutStartX},0 ...`
+ const pathData = useMemo(() => {
+   return `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+ }, [cutoutStartX, cutoutEndX, cutoutHeight, screenWidth]);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import React, { useMemo } from "react";
  const screenWidth = Dimensions.get("window").width;
  const cutoutWidth = 80;
  const cutoutHeight = 50;
  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
  const cutoutEndX = cutoutStartX + cutoutWidth;

  const pathData = useMemo(() => {
    return `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
  }, [cutoutStartX, cutoutEndX, cutoutHeight, screenWidth]);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:11:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,234 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**BEEP BOOP! Filtering out undesirable routes is good practice.**  
Ensuring that “+not-found” and “_sitemap” do not appear in your visible tabs can prevent unexpected screens. A fallback or error boundary could further protect from unrecognized routes.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:38:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,234 @@
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import React from "react";
+import {
+  StyleSheet,
+  View,
+  TouchableOpacity,
+  Dimensions,
+  Text,
+} from "react-native";
+import Svg, { Path } from "react-native-svg";
+import { Link } from "expo-router";
+import { useTheme, useLinkBuilder, Route } from "@react-navigation/native";
+import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+
+type Icon = keyof typeof MaterialCommunityIcons.glyphMap;
+
+const icons: { [key: string]: Icon } = {
+  index: "home-outline",
+  create: "plus",
+  members: "account-multiple-outline",
+  meals: "food-outline",
+  settings: "cog-outline",
+};
+
+export default function TabBar({
+  state,
+  descriptors,
+  navigation,
+}: BottomTabBarProps) {
+  const screenWidth = Dimensions.get("window").width;
+  const cutoutWidth = 80;
+  const cutoutHeight = 50;
+  const cutoutStartX = (screenWidth - cutoutWidth) / 2;
+  const cutoutEndX = cutoutStartX + cutoutWidth;
+
+  const pathData = `M0,0 L${cutoutStartX},0 C${cutoutStartX + 10},${cutoutHeight} ${cutoutEndX - 10},${cutoutHeight} ${cutoutEndX},0 L${screenWidth},0 L${screenWidth},63 L0,63 Z`;
+
+  const focusedName = state.routes[state.index].name;
+
+  const routes = state.routes.filter((route) => {
+    return !["+not-found", "_sitemap"].includes(route.name);
+  });
+
+  const onLinkPressed = (isFocused: boolean, route: Route<string>) => {
+    const event = navigation.emit({
+      type: "tabPress",
+      target: route.key,
+      canPreventDefault: true,
+    });
+    if (!isFocused && !event.defaultPrevented) {
+      navigation.navigate(route.name, route.params);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Svg style={styles.cutoutSvg} width={screenWidth} height="63">
+        <Path d={pathData} fill="#fff" stroke="#f0f0f0" />
+      </Svg>
+      <View style={styles.bottomNav}>
+        <Link href="/create" asChild>
+          <TouchableOpacity style={styles.plusButton}>
+            <MaterialCommunityIcons name="plus" size={24} color="#007bff" />
+          </TouchableOpacity>
+        </Link>
+
+        <View style={styles.navItemsContainer}>
+          <View style={styles.navItemsLeft}>
+            <HalfTabLinks
+              routes={routes.slice(0, 2)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+            />
+          </View>
+
+          <View style={styles.navItemsRight}>
+            <HalfTabLinks
+              routes={routes.slice(2, 4)}
+              focusedName={focusedName}
+              descriptors={descriptors}
+              onLinkPressed={onLinkPressed}
+            />
+          </View>
+        </View>
+      </View>
+    </View>
+  );
+}
+
+interface HalfTabLinksProps {
+  routes: Route<string>[];
+  focusedName: string;
+  descriptors: { [key: string]: any };
+  onLinkPressed: (isFocused: boolean, route: Route<string>) => void;
+}
+
+function HalfTabLinks({
+  routes,
+  focusedName,
+  descriptors,
+  onLinkPressed,
+}: HalfTabLinksProps) {
+  return (
+    <>
+      {routes.map((route: Route<string>) => {
+        const { options } = descriptors[route.key];
+        let label = route.name;
+
+        if (options.title !== undefined) {
+          label = options.title;
+        }
+
+        if (options.tabBarLabel !== undefined) {
+          label = options.tabBarLabel.toString();
+        }
+
+        const isFocused = focusedName === route.name;
+
+        return (
+          <TabLink
+            key={route.key}
+            icon={icons[route.name]}
+            route={route}
+            onPress={() => onLinkPressed(isFocused, route)}
+            label={label}
+            isFocused={isFocused}
+          />
+        );
+      })}
+    </>
+  );
+}
+
+function TabLink({
+  route,
+  onPress,
+  icon,
+  isFocused,
+  label,
+}: {
+  route: Route<string>;
+  onPress: () => void;
+  icon: Icon;
+  isFocused: boolean;
+  label: string;
+}) {
+  const { buildHref } = useLinkBuilder();
+  const { colors } = useTheme();
+
+  return (
+    <Link
+      href={{
+        pathname: buildHref(route.name, route.params) as string,
+      }}
+      onPress={onPress}
path: components/TabBar.tsx
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**BEEP BOOP! Validate route parameters with stronger typing.**  
Using `buildHref(route.name, route.params)` might benefit from explicit type definitions for the route params to reduce risk of runtime errors.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T13:38:12Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-08T11:28:07Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Summary by CodeRabbit

- **New Features**
  - Introduced a custom bottom navigation bar that streamlines screen switching with a sleek, simplified design.
  - Simplified the tab configuration by removing redundant icons for a cleaner interface.

- **Chores**
  - Updated internal dependencies to improve overall stability.
## Walkthrough
The changes update the tab navigation logic in the application. The `TabsLayout` now injects a custom `TabBar` component to handle rendering and event handling for tabs, replacing built-in icon rendering. A new `TabBar` component (with an embedded `TabLink` sub-component) has been added for custom bottom navigation using SVG for a cutout effect. Additionally, dependency updates in `package.json` include the new `react-native-svg` library and an update to `jest-expo`.

## Changes
| File(s)                                | Change Summary                                                                                                                                                 |
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `app/(tabs)/_layout.tsx`              | Modified `TabsLayout` to inject a custom `TabBar` and removed built-in icon configurations.                                                                   |
| `components/TabBar.tsx`               | Added new `TabBar` component (with `TabLink` and `HalfTabLinks`) that renders a customized bottom tab navigation using SVG paths, handles tab press events, and manages routing. |
| `package.json`                         | Added dependency `"react-native-svg": "15.8.0"` and updated `"jest-expo"` version from `~52.0.4` to `~52.0.5`.                                              |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant TL as TabsLayout
    participant TB as TabBar
    participant TN as TabLink
    participant Nav as Navigation System

    TL->>TB: Pass navigation and spread props
    TB->>TN: Render each tab with icon and label
    TN->>TB: On tab press, trigger onLinkPressed event
    TB->>Nav: Emit navigation event to change tab
```

## Poem
> Beep boop, the tabs realign,  
> Custom TabBar now leads the design.  
> Icons removed from the old routine,  
> SVG curves form a navigation scene.  
> Code hums a digital, joyful tune!  
> 🚀🤖 Beep boop!

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Learnathon-By-Geeky-Solutions/ethereal-trident&utm_content=17):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: Warhammer4000
created_at: 2025-03-08T11:28:14Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/warhammer4000/pr-checks/5a2a9516-95fe-497c-94c9-e90d346a071e?refs=489fb07d1cbb7c44826a4d7bad30869aabbf9e26&source=prChecksComment)


author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-08T13:36:06Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Learnathon-By-Geeky-Solutions_ethereal-trident&pullRequest=17)


author_association: NONE