├── 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 |
--------------------------------------------------------------------------------