├── .expo-shared
├── README.md
└── assets.json
├── .github
└── dependabot.yml
├── .gitignore
├── .prettierrc
├── App.tsx
├── README.md
├── app.json
├── assets
├── adaptive-icon.png
├── expoqr.png
├── favicon.png
├── icon.png
├── reanimated-visualizer-mohit23x-mockup.png
└── splash.png
├── babel.config.js
├── package.json
├── src
├── assets
│ ├── Chart.tsx
│ ├── Copy.tsx
│ ├── Github.tsx
│ ├── Move.tsx
│ ├── ReactNative.tsx
│ ├── Rotate.tsx
│ ├── Scale.tsx
│ ├── Setting.tsx
│ ├── Spring.tsx
│ └── Timing.tsx
├── components
│ ├── Actions.tsx
│ ├── AnimatedNumber.tsx
│ ├── AnimationOptions.tsx
│ ├── Attribution.tsx
│ ├── Chart.tsx
│ ├── Chip.tsx
│ ├── ConfigureLimit.tsx
│ ├── GithubIcon.tsx
│ ├── InputRenderer.tsx
│ ├── InputSection.tsx
│ ├── Movement.tsx
│ ├── OutputSection.tsx
│ ├── PlayButton.tsx
│ ├── Rotate.tsx
│ ├── Scale.tsx
│ ├── Select.tsx
│ ├── SettingModal.tsx
│ ├── Slider.tsx
│ ├── Tabs.tsx
│ ├── WithSpring.tsx
│ ├── WithTiming.tsx
│ ├── index.tsx
│ └── ui.tsx
├── constants
│ ├── TimingConfig.ts
│ └── index.tsx
├── screen
│ └── HomeScreen.tsx
├── styles
│ └── index.tsx
├── types
│ └── index.ts
└── utils
│ └── index.tsx
├── tsconfig.json
└── yarn.lock
/.expo-shared/README.md:
--------------------------------------------------------------------------------
1 | > Why do I have a folder named ".expo-shared" in my project?
2 |
3 | The ".expo-shared" folder is created when running commands that produce state that is intended to be shared with all developers on the project. For example, "npx expo-optimize".
4 |
5 | > What does the "assets.json" file contain?
6 |
7 | The "assets.json" file describes the assets that have been optimized through "expo-optimize" and do not need to be processed again.
8 |
9 | > Should I commit the ".expo-shared" folder?
10 |
11 | Yes, you should share the ".expo-shared" folder with your collaborators.
12 |
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "9d69cbe74fa841f48d08c605454c3dfcb0efe144f1737ecd9dfb90ea2681fd91": true,
3 | "ed1af881baff41bbd15236d9885eab1079701b6176126e6fa1601a6dec2c9389": true,
4 | "b888dc1584fa5acca6f0d1fbe76c19b06d2bd15fe7b27c6e993f6d2c2fe88fd3": true,
5 | "7154a908d6b7247386cc43fd67b307ad0fe67a6b78903e6bdf4358a5d921c869": true,
6 | "aa44c963e3440d8c16abce5c76ea0950f20876acdf6300b90e18334b46500a63": true
7 | }
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: monthly
7 | time: "23:30"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: "@babel/core"
11 | versions:
12 | - 7.13.16
13 | - dependency-name: "@react-native-async-storage/async-storage"
14 | versions:
15 | - 1.15.3
16 | - dependency-name: react-native-web
17 | versions:
18 | - 0.15.4
19 | - 0.16.0
20 | - dependency-name: expo-clipboard
21 | versions:
22 | - 1.0.2
23 | - dependency-name: "@types/react-dom"
24 | versions:
25 | - 17.0.2
26 | - dependency-name: react-native-reanimated
27 | versions:
28 | - 2.0.0
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 |
12 | # macOS
13 | .DS_Store
14 |
15 | .idea
16 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import * as React from 'react';
3 | import { SafeAreaView } from 'react-native';
4 | import { GithubIcon } from 'src/components/GithubIcon';
5 | import { HomeScreen } from 'src/screen/HomeScreen';
6 | import { ThemeProvider, StyleSheet } from 'src/styles';
7 |
8 | export default function App() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | const s = StyleSheet.create((theme) => ({
21 | safeArea: {
22 | flex: 1,
23 | backgroundColor: theme.background,
24 | },
25 | }));
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Reanimated Config Visualizer
2 |
3 | Works on Android, IOS and web in both potrait and landscape mode.
4 |
5 |
6 | 
7 |
8 | ## Live Demo
9 |
10 | Use the expo app and scan this QR
11 |
12 | https://expo.io/@mohit23x/projects/reanimated-config-visualizer
13 |
14 | 
15 |
16 | or
17 |
18 | [Click here for the web version](https://mohit23x.github.io/reanimated-config-visualizer/)
19 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "reanimated-config-visualizer",
4 | "slug": "reanimated-config-visualizer",
5 | "version": "1.0.0",
6 | "orientation": "default",
7 | "description": "Reanimated animation config visualizer",
8 | "icon": "./assets/icon.png",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "updates": {
15 | "fallbackToCacheTimeout": 0
16 | },
17 | "assetBundlePatterns": ["**/*"],
18 | "ios": {
19 | "supportsTablet": true
20 | },
21 | "android": {
22 | "adaptiveIcon": {
23 | "foregroundImage": "./assets/adaptive-icon.png",
24 | "backgroundColor": "#FFFFFF"
25 | }
26 | },
27 | "web": {
28 | "favicon": "./assets/favicon.png"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/expoqr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/expoqr.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/icon.png
--------------------------------------------------------------------------------
/assets/reanimated-visualizer-mohit23x-mockup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/reanimated-visualizer-mohit23x-mockup.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohit23x/reanimated-config-visualizer/5f27b9bcb1c0f3a875d6513074c2b51b3f7873d3/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: [
6 | [
7 | 'module-resolver',
8 | {
9 | alias: {
10 | src: './src/',
11 | },
12 | },
13 | ],
14 | ['react-native-reanimated/plugin'],
15 | ],
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject",
9 | "deploy": "gh-pages -d web-build",
10 | "predeploy": "expo export:web"
11 | },
12 | "dependencies": {
13 | "@expo/html-elements": "^0.0.2",
14 | "@expo/webpack-config": "^0.17.0",
15 | "@react-native-async-storage/async-storage": "~1.17.3",
16 | "@react-native-community/slider": "3.0.3",
17 | "@react-native-picker/picker": "2.4.2",
18 | "@types/react-helmet": "^6.1.3",
19 | "expo": "^46.0.0",
20 | "expo-clipboard": "~3.1.0",
21 | "expo-status-bar": "~1.4.0",
22 | "react": "18.0.0",
23 | "react-dom": "18.0.0",
24 | "react-helmet": "^6.1.0",
25 | "react-native": "0.69.6",
26 | "react-native-easing": "^1.0.1",
27 | "react-native-reanimated": "~2.9.1",
28 | "react-native-redash": "^16.1.1",
29 | "react-native-sugar-style": "^0.1.22",
30 | "react-native-svg": "12.3.0",
31 | "react-native-web": "~0.18.7"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "^7.18.6",
35 | "@types/react": "~18.0.0",
36 | "@types/react-dom": "~18.0.0",
37 | "@types/react-native": "~0.69.1",
38 | "gh-pages": "^3.2.2",
39 | "typescript": "^4.6.3"
40 | },
41 | "homepage": "http://mohit23x.github.io/reanimated-config-visualizer",
42 | "private": true
43 | }
44 |
--------------------------------------------------------------------------------
/src/assets/Chart.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Svg, { Rect } from 'react-native-svg';
3 |
4 | const ChartIcon = () => (
5 |
11 | );
12 |
13 | export default ChartIcon;
14 |
--------------------------------------------------------------------------------
/src/assets/Copy.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
18 | );
19 | }
20 |
21 | export default SvgComponent;
22 |
--------------------------------------------------------------------------------
/src/assets/Github.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
36 | );
37 | }
38 |
39 | export default SvgComponent;
40 |
--------------------------------------------------------------------------------
/src/assets/Move.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
10 | );
11 | }
12 |
13 | export default SvgComponent;
14 |
--------------------------------------------------------------------------------
/src/assets/ReactNative.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Circle, G, Ellipse } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
14 | );
15 | }
16 |
17 | export default SvgComponent;
18 |
--------------------------------------------------------------------------------
/src/assets/Rotate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
18 | );
19 | }
20 |
21 | export default SvgComponent;
22 |
--------------------------------------------------------------------------------
/src/assets/Scale.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
16 | );
17 | }
18 |
19 | export default SvgComponent;
20 |
--------------------------------------------------------------------------------
/src/assets/Setting.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
19 | );
20 | }
21 |
22 | export default SvgComponent;
23 |
--------------------------------------------------------------------------------
/src/assets/Spring.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
9 | );
10 | }
11 |
12 | export default SvgComponent;
13 |
--------------------------------------------------------------------------------
/src/assets/Timing.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Svg, { Path } from 'react-native-svg';
3 |
4 | function SvgComponent(props) {
5 | return (
6 |
17 | );
18 | }
19 |
20 | export default SvgComponent;
21 |
--------------------------------------------------------------------------------
/src/components/Actions.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TouchableOpacity, View } from 'react-native';
3 | import { StyleSheet } from 'src/styles';
4 | import { PlayButton } from './PlayButton';
5 | import SettingIcon from 'src/assets/Setting';
6 | import CopyIcon from 'src/assets/Copy';
7 |
8 | export const Actions = ({
9 | onPlay,
10 | handleSetting,
11 | handleCopy,
12 | }: {
13 | onPlay: () => void;
14 | handleSetting: () => void;
15 | handleCopy: () => void;
16 | }) => {
17 | const onCopyPress = () => {
18 | handleCopy();
19 | alert('🎉🎉🎉copied!🎉🎉🎉');
20 | };
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | const s = StyleSheet.create((theme) => ({
39 | row: { flexDirection: 'row', alignItems: 'center' },
40 | setting: {
41 | height: 50,
42 | paddingHorizontal: 15,
43 | backgroundColor: StyleSheet.theme.primary,
44 | justifyContent: 'center',
45 | borderRadius: theme.borderRadius.m,
46 | marginRight: 5,
47 | },
48 | }));
49 |
--------------------------------------------------------------------------------
/src/components/AnimatedNumber.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { StyleSheet } from 'src/styles';
3 | import Animated, {
4 | useAnimatedRef,
5 | useDerivedValue,
6 | } from 'react-native-reanimated';
7 | import { TextInput } from 'react-native';
8 | import { ReText } from 'react-native-redash';
9 |
10 | const FIXED = 1000;
11 | Animated.addWhitelistedNativeProps({ text: true });
12 | const ReTextInput = Animated.createAnimatedComponent(TextInput);
13 |
14 | type Props = { x: Animated.SharedValue };
15 |
16 | const WebText = ({ x }: Props) => {
17 | const inputAnimatedRef = useAnimatedRef();
18 |
19 | useDerivedValue(() => {
20 | if (inputAnimatedRef && inputAnimatedRef.current) {
21 | inputAnimatedRef.current?.setNativeProps({
22 | text: (Math.floor(x.value * FIXED) / FIXED).toString(),
23 | });
24 | }
25 | return 0;
26 | });
27 |
28 | return (
29 |
35 | );
36 | };
37 |
38 | const NativeText = ({ x }: Props) => {
39 | const animatedText = useDerivedValue(() =>
40 | (Math.floor(x.value * FIXED) / FIXED).toString()
41 | );
42 |
43 | return ;
44 | };
45 |
46 | export const AnimatedNumber = ({ x }: Props) => {
47 | if (StyleSheet.constants.platform.web) {
48 | return ;
49 | }
50 |
51 | return ;
52 | };
53 |
54 | const s = StyleSheet.create((theme) => ({
55 | text: {
56 | color: theme.text,
57 | textAlign: 'right',
58 | fontSize: theme.font.m,
59 | opacity: 0.8,
60 | },
61 | }));
62 |
--------------------------------------------------------------------------------
/src/components/AnimationOptions.tsx:
--------------------------------------------------------------------------------
1 | import { P } from '@expo/html-elements';
2 | import * as React from 'react';
3 | import { View, ScrollView, TouchableOpacity } from 'react-native';
4 | import { ExampleKeyType } from 'src/constants';
5 | import { StyleSheet } from 'src/styles';
6 |
7 | export const Option = ({
8 | option,
9 | toggleAnimation,
10 | active,
11 | }: {
12 | option: any;
13 | active: ExampleKeyType;
14 | toggleAnimation: (name: ExampleKeyType) => void;
15 | }) => {
16 | const Icon = option.icon;
17 |
18 | const activeColor =
19 | active === option.key ? option.color : StyleSheet.theme.icon;
20 |
21 | return (
22 | toggleAnimation(option.key)}>
23 |
24 |
30 | {option.title}
31 |
32 |
33 | );
34 | };
35 |
36 | export const AnimationOptions = ({
37 | options,
38 | toggleAnimation,
39 | active,
40 | }: {
41 | options: any;
42 | toggleAnimation: (name: ExampleKeyType) => void;
43 | active: ExampleKeyType;
44 | }) => {
45 | return (
46 |
47 | 480 ? true : false}
50 | >
51 | {options.map((option) => (
52 |
58 | ))}
59 |
60 |
61 | );
62 | };
63 |
64 | const s = StyleSheet.create((theme) => ({
65 | container: {
66 | maxWidth: [60, undefined, undefined],
67 | maxHeight: [undefined, 70],
68 | marginHorizontal: 8,
69 | marginVertical: [0, 10],
70 | },
71 | scroll: {
72 | backgroundColor: theme.surface,
73 | flexGrow: 1,
74 | justifyContent: 'space-evenly',
75 | paddingHorizontal: [8, undefined],
76 | paddingVertical: [undefined, 8],
77 | borderRadius: theme.borderRadius.m,
78 | },
79 | outputOption: {
80 | alignItems: 'center',
81 | justifyContent: 'center',
82 | },
83 | svg: {
84 | height: 30,
85 | width: 30,
86 | },
87 | outputText: {
88 | fontSize: 12,
89 | marginVertical: 5,
90 | fontWeight: 'bold',
91 | },
92 | }));
93 |
--------------------------------------------------------------------------------
/src/components/Attribution.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { StyleSheet } from 'src/styles';
4 | import { H1, A } from '@expo/html-elements';
5 |
6 | const GithubURL = 'https://github.com/mohit23x/reanimated-config-visualizer';
7 |
8 | const TECH = [
9 | { title: 'React Native', url: 'https://reactnative.dev/' },
10 | { title: 'Expo', url: 'https://expo.io/' },
11 | {
12 | title: 'React Native Reanimated',
13 | url: 'https://docs.swmansion.com/react-native-reanimated/',
14 | },
15 | {
16 | title: 'React Native Web',
17 | url: 'https://necolas.github.io/react-native-web/',
18 | },
19 | {
20 | title: 'React Native Redash',
21 | url: 'https://github.com/wcandillon/react-native-redash',
22 | },
23 | {
24 | title: '@react-native-community/slider',
25 | url: 'https://github.com/callstack/react-native-slider',
26 | },
27 | {
28 | title: '@react-native-picker/picker',
29 | url: 'https://www.npmjs.com/package/@react-native-community/picker',
30 | },
31 | {
32 | title: 'React Native Svg',
33 | url: 'react-native-svg',
34 | },
35 | {
36 | title: 'React Native Sugar Style',
37 | url: 'https://github.com/mohit23x/react-native-sugar-style',
38 | },
39 | ];
40 |
41 | const Link = ({ title, url }: { title: string; url: string }) => {
42 | return (
43 |
51 | {title}
52 |
53 | );
54 | };
55 |
56 | export const Attribution = () => {
57 | return (
58 |
65 |
66 | Thanks to for
67 | the assets
68 |
69 |
70 | Made with
71 |
72 | {TECH.map((d) => (
73 |
74 | ))}
75 |
76 | Open source
77 | Made with ❤️ by Mohit23x
78 |
79 | source available at
80 |
81 |
82 | );
83 | };
84 |
85 | const s = StyleSheet.create((theme) => ({
86 | title: {
87 | color: theme.text,
88 | fontWeight: 'bold',
89 | fontSize: theme.font.m,
90 | textAlign: 'center',
91 | marginVertical: 2,
92 | paddingTop: 10,
93 | },
94 | label: {
95 | color: theme.text,
96 | },
97 | }));
98 |
--------------------------------------------------------------------------------
/src/components/Chart.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { runOnJS, useFrameCallback } from 'react-native-reanimated';
3 | import { AnimationCompProps } from 'src/types';
4 | import { useEffect, useRef, useState } from 'react';
5 | import Svg, { Polyline } from 'react-native-svg';
6 | import { View } from 'react-native';
7 |
8 | const LineChart = ({data, width, height} : {data: Array, width: number, height: number}) => {
9 | const maxYValue = Math.max(...data);
10 | const minYValue = Math.min(...data);
11 | const points = data
12 | .map((point: number, index: number) => {
13 | const x = (width / (data.length - 1)) * index;
14 | const y = ((point - minYValue) / (maxYValue - minYValue)) * height;
15 | return `${x},${y}`;
16 | })
17 | .join(' ');
18 | // @ts-ignore
19 | return
20 |
28 |
29 | }
30 |
31 | export const Chart = ({ x, running }: AnimationCompProps) => {
32 | const trackedValueRef = useRef([])
33 | const [trackedValues, setTrackedValues] = useState([]);
34 |
35 | const setValuesInRef = (newValue: number) => {
36 | trackedValueRef.current = [...trackedValueRef.current, newValue]
37 | }
38 |
39 | const frameCallback = useFrameCallback(() => {
40 | const currentValue = x.value;
41 | runOnJS(setValuesInRef)(currentValue);
42 | })
43 |
44 | useEffect(() => {
45 | if (!running && trackedValueRef.current.length !== 0) {
46 | frameCallback.setActive(false)
47 | setTrackedValues(trackedValueRef.current)
48 | trackedValueRef.current = []
49 | }
50 | if (running) {
51 | frameCallback.setActive(true)
52 | }
53 | }, [running]);
54 |
55 |
56 |
57 | return
58 |
59 | };
60 |
61 |
--------------------------------------------------------------------------------
/src/components/Chip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { StyleSheet } from 'src/styles';
4 |
5 | // https://stackoverflow.com/questions/59827667/how-to-implement-bezier-function-in-react-native-animation
6 |
7 | export default function Chip = ({title="With Spring"}) => {
8 | return (
9 |
10 | {title}
11 |
12 | )
13 | }
14 |
15 | const s = StyleSheet.create(() => ({
16 | container: {
17 |
18 | },
19 | text: {
20 |
21 | }
22 | }))
--------------------------------------------------------------------------------
/src/components/ConfigureLimit.tsx:
--------------------------------------------------------------------------------
1 | import { H1, H6 } from '@expo/html-elements';
2 | import * as React from 'react';
3 | import { View, Text, TouchableOpacity } from 'react-native';
4 | import { LimitType } from 'src/constants';
5 | import { StyleSheet } from 'src/styles';
6 | import { Button } from './ui';
7 |
8 | export const ConfigureLimit = ({
9 | limit,
10 | setConfig,
11 | handleSave,
12 | handleReset,
13 | }: {
14 | limit: LimitType;
15 | setConfig: (a: any) => void;
16 | handleSave: () => void;
17 | handleReset: () => void;
18 | }) => {
19 | return (
20 |
21 | Configure max limits
22 |
27 |
28 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | const InputCounter = ({
48 | field,
49 | setConfig,
50 | value,
51 | }: {
52 | field: keyof LimitType;
53 | setConfig: (a: any) => void;
54 | value: number;
55 | }) => {
56 | const increase = () => {
57 | setConfig({ [field]: value + 2 });
58 | };
59 | const decrease = () => setConfig({ [field]: value - 2 });
60 |
61 | return (
62 |
63 |
64 | {field}
65 |
66 |
67 |
68 | -
69 |
70 | {value}
71 |
72 | +
73 |
74 |
75 |
76 | );
77 | };
78 |
79 | const s = StyleSheet.create((theme) => ({
80 | label: {
81 | color: theme.text,
82 | },
83 | counterContainer: {
84 | flexDirection: 'row',
85 | alignItems: 'center',
86 | marginVertical: 5,
87 | },
88 | heading: {
89 | color: theme.text,
90 | fontSize: theme.font.m,
91 | },
92 | counterBtn: {
93 | height: 40,
94 | width: 40,
95 | justifyContent: 'center',
96 | alignItems: 'center',
97 | backgroundColor: theme.surface,
98 | marginHorizontal: 10,
99 | borderRadius: theme.borderRadius.s,
100 | },
101 | btnText: {
102 | fontWeight: 'bold',
103 | fontSize: theme.font.m,
104 | color: theme.text,
105 | },
106 | counterValue: {
107 | color: theme.text,
108 | minWidth: 50,
109 | textAlign: 'center',
110 | },
111 | btnContainer: {
112 | justifyContent: ['space-evenly', 'flex-end'],
113 | flexDirection: 'row',
114 | marginVertical: 10,
115 | },
116 | }));
117 |
--------------------------------------------------------------------------------
/src/components/GithubIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Linking, Platform, TouchableOpacity, View } from 'react-native';
3 | import Github from '../assets/Github';
4 |
5 | export function GithubIcon() {
6 | const handleGithubIconPress = () => {
7 | Linking.openURL('https://github.com/mohit23x/reanimated-config-visualizer');
8 | };
9 |
10 | if (Platform.OS !== 'web') return null;
11 |
12 | return (
13 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/InputRenderer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Actions } from './Actions';
3 | import { View } from 'react-native';
4 |
5 | export const InputRenderer = ({ children, ...props }) => {
6 | return (
7 | <>
8 |
15 | {children}
16 |
17 |
18 | >
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/InputSection.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import AsyncStorage from '@react-native-async-storage/async-storage';
3 | import { Section } from './ui';
4 | import { SettingModal } from './SettingModal';
5 | import { StyleSheet } from 'src/styles';
6 | import { ActionTypes } from 'src/types';
7 | import {
8 | AnimationTypes,
9 | ConfigType,
10 | DEFAULT_MAX_LIMIT,
11 | SpringConfigType,
12 | SpringLimitType,
13 | StateType,
14 | Step,
15 | TimingConfigType,
16 | } from 'src/constants';
17 | import { Tabs } from './Tabs';
18 | import { WithTiming } from './WithTiming';
19 | import { WithSprings } from './WithSpring';
20 |
21 | const persistConfig = (payload: SpringLimitType) => {
22 | AsyncStorage.setItem('config', JSON.stringify(payload));
23 | };
24 |
25 | const reducer = (
26 | state: SpringLimitType = DEFAULT_MAX_LIMIT,
27 | action: ActionTypes
28 | ) => {
29 | switch (action.type) {
30 | case 'SET_CONFIG':
31 | return { ...state, ...action.payload };
32 | default:
33 | return state;
34 | }
35 | };
36 |
37 | export const InputSection = ({
38 | onPlay,
39 | state,
40 | handleChange,
41 | handleAnimationType,
42 | }: {
43 | state: StateType;
44 | onPlay: () => void;
45 | handleChange: (a: Partial) => void;
46 | handleAnimationType: (a: AnimationTypes) => void;
47 | }) => {
48 | const [LIMIT, dispatch] = React.useReducer(reducer, DEFAULT_MAX_LIMIT);
49 | const [show, setShow] = React.useState(false);
50 |
51 | const toggleModal = () => setShow((c) => !c);
52 |
53 | const getConfig = async () => {
54 | const data = await AsyncStorage.getItem('config');
55 | if (data) {
56 | dispatch({ type: 'SET_CONFIG', payload: JSON.parse(data) });
57 | } else {
58 | dispatch({ type: 'SET_CONFIG', payload: DEFAULT_MAX_LIMIT });
59 | }
60 | };
61 |
62 | const handleSave = () => {
63 | persistConfig(LIMIT);
64 | alert('saved');
65 | };
66 |
67 | const handleReset = () => {
68 | setConfig(DEFAULT_MAX_LIMIT);
69 | persistConfig(DEFAULT_MAX_LIMIT);
70 | alert('done');
71 | };
72 |
73 | const setConfig = (obj: Partial) => {
74 | const payload = Object.assign({}, LIMIT, obj);
75 | dispatch({ type: 'SET_CONFIG', payload });
76 | };
77 |
78 | React.useEffect(() => {
79 | getConfig();
80 | }, []);
81 |
82 | return (
83 |
84 |
88 | {state.animationType === 'timing' && (
89 |
95 | )}
96 |
97 | {state.animationType === 'spring' && (
98 |
106 | )}
107 |
108 |
116 |
117 | );
118 | };
119 |
120 | const s = StyleSheet.create({
121 | section: {
122 | flex: 1,
123 | alignItems: 'center',
124 | justifyContent: 'space-evenly',
125 | // backgroundColor: 'green',
126 | },
127 | });
128 |
--------------------------------------------------------------------------------
/src/components/Movement.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View } from 'react-native';
3 | import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4 | import { StyleSheet } from 'src/styles';
5 | import { AnimationCompProps } from 'src/types';
6 |
7 | export const Movement = ({ x, backgroundColor }: AnimationCompProps) => {
8 | const animatedStyles = useAnimatedStyle(() => {
9 | return {
10 | transform: [{ translateY: x.value * 100 }],
11 | };
12 | });
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | const s = StyleSheet.create((theme) => ({
22 | box: {
23 | height: [100, 150],
24 | width: [120, 200],
25 | backgroundColor: theme.primary,
26 | opacity: theme.opacity.fade,
27 | margin: -50,
28 | },
29 | boxBorder: {
30 | height: [100, 150],
31 | width: [120, 200],
32 | borderWidth: 2,
33 | borderColor: theme.border,
34 | alignItems: 'center',
35 | },
36 | }));
37 |
--------------------------------------------------------------------------------
/src/components/OutputSection.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View } from 'react-native';
3 | import { Section } from './ui';
4 | import { StyleSheet } from 'src/styles';
5 | import { AnimatedNumber } from 'src/components/AnimatedNumber';
6 | import { AnimationOptions } from './AnimationOptions';
7 | import Animated from 'react-native-reanimated';
8 | import { ExampleKeyType, examples } from 'src/constants';
9 |
10 | export const renderComponent = (key: ExampleKeyType) => {
11 | return examples[key].component;
12 | };
13 |
14 | export const options = Object.keys(examples).map((d) => {
15 | const item = examples[d];
16 | return { title: item.title, icon: item.icon, key: d, color: item.color };
17 | });
18 |
19 | export const OutputSection = ({
20 | x,
21 | stopAnimation,
22 | running
23 | }: {
24 | x: Animated.SharedValue;
25 | stopAnimation: () => void;
26 | running: boolean
27 | }) => {
28 | const [active, setActive] = React.useState('rotate');
29 |
30 | const toggleAnimation = (name: ExampleKeyType) => {
31 | stopAnimation();
32 | setActive(name);
33 | };
34 |
35 | const AnimationComp = renderComponent(active);
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
53 | );
54 | };
55 |
56 | const s = StyleSheet.create((theme) => ({
57 | container: {
58 | flexDirection: ['row', 'column'],
59 | flex: 1,
60 | },
61 | head: {
62 | flexDirection: 'row',
63 | alignItems: 'center',
64 | justifyContent: 'flex-end',
65 | marginHorizontal: 10,
66 | },
67 | playground: {
68 | flex: 1,
69 | justifyContent: 'center',
70 | alignItems: 'center',
71 | },
72 | }));
73 |
--------------------------------------------------------------------------------
/src/components/PlayButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { StyleSheet } from 'src/styles';
3 | import { Button } from './ui';
4 |
5 | export const PlayButton = ({ onPress }: { onPress: () => void }) => {
6 | const handlePress = () => onPress();
7 |
8 | return (
9 |
15 | );
16 | };
17 |
18 | const s = StyleSheet.create({
19 | btn: {
20 | backgroundColor: StyleSheet.theme.primary,
21 | minWidth: 220,
22 | },
23 | text: {
24 | fontWeight: 'bold',
25 | letterSpacing: 2,
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/Rotate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View } from 'react-native';
3 | import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4 | import { StyleSheet } from 'src/styles';
5 | import { AnimationCompProps } from 'src/types';
6 |
7 | export const Rotate = ({ x, backgroundColor }: AnimationCompProps) => {
8 | const animatedStyles = useAnimatedStyle(() => {
9 | return {
10 | transform: [{ rotate: `${x.value * 30}deg` }],
11 | };
12 | });
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | const s = StyleSheet.create((theme, constants) => ({
22 | box: {
23 | height: 180,
24 | width: 180,
25 | backgroundColor: theme.primary,
26 | opacity: theme.opacity.fade,
27 | },
28 | boxBorder: {
29 | borderWidth: 2,
30 | borderColor: theme.border,
31 | },
32 | }));
33 |
--------------------------------------------------------------------------------
/src/components/Scale.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View } from 'react-native';
3 | import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4 | import { StyleSheet } from 'src/styles';
5 | import { AnimationCompProps } from 'src/types';
6 |
7 | export const Scale = ({ x, backgroundColor }: AnimationCompProps) => {
8 | const animatedStyles = useAnimatedStyle(() => {
9 | return {
10 | transform: [{ scale: x.value }],
11 | };
12 | });
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | const s = StyleSheet.create((theme, constants) => ({
22 | circle: {
23 | height: 180,
24 | width: 180,
25 | backgroundColor: theme.primary,
26 | opacity: theme.opacity.fade,
27 | borderRadius: 100,
28 | },
29 | circleBorder: {
30 | borderWidth: 2,
31 | borderColor: theme.border,
32 | borderRadius: 100,
33 | },
34 | }));
35 |
--------------------------------------------------------------------------------
/src/components/Select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { Picker, PickerIOS } from '@react-native-picker/picker';
4 | import { StyleSheet } from 'src/styles';
5 |
6 | export const Select = ({
7 | title,
8 | data = [],
9 | onValueChange,
10 | selectedValue,
11 | labelFormatter = (a) => a,
12 | }: {
13 | title?: string;
14 | data?: Array<{ label: any; value: any }>;
15 | onValueChange: (a: any) => void;
16 | selectedValue: any;
17 | labelFormatter?: (a: string) => string;
18 | }) => {
19 | const onChange = (a) => {
20 | onValueChange(a);
21 | };
22 |
23 | return (
24 |
25 | {title}
26 | {StyleSheet.constants.platform.ios ? (
27 |
33 | {data.map((d) => (
34 |
39 | ))}
40 |
41 | ) : (
42 |
43 |
50 | {data.map((d) => (
51 |
56 | ))}
57 |
58 |
59 | )}
60 |
61 | );
62 | };
63 |
64 | const s = StyleSheet.create((theme, constants) => ({
65 | pickerContainer: {
66 | height: 40,
67 | width: [constants.width - 100, '100%'],
68 | borderWidth: 1,
69 | borderRadius: theme.borderRadius.m,
70 | overflow: 'hidden',
71 | borderColor: theme.text,
72 | backgroundColor: theme.background,
73 | },
74 | picker: {
75 | backgroundColor: theme.background,
76 | borderWidth: 0,
77 | height: constants.platform.ios ? 150 : '100%',
78 | width: 300,
79 | color: theme.text,
80 | overflow: 'hidden',
81 | justifyContent: 'center',
82 | },
83 | itemStyle: {
84 | color: theme.text,
85 | },
86 | label: {
87 | color: theme.text,
88 | alignSelf: 'flex-start',
89 | marginBottom: 5,
90 | },
91 | }));
92 |
--------------------------------------------------------------------------------
/src/components/SettingModal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View, Modal, Switch, Text, ScrollView } from 'react-native';
3 | import { ConfigType, LimitType } from 'src/constants';
4 | import { darkTheme, lightTheme, StyleSheet } from 'src/styles';
5 | import { Attribution } from './Attribution';
6 | import { ConfigureLimit } from './ConfigureLimit';
7 | import { Button, Divider } from './ui';
8 | import ReactNativeIcon from 'src/assets/ReactNative';
9 |
10 | const DarkModeSetting = () => {
11 | const isLight = StyleSheet.theme.name === 'light';
12 |
13 | const toggleSwitch = () => {
14 | StyleSheet.build(isLight ? darkTheme : lightTheme);
15 | };
16 |
17 | return (
18 |
19 | Light Mode
20 |
26 |
27 | );
28 | };
29 |
30 | export const SettingModal = ({
31 | toggleModal,
32 | show,
33 | limit,
34 | setConfig,
35 | handleReset,
36 | handleSave,
37 | }: {
38 | show: boolean;
39 | limit: LimitType;
40 | toggleModal: () => void;
41 | handleReset: () => void;
42 | handleSave: () => void;
43 | setConfig: (obj: Partial) => void;
44 | }) => {
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | const s = StyleSheet.create((theme, constants) => ({
68 | container: {
69 | flexGrow: 1,
70 | backgroundColor: theme.background,
71 | paddingHorizontal: [10, 30, constants.width / 3],
72 | },
73 | darkModeContainer: {
74 | flexDirection: 'row',
75 | justifyContent: 'space-between',
76 | marginVertical: 12,
77 | },
78 | label: {
79 | color: theme.text,
80 | },
81 | input: {
82 | color: theme.text,
83 | },
84 | image: {
85 | height: 100,
86 | width: 100,
87 | resizeMode: 'contain',
88 | marginVertical: 20,
89 | alignSelf: 'center',
90 | },
91 | }));
92 |
--------------------------------------------------------------------------------
/src/components/Slider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import RNSlider from '@react-native-community/slider';
3 | import { View, Text } from 'react-native';
4 | import { StyleSheet } from 'src/styles';
5 | import { InitStateType } from 'src/types';
6 |
7 | export const Slider = ({
8 | value,
9 | stateKey,
10 | handleChange,
11 | minimumValue = 0,
12 | maximumValue = 100,
13 | step = 1,
14 | }: {
15 | value: number | undefined;
16 | stateKey: keyof InitStateType;
17 | minimumValue?: number;
18 | maximumValue?: number;
19 | step?: number;
20 | handleChange: (a: Partial) => void;
21 | }) => {
22 | const [val, setVal] = React.useState(value);
23 |
24 | const handleFinalValue = () => {
25 | handleChange({ [stateKey]: val });
26 | };
27 |
28 | const handleValueChange = (e: number) => {
29 | const newVal = Math.floor(e * 100) / 100;
30 | setVal(newVal);
31 | };
32 |
33 | return (
34 |
35 |
36 | {stateKey}:
37 | {val}
38 |
39 |
51 |
52 | );
53 | };
54 |
55 | const s = StyleSheet.create((theme) => ({
56 | container: {
57 | marginVertical: 5,
58 | width: 300,
59 | },
60 | slider: {
61 | width: '100%',
62 | height: 30,
63 | },
64 | textContainer: {
65 | flexDirection: 'row',
66 | },
67 | label: {
68 | color: theme.text,
69 | },
70 | value: {
71 | marginLeft: 6,
72 | fontWeight: 'bold',
73 | color: theme.text,
74 | },
75 | }));
76 |
--------------------------------------------------------------------------------
/src/components/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { View, Text, TouchableOpacity } from 'react-native';
3 | import { StyleSheet } from 'src/styles';
4 | import { AnimationTypes } from 'src/constants';
5 | import Timing from 'src/assets/Timing';
6 | import Spring from 'src/assets/Spring';
7 |
8 | const Chip = ({
9 | title,
10 | onPress,
11 | active,
12 | Icon,
13 | }: {
14 | title: string;
15 | onPress: (...args: any) => any;
16 | active: boolean;
17 | Icon: typeof React.Component;
18 | }) => {
19 | const { theme } = StyleSheet;
20 |
21 | return (
22 |
34 |
35 |
36 |
37 |
38 |
41 | {title}
42 |
43 |
44 | );
45 | };
46 |
47 | export const Tabs = ({
48 | handleAnimationType,
49 | active,
50 | }: {
51 | handleAnimationType: (a: AnimationTypes) => void;
52 | active: AnimationTypes;
53 | }) => {
54 | return (
55 |
56 | handleAnimationType('spring')}
58 | title="Spring"
59 | active={active === 'spring'}
60 | // @ts-expect-error
61 | Icon={Spring}
62 | />
63 | handleAnimationType('timing')}
65 | title="Timing"
66 | active={active === 'timing'}
67 | // @ts-expect-error
68 | Icon={Timing}
69 | />
70 |
71 | );
72 | };
73 |
74 | const s = StyleSheet.create((theme) => ({
75 | head: {
76 | flexDirection: 'row',
77 | width: '100%',
78 | alignSelf: 'flex-start',
79 | marginHorizontal: 10,
80 | marginBottom: 10,
81 | justifyContent: ['flex-start', 'center'],
82 | },
83 | headBox: {
84 | flex: 1,
85 | textAlign: 'center',
86 | paddingVertical: 10,
87 | marginHorizontal: 3,
88 | },
89 | headTitle: {
90 | color: theme.text,
91 | textAlign: 'center',
92 | },
93 | chipContainer: {
94 | borderColor: theme.primary,
95 | marginRight: 15,
96 | borderRadius: 100,
97 | flexDirection: 'row',
98 | height: 30,
99 | alignItems: 'center',
100 | overflow: 'hidden',
101 | },
102 | chipTitle: {
103 | paddingRight: 15,
104 | paddingLeft: 8,
105 | },
106 | circle: {
107 | height: 30,
108 | width: 30,
109 | backgroundColor: theme.background + '40',
110 | alignSelf: 'flex-start',
111 | borderRadius: 100,
112 | justifyContent: 'center',
113 | alignItems: 'center',
114 | },
115 | icon: {
116 | height: 20,
117 | width: 20,
118 | },
119 | }));
120 |
--------------------------------------------------------------------------------
/src/components/WithSpring.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Slider } from './Slider';
3 | import { ConfigType, SpringConfigType, SpringLimitType } from 'src/constants';
4 | import { InputRenderer } from './InputRenderer';
5 | import * as Clipboard from 'expo-clipboard';
6 |
7 | export const WithSprings = ({
8 | config,
9 | step,
10 | limit,
11 | handleChange,
12 | onPlay,
13 | handleSetting,
14 | }: {
15 | step: SpringConfigType;
16 | config: SpringConfigType;
17 | limit: SpringLimitType;
18 | handleChange: (a: Partial) => void;
19 | onPlay: () => void;
20 | handleSetting: () => void;
21 | }) => {
22 | const handleCopy = () => {
23 | Clipboard.setString(JSON.stringify(config));
24 | };
25 |
26 | return (
27 |
32 |
39 |
46 |
53 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/components/WithTiming.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as Clipboard from 'expo-clipboard';
3 | import { EasingConfig } from 'src/constants/TimingConfig';
4 | import { camelCaseToNormal, isFunction } from 'src/utils';
5 | import { Slider } from './Slider';
6 | import { Select } from './Select';
7 | import { InputRenderer } from './InputRenderer';
8 | import { ConfigType, TimingConfigType } from 'src/constants';
9 |
10 | const withTimingData = Object.keys(EasingConfig).map((d) => {
11 | return { label: d, value: d };
12 | });
13 |
14 | export const WithTiming = ({
15 | handleChange,
16 | config,
17 | onPlay,
18 | handleSetting,
19 | }: {
20 | config: TimingConfigType;
21 | handleChange: (a: Partial) => void;
22 | onPlay: () => void;
23 | handleSetting: () => void;
24 | }) => {
25 | const [ease, setEase] = React.useState('linear');
26 | const [numSlider, setNumSlider] = React.useState({ show: false, value: 2 });
27 |
28 | const showNumSlider = () => setNumSlider({ show: true, value: 2 });
29 | const hideNumSlider = () => setNumSlider({ show: false, value: 2 });
30 |
31 | const calculateEasing = () => {
32 | const currentConfig = EasingConfig[ease];
33 | if (isFunction(currentConfig)) {
34 | const { config } = currentConfig(numSlider.value);
35 | handleChange({ easing: config });
36 | } else {
37 | handleChange({ easing: currentConfig.config });
38 | }
39 | };
40 |
41 | const handleCopy = () => {
42 | const currentConfig = EasingConfig[ease];
43 | if (currentConfig) {
44 | if (isFunction(currentConfig)) {
45 | const { copy } = currentConfig(numSlider.value);
46 | Clipboard.setString(`{ duration: ${config.duration}, easing: ${copy}}`);
47 | } else {
48 | Clipboard.setString(
49 | `{duration: ${config.duration}, easing: ${currentConfig.copy}}`
50 | );
51 | }
52 | }
53 | };
54 |
55 | const onChange = (val) => {
56 | const nextConfig = EasingConfig[val];
57 | isFunction(nextConfig) ? showNumSlider() : hideNumSlider();
58 | setEase(val);
59 | };
60 |
61 | const handleNumChange = (obj) => setNumSlider({ ...numSlider, ...obj });
62 |
63 | React.useEffect(() => {
64 | if (ease && numSlider) calculateEasing();
65 | }, [ease, numSlider]);
66 |
67 | return (
68 |
73 |
80 | {numSlider.show ? (
81 |
88 | ) : null}
89 |
96 |
97 | );
98 | };
99 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './PlayButton';
2 | export * from './Actions';
3 | export * from './Rotate';
4 | export * from './Movement';
5 | export * from './Scale';
6 |
--------------------------------------------------------------------------------
/src/components/ui.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {
3 | Text,
4 | TextStyle,
5 | TouchableOpacity,
6 | View,
7 | ViewStyle,
8 | } from 'react-native';
9 | import { StyleSheet } from 'src/styles';
10 |
11 | type Props = {
12 | style?: ViewStyle;
13 | children?: React.ReactNode | React.ReactNode[];
14 | };
15 |
16 | export const Container = ({ style, ...props }: Props) => {
17 | return ;
18 | };
19 |
20 | export const Section = ({ style, ...props }: Props) => {
21 | return ;
22 | };
23 |
24 | export const Divider = () => {
25 | return ;
26 | };
27 |
28 | export const Button = ({
29 | onPress,
30 | style,
31 | title,
32 | textStyle,
33 | }: {
34 | onPress: () => any;
35 | style?: ViewStyle;
36 | textStyle?: TextStyle;
37 | title: string;
38 | }) => {
39 | return (
40 |
41 | {title}
42 |
43 | );
44 | };
45 |
46 | const s = StyleSheet.create((theme, constants) => ({
47 | container: {
48 | flex: 1,
49 | flexDirection: ['column-reverse', 'row'],
50 | backgroundColor: theme.background,
51 | paddingHorizontal: [undefined, undefined, 100],
52 | width: constants.width,
53 | overflow: 'hidden',
54 | },
55 | section: {
56 | flex: 1,
57 | marginVertical: [undefined, undefined, '5%'],
58 | },
59 | divider: {
60 | height: 1,
61 | width: '100%',
62 | backgroundColor: theme.border,
63 | marginBottom: 5,
64 | opacity: theme.opacity.fade,
65 | },
66 | btn: {
67 | minHeight: 50,
68 | flex: 1,
69 | maxWidth: 200,
70 | minWidth: 100,
71 | borderRadius: theme.borderRadius.m,
72 | justifyContent: 'center',
73 | alignItems: 'center',
74 | backgroundColor: theme.secondary,
75 | marginHorizontal: 10,
76 | alignSelf: 'center',
77 | marginVertical: 5,
78 | },
79 | }));
80 |
--------------------------------------------------------------------------------
/src/constants/TimingConfig.ts:
--------------------------------------------------------------------------------
1 | // many easing function were picked from https://github.com/thisXY/react-native-easing
2 | import { Easing } from 'react-native-reanimated';
3 |
4 | export const EasingConfig = {
5 | linear: { config: Easing.linear, copy: 'Easing.linear' },
6 | ease: { config: Easing.ease, copy: 'Easing.ease' },
7 | quad: { config: Easing.quad, copy: 'Easing.quad' },
8 | cubic: { config: Easing.cubic, copy: 'Easing.cubic' },
9 | sin: { config: Easing.sin, copy: 'Easing.sin' },
10 | circle: { config: Easing.circle, copy: 'Easing.circle' },
11 | exp: { config: Easing.exp, copy: 'Easing.exp' },
12 | bounce: { config: Easing.bounce, copy: 'Easing.bounce' },
13 | in: { config: Easing.in, copy: 'Easing.in' },
14 | // out - not working
15 | // poly - not working
16 | // back - not working
17 | elastic: (x: number) => {
18 | return {
19 | config: Easing.elastic(x),
20 | copy: `Easing.elastic(${x})`,
21 | };
22 | },
23 |
24 | easeIn: {
25 | config: Easing.bezier(0.42, 0, 1, 1),
26 | copy: 'Easing.bezier(0.42, 0, 1, 1)',
27 | },
28 | easeOut: {
29 | config: Easing.bezier(0, 0, 0.58, 1),
30 | copy: 'Easing.bezier(0, 0, 0.58, 1)',
31 | },
32 | easeInOut: {
33 | config: Easing.bezier(0.42, 0, 0.58, 1),
34 | copy: 'Easing.bezier(0.42, 0, 0.58, 1)',
35 | },
36 |
37 | easeInCubic: {
38 | config: Easing.bezier(0.55, 0.055, 0.675, 0.19),
39 | copy: 'Easing.bezier(0.55, 0.055, 0.675, 0.19)',
40 | },
41 | easeOutCubic: {
42 | config: Easing.bezier(0.215, 0.61, 0.355, 1.0),
43 | copy: 'Easing.bezier(0.215, 0.61, 0.355, 1.0)',
44 | },
45 | easeInOutCubic: {
46 | config: Easing.bezier(0.645, 0.045, 0.355, 1.0),
47 | copy: 'Easing.bezier(0.645, 0.045, 0.355, 1.0)',
48 | },
49 |
50 | easeInCircle: {
51 | config: Easing.bezier(0.6, 0.04, 0.98, 0.335),
52 | copy: 'Easing.bezier(0.6, 0.04, 0.98, 0.335)',
53 | },
54 | easeOutCircle: {
55 | config: Easing.bezier(0.075, 0.82, 0.165, 1.0),
56 | copy: 'Easing.bezier(0.075, 0.82, 0.165, 1.0)',
57 | },
58 | easeInOutCirc: {
59 | config: Easing.bezier(0.785, 0.135, 0.15, 0.86),
60 | copy: 'Easing.bezier(0.785, 0.135, 0.15, 0.86)',
61 | },
62 |
63 | easeInExpo: {
64 | config: Easing.bezier(0.95, 0.05, 0.795, 0.035),
65 | copy: 'Easing.bezier(0.95, 0.05, 0.795, 0.035)',
66 | },
67 | easeOutExpo: {
68 | config: Easing.bezier(0.19, 1.0, 0.22, 1.0),
69 | copy: 'Easing.bezier(0.19, 1.0, 0.22, 1.0)',
70 | },
71 | easeInOutExpo: {
72 | config: Easing.bezier(1.0, 0.0, 0.0, 1.0),
73 | copy: 'Easing.bezier(1.0, 0.0, 0.0, 1.0)',
74 | },
75 |
76 | easeInQuad: {
77 | config: Easing.bezier(0.55, 0.085, 0.68, 0.53),
78 | copy: 'Easing.bezier(0.55, 0.085, 0.68, 0.53)',
79 | },
80 | easeOutQuad: {
81 | config: Easing.bezier(0.25, 0.46, 0.45, 0.94),
82 | copy: 'Easing.bezier(0.25, 0.46, 0.45, 0.94)',
83 | },
84 | easeInOutQuad: {
85 | config: Easing.bezier(0.455, 0.03, 0.515, 0.955),
86 | copy: 'Easing.bezier(0.455, 0.03, 0.515, 0.955)',
87 | },
88 |
89 | easeInQuart: {
90 | config: Easing.bezier(0.895, 0.03, 0.685, 0.22),
91 | copy: 'Easing.bezier(0.895, 0.03, 0.685, 0.22)',
92 | },
93 | easeOutQuart: {
94 | config: Easing.bezier(0.165, 0.84, 0.44, 1.0),
95 | copy: 'Easing.bezier(0.165, 0.84, 0.44, 1.0)',
96 | },
97 | easeInOutQuart: {
98 | config: Easing.bezier(0.86, 0.0, 0.07, 1.0),
99 | copy: 'Easing.bezier(0.86, 0.0, 0.07, 1.0)',
100 | },
101 |
102 | easeInSine: {
103 | config: Easing.bezier(0.47, 0.0, 0.745, 0.715),
104 | copy: 'Easing.bezier(0.47, 0.0, 0.745, 0.715)',
105 | },
106 | easeOutSine: {
107 | config: Easing.bezier(0.39, 0.575, 0.565, 1.0),
108 | copy: 'Easing.bezier(0.39, 0.575, 0.565, 1.0)',
109 | },
110 | easeInOutSine: {
111 | config: Easing.bezier(0.445, 0.05, 0.55, 0.95),
112 | copy: 'Easing.bezier(0.445, 0.05, 0.55, 0.95)',
113 | },
114 |
115 | easeInBack: {
116 | config: Easing.bezier(0.6, -0.28, 0.735, 0.045),
117 | copy: 'Easing.bezier(0.6, -0.28, 0.735, 0.045)',
118 | },
119 | easeOutBack: {
120 | config: Easing.bezier(0.175, 0.885, 0.32, 1.275),
121 | copy: 'Easing.bezier(0.175, 0.885, 0.32, 1.275)',
122 | },
123 | easeInOutBack: {
124 | config: Easing.bezier(0.68, -0.55, 0.265, 1.55),
125 | copy: 'Easing.bezier(0.68, -0.55, 0.265, 1.55)',
126 | },
127 |
128 | easeInElastic: (x: number) => {
129 | return {
130 | config: Easing.out(Easing.elastic(x)),
131 | copy: `Easing.out(Easing.elastic(${x}))`,
132 | };
133 | },
134 |
135 | easeOutElastic: (x: number) => {
136 | return {
137 | config: Easing.in(Easing.elastic(x)),
138 | copy: `Easing.in(Easing.elastic(${x}))`,
139 | };
140 | },
141 |
142 | easeInOutElastic: (x: number) => {
143 | return {
144 | config: Easing.inOut(Easing.out(Easing.elastic(x))),
145 | copy: `Easing.inOut(Easing.out(Easing.elastic(${x})))`,
146 | };
147 | },
148 |
149 | easeInBounce: {
150 | config: Easing.out(Easing.bounce),
151 | copy: 'Easing.out(Easing.bounce)',
152 | },
153 | easeOutBounce: {
154 | config: Easing.in(Easing.bounce),
155 | copy: 'Easing.in(Easing.bounce)',
156 | },
157 | easeInOutBounce: {
158 | config: Easing.inOut(Easing.out(Easing.bounce)),
159 | copy: 'Easing.inOut(Easing.out(Easing.bounce))',
160 | },
161 | };
162 |
--------------------------------------------------------------------------------
/src/constants/index.tsx:
--------------------------------------------------------------------------------
1 | import { Easing } from 'react-native-reanimated';
2 | import MoveIcon from 'src/assets/Move';
3 | import RotateIcon from 'src/assets/Rotate';
4 | import ScaleIcon from 'src/assets/Scale';
5 | import ChartIcon from 'src/assets/Chart';
6 | import { Movement, Rotate, Scale } from 'src/components';
7 | import { Chart } from '../components/Chart';
8 |
9 | // ============= spring config =============
10 |
11 | export const SPRING_CONFIG = {
12 | damping: 10,
13 | mass: 1,
14 | stiffness: 100,
15 | velocity: 1,
16 | };
17 |
18 | export const DEFAULT_MAX_LIMIT = {
19 | damping: 14,
20 | mass: 5,
21 | stiffness: 300,
22 | velocity: 100,
23 | } as const;
24 |
25 | export type LimitType = typeof DEFAULT_MAX_LIMIT;
26 |
27 | export const Step = {
28 | damping: 0.5,
29 | mass: 0.1,
30 | stiffness: 1,
31 | velocity: 1,
32 | };
33 |
34 | export type SpringConfigType = typeof DEFAULT_MAX_LIMIT;
35 | export type SpringLimitType = typeof DEFAULT_MAX_LIMIT;
36 |
37 | // ============= timing config ==================
38 |
39 | export const TIMING_CONFIG: TimingConfigType = {
40 | duration: 1000,
41 | easing: Easing.linear,
42 | };
43 |
44 | export type TimingConfigType = {
45 | duration: number;
46 | easing: typeof Easing;
47 | };
48 |
49 | // ================= default state ==============
50 |
51 | export type ConfigType = SpringConfigType | TimingConfigType;
52 |
53 | export type StateType = {
54 | animationType: AnimationTypes;
55 | config: ConfigType;
56 | };
57 |
58 | export const DefaultState: StateType = {
59 | animationType: 'spring',
60 | config: SPRING_CONFIG,
61 | };
62 |
63 | export type AnimationTypes = 'spring' | 'timing';
64 |
65 | // ========== output examples =============
66 |
67 | export const examples = {
68 | rotate: {
69 | title: 'Rotate',
70 | component: Rotate,
71 | icon: RotateIcon,
72 | color: '#eb4d4b',
73 | },
74 | scale: {
75 | title: 'Scale',
76 | component: Scale,
77 | icon: ScaleIcon,
78 | color: '#10ac84',
79 | },
80 | movement: {
81 | title: 'Move',
82 | component: Movement,
83 | icon: MoveIcon,
84 | color: '#f368e0',
85 | },
86 | chart: {
87 | title: 'Chart',
88 | component: Chart,
89 | icon: ChartIcon,
90 | color: '#000',
91 | },
92 | };
93 |
94 | export type ExampleKeyType = keyof typeof examples;
95 |
96 | // ============= keyboard ==========
97 |
98 | export const KEYS = ['Space', 'Enter', 'NumpadEnter'];
99 |
--------------------------------------------------------------------------------
/src/screen/HomeScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useEffect, useReducer, useState } from 'react';
3 | import { Platform } from 'react-native';
4 | import {
5 | cancelAnimation, runOnJS,
6 | useSharedValue,
7 | withSpring,
8 | withTiming
9 | } from 'react-native-reanimated';
10 | import { InputSection } from 'src/components/InputSection';
11 | import { OutputSection } from 'src/components/OutputSection';
12 | import { Container } from 'src/components/ui';
13 | import {
14 | AnimationTypes,
15 | ConfigType,
16 | DefaultState,
17 | KEYS,
18 | SPRING_CONFIG,
19 | StateType,
20 | TIMING_CONFIG,
21 | } from 'src/constants';
22 | import { useTheme } from 'src/styles';
23 | import type { ActionTypes } from 'src/types';
24 | import { Helmet } from 'react-helmet';
25 |
26 | const reducer = (state: StateType, action: ActionTypes): StateType => {
27 | switch (action.type) {
28 | case 'SET_ANIMATION':
29 | return {
30 | ...state,
31 | animationType: action.payload,
32 | config: action.payload === 'spring' ? SPRING_CONFIG : TIMING_CONFIG,
33 | };
34 | case 'SET_CONFIG':
35 | return { ...state, config: { ...state.config, ...action.payload } };
36 | default:
37 | return state;
38 | }
39 | };
40 |
41 | export const HomeScreen = () => {
42 | const x = useSharedValue(0);
43 | const [running, setRunning] = useState(false)
44 | const [state, dispatch] = useReducer<
45 | (state: StateType, actions: ActionTypes) => StateType
46 | >(reducer, DefaultState);
47 | useTheme();
48 |
49 | const handleChange = (payload: Partial) => {
50 | dispatch({ type: 'SET_CONFIG', payload });
51 | };
52 |
53 | const handleAnimationType = (payload: AnimationTypes) => {
54 | dispatch({ type: 'SET_ANIMATION', payload });
55 | };
56 |
57 | const stopAnimation = () => {
58 | cancelAnimation(x);
59 | x.value = x.value >= 0.5 ? 1 : 0;
60 | };
61 |
62 | const onPlay = () => {
63 | cancelAnimation(x);
64 | setRunning(true)
65 | const onFinish = (finished: any) => {
66 | setRunning(false)
67 | }
68 | if (state.animationType === 'timing') {
69 | // @ts-expect-error
70 | x.value = withTiming(x.value === 0 ? 1 : 0, state.config, finished => runOnJS(onFinish)(finished))
71 | }
72 | else {
73 | // @ts-expect-error
74 | x.value = withSpring(x.value === 0 ? 1 : 0, state.config, finished => runOnJS(onFinish)(finished));
75 | }
76 | };
77 |
78 | useEffect(() => {
79 | if (Platform.OS === 'web') {
80 | document.body.onkeyup = (e) => (KEYS.includes(e.code) ? onPlay() : null);
81 | }
82 | }, []);
83 |
84 | return (
85 | <>
86 | {Platform.OS === 'web' ? (
87 |
88 | Reanimated 2 Config Visualizer
89 |
93 |
94 | ) : null}
95 |
96 |
97 |
103 |
104 |
105 | >
106 | );
107 | };
108 |
--------------------------------------------------------------------------------
/src/styles/index.tsx:
--------------------------------------------------------------------------------
1 | import Sugar from 'react-native-sugar-style';
2 | import { Appearance } from 'react-native';
3 |
4 | const commonTheme = {
5 | font: {
6 | s: 10,
7 | m: 18,
8 | l: 24,
9 | },
10 | opacity: {
11 | fade: 0.7,
12 | half: 0.4,
13 | },
14 | borderRadius: {
15 | s: 4,
16 | m: 10,
17 | },
18 | black: '#2b2b2b',
19 | };
20 |
21 | export const lightTheme = {
22 | ...commonTheme,
23 | name: 'light',
24 | background: '#f5f5f5',
25 | surface: '#e9e9e9',
26 | text: '#2b2b2b',
27 | primary: '#feca57',
28 | secondary: '#13c8f9',
29 | border: '#A1A9B1',
30 | slider: '#222f3e',
31 | icon: '#222f3e',
32 | };
33 |
34 | export const darkTheme = {
35 | ...commonTheme,
36 | name: 'dark',
37 | background: '#2b2b2b',
38 | surface: '#111',
39 | secondary: '#61dafb',
40 | text: '#f5f5f5',
41 | primary: '#feca57',
42 | border: '#A1A9B1',
43 | slider: '#888',
44 | icon: '#cccccc',
45 | };
46 |
47 | const isDark = Appearance.getColorScheme() === 'dark';
48 | const theme = isDark ? darkTheme : lightTheme;
49 |
50 | export const { StyleSheet, ThemeProvider, useTheme } = Sugar(theme);
51 | export default StyleSheet;
52 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import Animated from 'react-native-reanimated';
2 | import { DefaultStateType } from 'src/screen/HomeScreen';
3 | import { AnimationTypes, ConfigType } from 'src/constants';
4 |
5 | export type InitStateType = typeof DefaultStateType;
6 |
7 | type SetConfigType = {
8 | type: 'SET_CONFIG';
9 | payload: Partial;
10 | };
11 |
12 | type SetAnimationType = {
13 | type: 'SET_ANIMATION';
14 | payload: AnimationTypes;
15 | };
16 |
17 | export type ActionTypes = SetConfigType | SetAnimationType;
18 |
19 | export type AnimationCompProps = {
20 | x: Animated.SharedValue;
21 | backgroundColor: string;
22 | running: boolean
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/index.tsx:
--------------------------------------------------------------------------------
1 | export const isFunction = (a: any) => typeof a === 'function';
2 |
3 | export const camelCaseToNormal = (str: string): string => {
4 | // code credits: https://stackoverflow.com/questions/7225407/convert-camelcasetext-to-sentence-case-text
5 | const result = str.replace(/([A-Z])/g, ' $1');
6 | const finalResult = result.charAt(0).toUpperCase() + result.slice(1);
7 | return finalResult;
8 | };
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": [
6 | "dom",
7 | "esnext"
8 | ],
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "skipLibCheck": true,
12 | "resolveJsonModule": true,
13 | "strict": true,
14 | "baseUrl": "./"
15 | },
16 | "extends": "expo/tsconfig.base"
17 | }
18 |
--------------------------------------------------------------------------------