├── assets └── images │ ├── icon.png │ ├── splash.png │ ├── favicon.png │ ├── react-logo.png │ ├── adaptive-icon.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ └── partial-react-logo.png ├── expo-env.d.ts ├── babel.config.js ├── tsconfig.json ├── .gitignore ├── types └── index.tsx ├── components └── CustomBlurView.tsx ├── app ├── _layout.tsx └── index.tsx ├── app.json ├── package.json ├── README.md └── mock └── groupData.ts /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/react-logo.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /expo-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be in your git ignore -------------------------------------------------------------------------------- /assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunabhverma/expo-stack-list/HEAD/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.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 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 17 | # The following patterns were generated by expo-cli 18 | 19 | expo-env.d.ts 20 | # @end expo-cli -------------------------------------------------------------------------------- /types/index.tsx: -------------------------------------------------------------------------------- 1 | export type ITEM = { 2 | id: string; 3 | name: string; 4 | description: string; 5 | image: string; 6 | }; 7 | 8 | export type GROUP = ITEM & { 9 | subGroups: ITEM[]; 10 | }; 11 | 12 | export type GROUPS = GROUP[]; 13 | 14 | export type SubGroupRenderTypes = { 15 | val: ITEM; 16 | i: number; 17 | isSubGroups: boolean; 18 | }; 19 | -------------------------------------------------------------------------------- /components/CustomBlurView.tsx: -------------------------------------------------------------------------------- 1 | import { Platform, StyleSheet, useColorScheme } from "react-native"; 2 | import React from "react"; 3 | import { BlurView } from "expo-blur"; 4 | 5 | const CustomBlurView = () => { 6 | const tint = useColorScheme(); 7 | return ( 8 | 17 | ); 18 | }; 19 | 20 | export default CustomBlurView; 21 | -------------------------------------------------------------------------------- /app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | import { 3 | DarkTheme, 4 | DefaultTheme, 5 | ThemeProvider, 6 | } from "@react-navigation/native"; 7 | import { useColorScheme } from "react-native"; 8 | import "react-native-reanimated"; 9 | 10 | export default function RootLayout() { 11 | const colorScheme = useColorScheme(); 12 | 13 | return ( 14 | 15 | 16 | 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "expo-list-stack-item", 4 | "slug": "expo-list-stack-item", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "android": { 19 | "adaptiveIcon": { 20 | "foregroundImage": "./assets/images/adaptive-icon.png", 21 | "backgroundColor": "#ffffff" 22 | } 23 | }, 24 | "web": { 25 | "bundler": "metro", 26 | "output": "static", 27 | "favicon": "./assets/images/favicon.png" 28 | }, 29 | "plugins": ["expo-router"], 30 | "experiments": { 31 | "typedRoutes": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-list-stack-item", 3 | "main": "expo-router/entry", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "expo start", 7 | "reset-project": "node ./scripts/reset-project.js", 8 | "android": "expo start --android", 9 | "ios": "expo start --ios", 10 | "web": "expo start --web", 11 | "test": "jest --watchAll", 12 | "lint": "expo lint" 13 | }, 14 | "jest": { 15 | "preset": "jest-expo" 16 | }, 17 | "dependencies": { 18 | "@expo/vector-icons": "^14.0.2", 19 | "@react-navigation/elements": "^1.3.31", 20 | "@react-navigation/native": "^6.0.2", 21 | "expo": "~51.0.26", 22 | "expo-blur": "~13.0.2", 23 | "expo-constants": "~16.0.2", 24 | "expo-font": "~12.0.9", 25 | "expo-image": "~1.12.13", 26 | "expo-linking": "~6.3.1", 27 | "expo-router": "~3.5.21", 28 | "expo-splash-screen": "~0.27.5", 29 | "expo-status-bar": "~1.12.1", 30 | "expo-system-ui": "~3.0.7", 31 | "expo-web-browser": "~13.0.3", 32 | "react": "18.2.0", 33 | "react-dom": "18.2.0", 34 | "react-native": "0.74.5", 35 | "react-native-gesture-handler": "~2.16.1", 36 | "react-native-reanimated": "~3.10.1", 37 | "react-native-safe-area-context": "4.10.5", 38 | "react-native-screens": "3.31.1", 39 | "react-native-web": "~0.19.10" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.20.0", 43 | "@types/jest": "^29.5.12", 44 | "@types/react": "~18.2.45", 45 | "@types/react-test-renderer": "^18.0.7", 46 | "jest": "^29.2.1", 47 | "jest-expo": "~51.0.3", 48 | "react-test-renderer": "18.2.0", 49 | "typescript": "~5.3.3" 50 | }, 51 | "private": true 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Expo app 👋 2 | 3 | This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). 4 | 5 | ## Get started 6 | 7 | 1. Install dependencies 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | 2. Start the app 14 | 15 | ```bash 16 | npx expo start 17 | ``` 18 | 19 | In the output, you'll find options to open the app in a 20 | 21 | - [development build](https://docs.expo.dev/develop/development-builds/introduction/) 22 | - [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) 23 | - [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) 24 | - [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo 25 | 26 | You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). 27 | 28 | ## Get a fresh project 29 | 30 | When you're ready, run: 31 | 32 | ```bash 33 | npm run reset-project 34 | ``` 35 | 36 | This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. 37 | 38 | ## Learn more 39 | 40 | To learn more about developing your project with Expo, look at the following resources: 41 | 42 | - [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). 43 | - [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. 44 | 45 | ## Join the community 46 | 47 | Join our community of developers creating universal apps. 48 | 49 | - [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. 50 | - [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. 51 | -------------------------------------------------------------------------------- /mock/groupData.ts: -------------------------------------------------------------------------------- 1 | export const DATA = [ 2 | { 3 | id: "1a2b3c4d", 4 | name: "Tech Innovators", 5 | description: "We are discussing the latest in technology and innovation.", 6 | image: 7 | "https://img.freepik.com/free-photo/man-with-vr-glasses-experiencing-metaverse_23-2150904679.jpg?size=626&ext=jpg&ga=GA1.1.553209589.1714953600&semt=ais", 8 | subGroups: [ 9 | { 10 | id: "1a2b3c4d1", 11 | name: "AI Enthusiasts", 12 | description: "AI tricks and tips.", 13 | image: "https://picsum.photos/200/300?random=2", 14 | }, 15 | { 16 | id: "1a2b3c4d2", 17 | name: "Blockchain Pioneers", 18 | description: "Join for iscussions on blockchain technology.", 19 | image: "https://picsum.photos/200/300?random=2", 20 | }, 21 | ], 22 | }, 23 | { 24 | id: "3c4d5e6f", 25 | name: "Book Club", 26 | description: "Bookworms only.", 27 | image: "https://example.com/images/book-club.jpg", 28 | subGroups: [ 29 | { 30 | id: "3c4d5e6f1", 31 | name: "Fiction Fans", 32 | description: "fiction book discussions.", 33 | image: "https://example.com/images/fiction-fans.jpg", 34 | }, 35 | ], 36 | }, 37 | { 38 | id: "2b3c4d5e", 39 | name: "Fitness Freaks", 40 | description: "Only fitness enthusiasts.", 41 | image: "https://picsum.photos/200/300?random=2", 42 | subGroups: [], 43 | }, 44 | { 45 | id: "5e6f7g8h", 46 | name: "Foodies United", 47 | description: "This group for food lovers to share recipes and reviews.", 48 | image: "https://picsum.photos/200/300?random=2", 49 | subGroups: [ 50 | { 51 | id: "5e6f7g8h1", 52 | name: "Vegan Delights", 53 | description: "Vegan recipes", 54 | image: "https://picsum.photos/200/300?random=2", 55 | }, 56 | { 57 | id: "5e6f7g8h2", 58 | name: "Dessert Lovers", 59 | description: "Best Dessert recipes.", 60 | image: "https://picsum.photos/200/300?random=2", 61 | }, 62 | ], 63 | }, 64 | { 65 | id: "4d5e6f7g", 66 | name: "Travel Buddies", 67 | description: "We are sharing travel experiences and tips.", 68 | image: "https://example.com/images/travel-buddies.jpg", 69 | subGroups: [], 70 | }, 71 | { 72 | id: "6f7g8h9i", 73 | name: "Music Maniacs", 74 | description: "Join us to connect with fellow music enthusiasts", 75 | image: "https://picsum.photos/200/300?random=2", 76 | subGroups: [ 77 | { 78 | id: "6f7g8h9i1", 79 | name: "Rock and Roll", 80 | description: "Have fun.", 81 | image: "https://picsum.photos/200/300?random=2", 82 | }, 83 | { 84 | id: "6f7g8h9i2", 85 | name: "Classical Harmony", 86 | description: "Millenial", 87 | image: "https://picsum.photos/200/300?random=2", 88 | }, 89 | { 90 | id: "6f7g8h9i3", 91 | name: "Jazz Vibes", 92 | description: "Join for cool vibes.", 93 | image: "https://picsum.photos/200/300?random=", 94 | }, 95 | ], 96 | }, 97 | ]; 98 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | LayoutChangeEvent, 4 | Platform, 5 | Pressable, 6 | StyleSheet, 7 | Text, 8 | useColorScheme, 9 | View, 10 | } from "react-native"; 11 | import { Image } from "expo-image"; 12 | import Animated, { 13 | LinearTransition, 14 | SharedValue, 15 | useAnimatedStyle, 16 | useSharedValue, 17 | withSpring, 18 | withTiming, 19 | } from "react-native-reanimated"; 20 | import { useHeaderHeight } from "@react-navigation/elements"; 21 | import { useTheme } from "@react-navigation/native"; 22 | import CustomBlurView from "@/components/CustomBlurView"; 23 | import { GROUP, SubGroupRenderTypes } from "@/types"; 24 | import { DATA } from "@/mock/groupData"; 25 | import { Ionicons } from "@expo/vector-icons"; 26 | import { StatusBar } from "expo-status-bar"; 27 | 28 | const ITEM_HEIGHT = 80; 29 | const GAP = 8; 30 | 31 | const SubGroupRender = ({ val, i, isSubGroups }: SubGroupRenderTypes) => { 32 | const theme = useTheme(); 33 | 34 | const animatedSubGroupStyle = useAnimatedStyle(() => { 35 | return { 36 | transform: [ 37 | { 38 | scale: !isSubGroups ? withTiming(1 - 0.05 * (i + 1)) : withTiming(1), 39 | }, 40 | ], 41 | }; 42 | }); 43 | 44 | return ( 45 | 56 | 57 | 58 | {val.name} 59 | 60 | 61 | {val.description} 62 | 63 | 64 | ); 65 | }; 66 | 67 | const RenderItem = ({ 68 | item, 69 | bottomPadding, 70 | }: { 71 | item: GROUP; 72 | index: number; 73 | bottomPadding: SharedValue; 74 | }) => { 75 | const theme = useTheme(); 76 | const colorScheme = useColorScheme(); 77 | 78 | const [isSubGroups, setSubGroups] = useState(false); 79 | const [extraPadding, setExtraPadding] = useState(0); 80 | 81 | const animatedChevronIcon = useAnimatedStyle(() => { 82 | return { 83 | transform: [ 84 | { rotate: isSubGroups ? withSpring("180deg") : withSpring("0deg") }, 85 | ], 86 | }; 87 | }); 88 | 89 | const onLayout = (event: LayoutChangeEvent) => { 90 | const { height } = event.nativeEvent.layout; 91 | setExtraPadding(height); 92 | }; 93 | 94 | const setGroup = () => { 95 | if (isSubGroups) { 96 | setTimeout(() => { 97 | setSubGroups(false); 98 | }, 150); 99 | bottomPadding.value += extraPadding; 100 | setTimeout(() => { 101 | bottomPadding.value = withTiming(0, { duration: 800 }); 102 | }, 200); 103 | } else { 104 | setSubGroups(true); 105 | } 106 | }; 107 | 108 | return ( 109 | 113 | 126 | 127 | {item.subGroups?.length > 0 && ( 128 | 129 | 130 | 131 | )} 132 | 135 | {item.name} 136 | 137 | 138 | {item.description} 139 | 140 | 141 | {item.subGroups?.length > 0 && ( 142 | 143 | 152 | {item.subGroups.map((val, i) => ( 153 | 159 | ))} 160 | 161 | 162 | )} 163 | 164 | ); 165 | }; 166 | 167 | const Main = () => { 168 | const bottomPadding = useSharedValue(0); 169 | const headerHeight = useHeaderHeight(); 170 | 171 | const animatedPadding = useAnimatedStyle(() => { 172 | return { 173 | paddingBottom: bottomPadding.value, 174 | }; 175 | }); 176 | 177 | return ( 178 | 179 | {Platform.OS === "android" && } 180 | 181 | 185 | {DATA.map((item, index) => ( 186 | 192 | ))} 193 | 194 | 195 | 204 | 205 | ); 206 | }; 207 | 208 | export default Main; 209 | 210 | const styles = StyleSheet.create({ 211 | container: { 212 | flex: 1, 213 | }, 214 | contentContainer: { 215 | gap: GAP, 216 | margin: 20, 217 | paddingBottom: 20, 218 | }, 219 | bgImage: { 220 | ...StyleSheet.absoluteFillObject, 221 | zIndex: -1, 222 | }, 223 | groupListItem: { 224 | height: ITEM_HEIGHT, 225 | justifyContent: "center", 226 | paddingHorizontal: 15, 227 | borderRadius: 10, 228 | overflow: "hidden", 229 | }, 230 | subGroupWrapper: { 231 | position: "relative", 232 | width: "100%", 233 | }, 234 | cardShadow: { 235 | shadowColor: "#000", 236 | shadowOffset: { 237 | width: 0, 238 | height: 0, 239 | }, 240 | shadowOpacity: 0.1, 241 | shadowRadius: 5, 242 | elevation: 5, 243 | }, 244 | title: { 245 | fontSize: 15, 246 | fontWeight: "600", 247 | }, 248 | desc: { 249 | marginTop: 2, 250 | fontSize: 15, 251 | fontWeight: "500", 252 | }, 253 | chevronIcon: { 254 | position: "absolute", 255 | top: 5, 256 | right: 5, 257 | opacity: 0.8, 258 | }, 259 | }); 260 | --------------------------------------------------------------------------------