├── .gitignore
├── README.md
├── app.json
├── app
├── _layout.tsx
└── index.tsx
├── assets
├── fonts
│ ├── Nunito.ttf
│ └── Quicksand.ttf
└── images
│ ├── adaptive-icon.png
│ ├── icon.png
│ └── splash-icon.png
├── components
└── PressableScale.tsx
├── constants
└── anagramWords.ts
├── package-lock.json
├── package.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 | expo-env.d.ts
11 |
12 | # Native
13 | *.orig.*
14 | *.jks
15 | *.p8
16 | *.p12
17 | *.key
18 | *.mobileprovision
19 |
20 | # Metro
21 | .metro-health-check*
22 |
23 | # debug
24 | npm-debug.*
25 | yarn-debug.*
26 | yarn-error.*
27 |
28 | # macOS
29 | .DS_Store
30 | *.pem
31 |
32 | # local env files
33 | .env*.local
34 |
35 | # typescript
36 | *.tsbuildinfo
37 |
38 | app-example
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-text-transition",
4 | "slug": "expo-text-transition",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "myapp",
9 | "userInterfaceStyle": "automatic",
10 | "newArchEnabled": true,
11 | "ios": {
12 | "supportsTablet": true
13 | },
14 | "android": {
15 | "adaptiveIcon": {
16 | "foregroundImage": "./assets/images/adaptive-icon.png",
17 | "backgroundColor": "#ffffff"
18 | }
19 | },
20 | "web": {
21 | "bundler": "metro",
22 | "output": "static",
23 | "favicon": "./assets/images/favicon.png"
24 | },
25 | "plugins": [
26 | "expo-router",
27 | [
28 | "expo-splash-screen",
29 | {
30 | "image": "./assets/images/splash-icon.png",
31 | "imageWidth": 200,
32 | "resizeMode": "contain",
33 | "backgroundColor": "#ffffff"
34 | }
35 | ]
36 | ],
37 | "experiments": {
38 | "typedRoutes": true
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DarkTheme,
3 | DefaultTheme,
4 | ThemeProvider,
5 | } from "@react-navigation/native";
6 | import { SplashScreen, Stack } from "expo-router";
7 | import { StatusBar } from "expo-status-bar";
8 | import { useEffect } from "react";
9 | import { useColorScheme } from "react-native";
10 | import { useFonts } from "expo-font";
11 | import "react-native-reanimated";
12 |
13 | SplashScreen.preventAutoHideAsync();
14 |
15 | export default function RootLayout() {
16 | const colorScheme = useColorScheme();
17 | const [loaded, error] = useFonts({
18 | Quicksand: require("../assets/fonts/Quicksand.ttf"),
19 | Nunito: require("../assets/fonts/Nunito.ttf"),
20 | });
21 |
22 | useEffect(() => {
23 | if (loaded || error) {
24 | SplashScreen.hideAsync();
25 | }
26 | }, [loaded, error]);
27 |
28 | if (!loaded && !error) {
29 | return null;
30 | }
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import { LogBox, StyleSheet, Text, View } from "react-native";
2 | import React, { useState } from "react";
3 | import Animated, {
4 | LayoutAnimationConfig,
5 | LinearTransition,
6 | withTiming,
7 | } from "react-native-reanimated";
8 | import { useTheme } from "@react-navigation/native";
9 | import { PressableScale } from "@/components/PressableScale";
10 | import { anagramWords } from "@/constants/anagramWords";
11 |
12 | LogBox.ignoreAllLogs();
13 |
14 | const Exiting = () => {
15 | "worklet";
16 | const animations = {
17 | opacity: withTiming(0, { duration: 250 }),
18 | transform: [{ scale: withTiming(0.5, { duration: 250 }) }],
19 | };
20 | const initialValues = {
21 | opacity: 1,
22 | transform: [{ scale: 1 }],
23 | };
24 | return {
25 | initialValues,
26 | animations,
27 | };
28 | };
29 |
30 | const Entering = () => {
31 | "worklet";
32 | const animations = {
33 | opacity: withTiming(1, { duration: 250 }),
34 | transform: [{ scale: withTiming(1, { duration: 250 }) }],
35 | };
36 | const initialValues = {
37 | opacity: 0,
38 | transform: [{ scale: 0.5 }],
39 | };
40 | return {
41 | initialValues,
42 | animations,
43 | };
44 | };
45 |
46 | export default function () {
47 | const theme = useTheme();
48 | const [value, setValue] = useState(anagramWords[0]);
49 | const valueArray = value.toString().split("");
50 |
51 | return (
52 |
53 |
54 |
58 | {valueArray.map((digit) => {
59 | return (
60 |
66 |
67 | {digit}
68 |
69 |
70 | );
71 | })}
72 |
73 |
74 | {anagramWords.map((word) => {
75 | const isActive = value === word;
76 | return (
77 | setValue(word)}
81 | >
82 |
93 | {word}
94 |
95 |
96 | );
97 | })}
98 |
99 |
100 |
101 | );
102 | }
103 |
104 | const styles = StyleSheet.create({
105 | container: {
106 | flex: 1,
107 | alignItems: "center",
108 | justifyContent: "center",
109 | },
110 | value: {
111 | fontSize: 84,
112 | fontWeight: "bold",
113 | marginBottom: 20,
114 | fontFamily: "Nunito",
115 | },
116 | buttonContainer: {
117 | flexDirection: "row",
118 | flexWrap: "wrap",
119 | alignItems: "center",
120 | justifyContent: "center",
121 | width: "90%",
122 | },
123 | button: {
124 | padding: 15,
125 | paddingVertical: 8,
126 | borderRadius: 10,
127 | textAlign: "center",
128 | },
129 | buttonText: {
130 | fontSize: 18,
131 | fontFamily: "Nunito",
132 | fontWeight: "600",
133 | },
134 | });
135 |
--------------------------------------------------------------------------------
/assets/fonts/Nunito.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-text-transition/382945069abae6b74943b99ff162979f03e55b3e/assets/fonts/Nunito.ttf
--------------------------------------------------------------------------------
/assets/fonts/Quicksand.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-text-transition/382945069abae6b74943b99ff162979f03e55b3e/assets/fonts/Quicksand.ttf
--------------------------------------------------------------------------------
/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-text-transition/382945069abae6b74943b99ff162979f03e55b3e/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-text-transition/382945069abae6b74943b99ff162979f03e55b3e/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-text-transition/382945069abae6b74943b99ff162979f03e55b3e/assets/images/splash-icon.png
--------------------------------------------------------------------------------
/components/PressableScale.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import React from "react";
3 | import type { PressableProps, StyleProp, ViewStyle } from "react-native";
4 | import { Pressable } from "react-native";
5 | import Animated, {
6 | cancelAnimation,
7 | runOnJS,
8 | useAnimatedStyle,
9 | useReducedMotion,
10 | useSharedValue,
11 | withTiming,
12 | } from "react-native-reanimated";
13 |
14 | const DEFAULT_TARGET_SCALE = 0.95;
15 |
16 | const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
17 |
18 | export const PressableScale = (
19 | params: {
20 | targetScale?: number;
21 | style?: StyleProp;
22 | } & Exclude
23 | ): ReactNode => {
24 | const {
25 | targetScale = DEFAULT_TARGET_SCALE,
26 | children,
27 | style,
28 | onPressIn,
29 | onPressOut,
30 | ...rest
31 | } = params;
32 | const reducedMotion = useReducedMotion();
33 |
34 | const scale = useSharedValue(1);
35 |
36 | const animatedStyle = useAnimatedStyle(() => ({
37 | transform: [{ scale: scale.value }],
38 | }));
39 |
40 | return (
41 | {
44 | "worklet";
45 | if (onPressIn) {
46 | runOnJS(onPressIn)(e);
47 | }
48 | cancelAnimation(scale);
49 | scale.value = withTiming(targetScale, { duration: 60 });
50 | }}
51 | onPressOut={(e) => {
52 | "worklet";
53 | if (onPressOut) {
54 | runOnJS(onPressOut)(e);
55 | }
56 | cancelAnimation(scale);
57 | scale.value = withTiming(1, { duration: 120 });
58 | }}
59 | style={[!reducedMotion && animatedStyle, style]}
60 | {...rest}
61 | >
62 | {children}
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/constants/anagramWords.ts:
--------------------------------------------------------------------------------
1 | export const anagramWords = [
2 | "curse",
3 | "tamers",
4 | "least",
5 | "desserts",
6 | "sisters",
7 | "stressed",
8 | "secure",
9 | "crate",
10 | "recuse",
11 | "react",
12 | "cater",
13 | "caret",
14 | "trace",
15 | "stales",
16 | "steals",
17 | "resists",
18 | "rescue",
19 | "master",
20 | "slates",
21 | "stream",
22 | ];
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo-text-transition",
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/bottom-tabs": "^7.2.0",
20 | "@react-navigation/native": "^7.0.14",
21 | "expo": "~52.0.37",
22 | "expo-blur": "~14.0.3",
23 | "expo-constants": "~17.0.7",
24 | "expo-font": "~13.0.4",
25 | "expo-haptics": "~14.0.1",
26 | "expo-linking": "~7.0.5",
27 | "expo-router": "~4.0.17",
28 | "expo-splash-screen": "~0.29.22",
29 | "expo-status-bar": "~2.0.1",
30 | "expo-symbols": "~0.2.2",
31 | "expo-system-ui": "~4.0.8",
32 | "expo-web-browser": "~14.0.2",
33 | "react": "18.3.1",
34 | "react-dom": "18.3.1",
35 | "react-native": "0.76.7",
36 | "react-native-gesture-handler": "~2.20.2",
37 | "react-native-reanimated": "~3.16.1",
38 | "react-native-safe-area-context": "4.12.0",
39 | "react-native-screens": "~4.4.0",
40 | "react-native-web": "~0.19.13",
41 | "react-native-webview": "13.12.5",
42 | "@react-native-community/slider": "4.5.5"
43 | },
44 | "devDependencies": {
45 | "@babel/core": "^7.25.2",
46 | "@types/jest": "^29.5.12",
47 | "@types/react": "~18.3.12",
48 | "@types/react-test-renderer": "^18.3.0",
49 | "jest": "^29.2.1",
50 | "jest-expo": "~52.0.4",
51 | "react-test-renderer": "18.3.1",
52 | "typescript": "^5.3.3"
53 | },
54 | "private": true,
55 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------