From 77bce1ddf7259bfb51b073841825cee0fafe08ef Mon Sep 17 00:00:00 2001 From: Jakub Tkacz Date: Tue, 28 Oct 2025 14:12:28 +0100 Subject: [PATCH 1/2] add initial tabs example --- with-native-tabs/README.md | 50 +++++++ with-native-tabs/app.json | 10 ++ with-native-tabs/app/_layout.tsx | 63 ++++++++ with-native-tabs/app/explore.tsx | 141 ++++++++++++++++++ with-native-tabs/app/index.tsx | 22 +++ with-native-tabs/app/search/[name].tsx | 31 ++++ with-native-tabs/app/search/_layout.tsx | 17 +++ with-native-tabs/app/search/index.tsx | 76 ++++++++++ with-native-tabs/eslint.config.js | 10 ++ with-native-tabs/package.json | 27 ++++ with-native-tabs/tsconfig.json | 15 ++ .../utils/tabConfigurationContext.ts | 9 ++ 12 files changed, 471 insertions(+) create mode 100644 with-native-tabs/README.md create mode 100644 with-native-tabs/app.json create mode 100644 with-native-tabs/app/_layout.tsx create mode 100644 with-native-tabs/app/explore.tsx create mode 100644 with-native-tabs/app/index.tsx create mode 100644 with-native-tabs/app/search/[name].tsx create mode 100644 with-native-tabs/app/search/_layout.tsx create mode 100644 with-native-tabs/app/search/index.tsx create mode 100644 with-native-tabs/eslint.config.js create mode 100644 with-native-tabs/package.json create mode 100644 with-native-tabs/tsconfig.json create mode 100644 with-native-tabs/utils/tabConfigurationContext.ts diff --git a/with-native-tabs/README.md b/with-native-tabs/README.md new file mode 100644 index 00000000..48dd63ff --- /dev/null +++ b/with-native-tabs/README.md @@ -0,0 +1,50 @@ +# Welcome to your Expo app 👋 + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/with-native-tabs/app.json b/with-native-tabs/app.json new file mode 100644 index 00000000..19a4cece --- /dev/null +++ b/with-native-tabs/app.json @@ -0,0 +1,10 @@ +{ + "expo": { + "name": "with-native-tabs", + "slug": "with-native-tabs", + "orientation": "default", + "scheme": "withnativetabs", + "userInterfaceStyle": "automatic", + "plugins": ["expo-router"] + } +} diff --git a/with-native-tabs/app/_layout.tsx b/with-native-tabs/app/_layout.tsx new file mode 100644 index 00000000..c79b42c6 --- /dev/null +++ b/with-native-tabs/app/_layout.tsx @@ -0,0 +1,63 @@ +import MaterialIcons from "@expo/vector-icons/MaterialCommunityIcons"; +import { + Badge, + Icon, + Label, + NativeTabs, + VectorIcon, +} from "expo-router/unstable-native-tabs"; +import { Platform, useColorScheme, View } from "react-native"; +import { + DarkTheme, + DefaultTheme, + ThemeProvider, +} from "@react-navigation/native"; +import { TabConfigurationContext } from "@/utils/tabConfigurationContext"; +import { useState } from "react"; + +export default function RootLayout() { + const scheme = useColorScheme(); + const [isMinimizeOnScrollEnabled, setIsMinimizeOnScrollEnabled] = + useState(true); + + return ( + + + + + + } + /> + + + + 3 + } + /> + + + + + } + /> + + + + + ); +} diff --git a/with-native-tabs/app/explore.tsx b/with-native-tabs/app/explore.tsx new file mode 100644 index 00000000..519692e6 --- /dev/null +++ b/with-native-tabs/app/explore.tsx @@ -0,0 +1,141 @@ +import { TabConfigurationContext } from "@/utils/tabConfigurationContext"; +import { useTheme } from "@react-navigation/native"; +import { + Badge, + Icon, + Label, + NativeTabs, +} from "expo-router/unstable-native-tabs"; +import { use, useState } from "react"; +import { + View, + Text, + StyleSheet, + ScrollView, + Switch, + TextInput, +} from "react-native"; + +export default function Index() { + const { colors } = useTheme(); + const [isBadgeEnabled, setIsBadgeEnabled] = useState(true); + const [isLabelVisible, setIsLabelVisible] = useState(true); + const [isScrollToTopEnabled, setIsScrollToTopEnabled] = useState(true); + const { isMinimizeOnScrollEnabled, setIsMinimizeOnScrollEnabled } = use( + TabConfigurationContext + ); + return ( + <> + + + + + + + Scroll + down + \/ + This + + content + + is + here + to + test + + scrolling + + It + needs + to + be + long, + to + enable + + minimize + + + behavior + + + + + + + + + ); +} + +const SwitchWithLabel = ({ + label, + value, + onValueChange, +}: { + label: string; + value: boolean; + onValueChange: (value: boolean) => void; +}) => { + const { colors } = useTheme(); + return ( + + {label} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + contentContainer: { + paddingHorizontal: 16, + gap: 16, + }, + largeContent: { + marginTop: 96, + gap: 48, + alignItems: "center", + }, + text: { + fontSize: 20, + }, + largeText: { + fontSize: 48, + }, +}); diff --git a/with-native-tabs/app/index.tsx b/with-native-tabs/app/index.tsx new file mode 100644 index 00000000..5a3002f2 --- /dev/null +++ b/with-native-tabs/app/index.tsx @@ -0,0 +1,22 @@ +import { useTheme } from "@react-navigation/native"; +import { View, Text, StyleSheet, PlatformColor } from "react-native"; + +export default function Index() { + const { colors } = useTheme(); + return ( + + Home tab + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + text: { + fontSize: 20, + }, +}); diff --git a/with-native-tabs/app/search/[name].tsx b/with-native-tabs/app/search/[name].tsx new file mode 100644 index 00000000..60fa2c7b --- /dev/null +++ b/with-native-tabs/app/search/[name].tsx @@ -0,0 +1,31 @@ +import { useTheme } from "@react-navigation/native"; +import { Stack, useLocalSearchParams } from "expo-router"; +import { StyleSheet, Text, View } from "react-native"; + +export default function Page() { + const { colors } = useTheme(); + const { name } = useLocalSearchParams<{ name: string }>(); + return ( + <> + + + {name} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + label: { + fontSize: 24, + }, +}); diff --git a/with-native-tabs/app/search/_layout.tsx b/with-native-tabs/app/search/_layout.tsx new file mode 100644 index 00000000..d5062f71 --- /dev/null +++ b/with-native-tabs/app/search/_layout.tsx @@ -0,0 +1,17 @@ +import { isLiquidGlassAvailable } from "expo-glass-effect"; +import { Stack } from "expo-router"; + +export default function SearchLayout() { + return ( + + + + ); +} diff --git a/with-native-tabs/app/search/index.tsx b/with-native-tabs/app/search/index.tsx new file mode 100644 index 00000000..02707ff1 --- /dev/null +++ b/with-native-tabs/app/search/index.tsx @@ -0,0 +1,76 @@ +import { useTheme } from "@react-navigation/native"; +import { Link, Stack } from "expo-router"; +import { useMemo, useState } from "react"; +import { StyleSheet, ScrollView, Text } from "react-native"; + +const fruits = [ + "apple", + "banana", + "cherry", + "date", + "elderberry", + "fig", + "grape", + "honeydew", + "kiwi", + "lemon", + "mango", + "nectarine", +]; + +const adjectives = [ + "fresh", + "ripe", + "juicy", + "sweet", + "delicious", + "tasty", + "zesty", +]; + +const results = fruits.flatMap((fruit) => + adjectives.map((adj) => `${adj} ${fruit}`) +); + +export default function Index() { + const { colors } = useTheme(); + const [searchQuery, setSearchQuery] = useState(""); + const data = useMemo( + () => + results.filter((result) => + result.toLowerCase().includes(searchQuery.toLowerCase()) + ), + [searchQuery] + ); + return ( + <> + setSearchQuery(e.nativeEvent.text), + }, + }} + /> + + {data.map((result, index) => ( + + {result} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + text: { + fontSize: 20, + padding: 4, + }, +}); diff --git a/with-native-tabs/eslint.config.js b/with-native-tabs/eslint.config.js new file mode 100644 index 00000000..5025da68 --- /dev/null +++ b/with-native-tabs/eslint.config.js @@ -0,0 +1,10 @@ +// https://docs.expo.dev/guides/using-eslint/ +const { defineConfig } = require('eslint/config'); +const expoConfig = require('eslint-config-expo/flat'); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ['dist/*'], + }, +]); diff --git a/with-native-tabs/package.json b/with-native-tabs/package.json new file mode 100644 index 00000000..8916c2bd --- /dev/null +++ b/with-native-tabs/package.json @@ -0,0 +1,27 @@ +{ + "name": "with-native-tabs", + "main": "expo-router/entry", + "version": "1.0.0", + "scripts": { + "start": "expo start" + }, + "dependencies": { + "@expo/vector-icons": "^15.0.3", + "@react-navigation/native": "^7.1.19", + "expo": "~54.0.20", + "expo-constants": "~18.0.10", + "expo-glass-effect": "~0.1.4", + "expo-router": "~6.0.13", + "expo-splash-screen": "^31.0.10", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-web": "~0.21.0" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "typescript": "~5.9.2" + } +} diff --git a/with-native-tabs/tsconfig.json b/with-native-tabs/tsconfig.json new file mode 100644 index 00000000..fc3e1466 --- /dev/null +++ b/with-native-tabs/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ] +} diff --git a/with-native-tabs/utils/tabConfigurationContext.ts b/with-native-tabs/utils/tabConfigurationContext.ts new file mode 100644 index 00000000..84964338 --- /dev/null +++ b/with-native-tabs/utils/tabConfigurationContext.ts @@ -0,0 +1,9 @@ +import { createContext, type Dispatch, type SetStateAction } from "react"; + +export const TabConfigurationContext = createContext<{ + isMinimizeOnScrollEnabled: boolean; + setIsMinimizeOnScrollEnabled: Dispatch>; +}>({ + isMinimizeOnScrollEnabled: true, + setIsMinimizeOnScrollEnabled: () => {}, +}); From 76c6e413433f4bcd2deab8adc786e0fb38a93117 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz Date: Tue, 28 Oct 2025 14:28:56 +0100 Subject: [PATCH 2/2] add platform info and global config --- with-native-tabs/app/_layout.tsx | 20 ++++++++- with-native-tabs/app/explore.tsx | 43 ++++++++++++++++--- .../utils/tabConfigurationContext.ts | 8 ++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/with-native-tabs/app/_layout.tsx b/with-native-tabs/app/_layout.tsx index c79b42c6..ca4ade61 100644 --- a/with-native-tabs/app/_layout.tsx +++ b/with-native-tabs/app/_layout.tsx @@ -19,14 +19,30 @@ export default function RootLayout() { const scheme = useColorScheme(); const [isMinimizeOnScrollEnabled, setIsMinimizeOnScrollEnabled] = useState(true); + const [isIndicatorEnabled, setIsIndicatorEnabled] = useState(true); + const [ + isTransparentOnScrollEdgeEnabled, + setIsTransparentOnScrollEdgeEnabled, + ] = useState(true); return ( + + Scroll down diff --git a/with-native-tabs/utils/tabConfigurationContext.ts b/with-native-tabs/utils/tabConfigurationContext.ts index 84964338..16e6393e 100644 --- a/with-native-tabs/utils/tabConfigurationContext.ts +++ b/with-native-tabs/utils/tabConfigurationContext.ts @@ -3,7 +3,15 @@ import { createContext, type Dispatch, type SetStateAction } from "react"; export const TabConfigurationContext = createContext<{ isMinimizeOnScrollEnabled: boolean; setIsMinimizeOnScrollEnabled: Dispatch>; + isIndicatorEnabled: boolean; + setIsIndicatorEnabled: Dispatch>; + isTransparentOnScrollEdgeEnabled: boolean; + setIsTransparentOnScrollEdgeEnabled: Dispatch>; }>({ isMinimizeOnScrollEnabled: true, setIsMinimizeOnScrollEnabled: () => {}, + isIndicatorEnabled: true, + setIsIndicatorEnabled: () => {}, + isTransparentOnScrollEdgeEnabled: true, + setIsTransparentOnScrollEdgeEnabled: () => {}, });