= function ({
76 | date,
77 | onPress,
78 | isCurrent = false,
79 | selectedColor,
80 | dots,
81 | titleStyle,
82 | textStyle,
83 | isCurrentMonth
84 | }) {
85 | const dayOfWeek = date.getDay()
86 | const dayOfMonth = date.getDate()
87 |
88 | const _dots = dots.slice(0, 4)
89 |
90 | return (
91 |
92 |
93 |
94 | {LOCALES.fr.dayNamesShort[dayOfWeek]}
95 |
96 |
97 |
106 |
107 | {_dots.map((x, i) => (
108 |
109 | ))}
110 |
111 |
112 | {dayOfMonth}
113 |
114 |
115 |
116 | )
117 | }
118 |
119 | export { DayItem }
120 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 | @rem Execute Gradle
88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
89 |
90 | :end
91 | @rem End local scope for the variables with windows NT shell
92 | if "%ERRORLEVEL%"=="0" goto mainEnd
93 |
94 | :fail
95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
96 | rem the _cmd.exe /c_ return code!
97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
98 | exit /b 1
99 |
100 | :mainEnd
101 | if "%OS%"=="Windows_NT" endlocal
102 |
103 | :omega
104 |
--------------------------------------------------------------------------------
/android/app/src/debug/java/com/rnanimationsplayground/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.rnanimationsplayground;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 |
32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
33 | client.addPlugin(new ReactFlipperPlugin());
34 | client.addPlugin(new DatabasesFlipperPlugin(context));
35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
36 | client.addPlugin(CrashReporterPlugin.getInstance());
37 |
38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
39 | NetworkingModule.setCustomClientBuilder(
40 | new NetworkingModule.CustomClientBuilder() {
41 | @Override
42 | public void apply(OkHttpClient.Builder builder) {
43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
44 | }
45 | });
46 | client.addPlugin(networkFlipperPlugin);
47 | client.start();
48 |
49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
50 | // Hence we run if after all native modules have been initialized
51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
52 | if (reactContext == null) {
53 | reactInstanceManager.addReactInstanceEventListener(
54 | new ReactInstanceManager.ReactInstanceEventListener() {
55 | @Override
56 | public void onReactContextInitialized(ReactContext reactContext) {
57 | reactInstanceManager.removeReactInstanceEventListener(this);
58 | reactContext.runOnNativeModulesQueueThread(
59 | new Runnable() {
60 | @Override
61 | public void run() {
62 | client.addPlugin(new FrescoFlipperPlugin());
63 | }
64 | });
65 | }
66 | });
67 | } else {
68 | client.addPlugin(new FrescoFlipperPlugin());
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/screens/animations/ViewPagerHeader/ViewPagerHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/theme'
2 | import { getIndex } from '@src/utils'
3 | import React from 'react'
4 | import { StyleProp, StyleSheet, Text, TextStyle, useWindowDimensions, View } from 'react-native'
5 | import { RectButton } from 'react-native-gesture-handler'
6 | import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
7 |
8 |
9 | const styles = StyleSheet.create({
10 | btnsContainer: {
11 | alignItems: 'flex-end',
12 | flex: 1,
13 | justifyContent: 'center',
14 | flexDirection: 'row'
15 | },
16 | btn: {
17 | flex: 1,
18 | alignItems: 'center',
19 | justifyContent: 'center',
20 | },
21 | text: {
22 | fontSize: 18,
23 | color: Colors.white
24 | },
25 | selectedBtn: { },
26 | selectedText: {
27 | color: Colors.white
28 | },
29 | indicator: {
30 | position: 'absolute',
31 | bottom: 4,
32 | left: 0,
33 | height: 2,
34 | backgroundColor: Colors.white,
35 | }
36 | })
37 |
38 | export type Item = {
39 | data: T
40 | text: string
41 | }
42 |
43 | export type Props = {
44 | items: Item[]
45 | currentValue: T
46 | onPress: (item: T) => void
47 |
48 | btnHeight?: number
49 | indicatorWidth?: number
50 | textStyle?: StyleProp
51 | }
52 |
53 | const ViewPagerHeader = function ({
54 | items, currentValue, onPress,
55 | indicatorWidth = 80,
56 | btnHeight = 45,
57 | textStyle
58 | }: Props) {
59 |
60 | const { width } = useWindowDimensions()
61 |
62 | const btnWidth = width / items.length
63 |
64 | const itemPosition = getIndex(items, (x) => x.data === currentValue)
65 | const translateX = useSharedValue(
66 | (btnWidth - indicatorWidth) / 2 + btnWidth * itemPosition)
67 |
68 | const animatedStyle = useAnimatedStyle(() => ({
69 | transform: [
70 | { translateX: withTiming(translateX.value, { duration: 200, easing: Easing.inOut(Easing.cubic) }) }
71 | ]
72 | }), [])
73 |
74 | return (
75 |
76 |
77 | {
78 | items.map(({ data, text }, index) =>
79 | {
87 | onPress(data)
88 | translateX.value = (btnWidth - indicatorWidth) / 2 + btnWidth * index
89 | }}>
90 |
96 | {text}
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | )
104 | }
105 |
106 |
107 | export { ViewPagerHeader }
108 |
--------------------------------------------------------------------------------
/src/screens/animations/ZoomableLineChart/ZoomableChart/AnimatedSvg.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/theme'
2 | import React, { FC } from 'react'
3 | import Animated, { useAnimatedProps } from 'react-native-reanimated'
4 | import { Path as RPath } from 'react-native-redash'
5 | import { G, GProps, Line, LineProps, Path, PathProps } from 'react-native-svg'
6 |
7 | const _AnimatedPath = Animated.createAnimatedComponent(Path)
8 | const _AnimatedG = Animated.createAnimatedComponent(G)
9 | const _AnimatedLine = Animated.createAnimatedComponent(Line)
10 |
11 | export const serializeScale = function (
12 | path: RPath,
13 | scaleX?: Animated.SharedValue,
14 | scaleY?: Animated.SharedValue
15 | ) {
16 | 'worklet'
17 | const _scaleX = scaleX ? scaleX.value : 1
18 | const _scaleY = scaleY ? scaleY.value : 1
19 | const _path = `M${path.move.x},${path.move.y} ${path.curves
20 | .map(
21 | (c) =>
22 | `C${(c.c1.x * _scaleX).toFixed(2)},${(c.c1.y * _scaleY).toFixed(
23 | 2
24 | )} ${(c.c2.x * _scaleX).toFixed(2)},${(
25 | c.c2.y * _scaleY
26 | ).toFixed(2)} ${(c.to.x * _scaleX).toFixed(2)},${(
27 | c.to.y * _scaleY
28 | ).toFixed(2)}`
29 | )
30 | .join(' ')}${path.close ? 'Z' : ''}`
31 | return _path
32 | }
33 | type AnimatedPathProps = {
34 | path: RPath
35 | color?: string
36 | strokeWidth?: number
37 | scale?: Animated.SharedValue
38 | }
39 |
40 | const AnimatedPath: FC = function ({
41 | path,
42 | color = Colors.primary,
43 | strokeWidth = 2,
44 | scale
45 | }) {
46 | const animatedProps = useAnimatedProps(() => {
47 | return {
48 | d: serializeScale(path, scale),
49 | markerStart: 'url(#dot)',
50 | markerMid: 'url(#dot)',
51 | markerEnd: 'url(#dot)'
52 | }
53 | }, [path, scale])
54 | return (
55 | <_AnimatedPath
56 | animatedProps={animatedProps}
57 | fill="transparent"
58 | stroke={color}
59 | strokeWidth={strokeWidth}
60 | />
61 | )
62 | }
63 |
64 | type AnimatedGProps = {
65 | translateX: Animated.SharedValue
66 | }
67 |
68 | const AnimatedG: FC = function ({ translateX, children }) {
69 | //@ts-expect-error
70 | const animatedProps = useAnimatedProps(() => {
71 | return {
72 | transform: [{ translateX: translateX.value }]
73 | }
74 | }, [translateX])
75 | return <_AnimatedG animatedProps={animatedProps}>{children}
76 | }
77 |
78 | type AnimatedLineProps = {
79 | x: Animated.SharedValue
80 | height: number
81 | stroke?: string
82 | strokeWidth?: number
83 | }
84 |
85 | const AnimatedLine: FC = function ({
86 | x,
87 | height,
88 | stroke = Colors.secondary,
89 | strokeWidth = 2
90 | }) {
91 | const animatedProps = useAnimatedProps(() => {
92 | return {
93 | x1: x.value,
94 | x2: x.value
95 | }
96 | }, [x])
97 | return (
98 | <_AnimatedLine
99 | animatedProps={animatedProps}
100 | stroke={stroke}
101 | strokeWidth={strokeWidth}
102 | y1={0}
103 | y2={height}
104 | />
105 | )
106 | }
107 |
108 | export { AnimatedPath, AnimatedG, AnimatedLine }
109 |
--------------------------------------------------------------------------------
/src/screens/animations/Gallery/GallerySlider/index.tsx:
--------------------------------------------------------------------------------
1 | import { getArrayDiff } from '@src/utils'
2 | import React, { useCallback, useEffect, useRef, useState } from 'react'
3 | import { useWindowDimensions, View } from 'react-native'
4 | import Animated, {
5 | runOnJS,
6 | useAnimatedReaction,
7 | useSharedValue
8 | } from 'react-native-reanimated'
9 | import { GalleryItem, Offset } from './GalleryItem'
10 |
11 | export type Props = {
12 | data: T[]
13 | height: number
14 | renderItem: (item: T) => React.ReactElement
15 | onIndexChange?: (index: number) => void
16 | currentPage?: Animated.SharedValue
17 | }
18 |
19 | export type GalleryItemType = { id: number }
20 |
21 | const GallerySlider = function ({
22 | data,
23 | height,
24 | currentPage,
25 | onIndexChange,
26 | renderItem
27 | }: Props) {
28 | const { width } = useWindowDimensions()
29 |
30 | // const isMoving = useSharedValue(false)
31 | // eslint-disable-next-line react-hooks/rules-of-hooks
32 | const currentIndex = currentPage ?? useSharedValue(0)
33 | const ids = useRef([])
34 | const [offsets, setOffsets] = useState<(Offset & { id: number })[]>([])
35 |
36 | useAnimatedReaction(
37 | () => currentIndex.value,
38 | (newIndex: number) => {
39 | if (onIndexChange) runOnJS(onIndexChange)(newIndex)
40 | }
41 | )
42 |
43 | const onInit = useCallback((id: number, offset: Offset) => {
44 | if (!ids.current.includes(id)) ids.current.push(id)
45 |
46 | setOffsets((old) => {
47 | const _ids = old.map((x) => x.id)
48 | if (_ids.includes(id)) return old
49 | else return [...old, { id, ...offset }]
50 | })
51 | }, [])
52 |
53 | useEffect(() => {
54 | const changedIds = getArrayDiff(
55 | ids.current,
56 | data.map((x) => x.id)
57 | )
58 |
59 | offsets.map((x, i) => {
60 | x.x.value = i === currentIndex.value ? 0 : width
61 | x.translateX.value = 0
62 | })
63 |
64 | if (changedIds.length > 0) {
65 | const removedItems = data.length < ids.current.length
66 | if (removedItems)
67 | setOffsets((old) => {
68 | const cp = [
69 | ...old.filter((x) => !changedIds.includes(x.id))
70 | ]
71 | ids.current = cp.map((x) => x.id)
72 | if (cp.length > 0) {
73 | cp[0].x.value = 0
74 | currentIndex.value = 0
75 | }
76 | return cp
77 | })
78 | }
79 | }, [currentIndex, data, offsets, width])
80 |
81 | return (
82 |
83 | {data.map((x, i) => (
84 |
94 | {renderItem(x)}
95 |
96 | ))}
97 |
98 | )
99 | }
100 |
101 | export { GallerySlider }
102 |
--------------------------------------------------------------------------------
/src/screens/animations/TopbarMenuScreen/ShowMore.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/theme'
2 | import React, { useEffect } from 'react'
3 | import { Image, StyleSheet, Text, View } from 'react-native'
4 | import { BaseButton, RectButton } from 'react-native-gesture-handler'
5 | import {
6 | Navigation,
7 | NavigationFunctionComponent as RNNFC
8 | } from 'react-native-navigation'
9 | import Animated, {
10 | Easing,
11 | useAnimatedStyle,
12 | useSharedValue,
13 | withTiming
14 | } from 'react-native-reanimated'
15 |
16 | export const BTN_HEIGHT = 50
17 | export const ICON_SIZE = 22
18 |
19 | const styles = StyleSheet.create({
20 | container: {
21 | flex: 1
22 | },
23 | menu: {
24 | backgroundColor: Colors.white,
25 | borderRadius: 10,
26 | position: 'absolute',
27 | top: 5,
28 | right: 15,
29 | elevation: 2,
30 | overflow: 'hidden'
31 | // paddingHorizontal: 20
32 | },
33 | btn: {
34 | flexDirection: 'row',
35 | height: BTN_HEIGHT,
36 | alignItems: 'center',
37 | justifyContent: 'flex-start',
38 | paddingRight: 10
39 | },
40 | iconContainer: {
41 | width: 60,
42 | justifyContent: 'center',
43 | alignItems: 'center'
44 | },
45 | icon: {
46 | width: ICON_SIZE,
47 | height: ICON_SIZE,
48 | tintColor: Colors.primary
49 | },
50 | text: {
51 | fontSize: 16,
52 | color: Colors.primary
53 | }
54 | })
55 |
56 | type Btn = {
57 | onPress: () => void
58 | icon: number
59 | text: string
60 | testID?: string
61 | }
62 |
63 | export type Props = {
64 | btns: Btn[]
65 | width?: number
66 | }
67 |
68 | const ShowMore: RNNFC = function ({ componentId, btns, width = 100 }) {
69 | const height = useSharedValue(0)
70 | const opacity = useSharedValue(1)
71 |
72 | useEffect(() => {
73 | height.value = btns.length * BTN_HEIGHT
74 | }, [btns, height])
75 |
76 | const animatedStyle = useAnimatedStyle(() => ({
77 | height: withTiming(height.value, {
78 | duration: 200,
79 | easing: Easing.inOut(Easing.cubic)
80 | }),
81 | opacity: opacity.value
82 | }))
83 |
84 | async function _onDismiss() {
85 | await Navigation.dismissOverlay(componentId)
86 | }
87 |
88 | return (
89 |
93 |
95 | {btns.map(({ icon, text, onPress, testID }, i) => (
96 | {
99 | onPress()
100 | _onDismiss()
101 | }}
102 | key={i}
103 | style={styles.btn}>
104 |
105 |
106 |
107 | {text}
108 |
109 | ))}
110 |
111 |
112 | )
113 | }
114 |
115 | ShowMore.options = {
116 | layout: {
117 | componentBackgroundColor: 'transparent'
118 | },
119 | overlay: {
120 | interceptTouchOutside: false
121 | }
122 | }
123 |
124 | export { ShowMore }
125 |
--------------------------------------------------------------------------------
/ios/RnAnimationsPlayground.xcodeproj/xcshareddata/xcschemes/RnAnimationsPlayground.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/screens/animations/BarchartScreen/BarchartCustom/index.tsx:
--------------------------------------------------------------------------------
1 | import { normalizeValue } from '@src/utils'
2 | import React, { FC, useEffect } from 'react'
3 | import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
4 | import Animated, { interpolate, useSharedValue } from 'react-native-reanimated'
5 |
6 | const styles = StyleSheet.create({
7 | container: {
8 | flex: 1,
9 | justifyContent: 'space-evenly'
10 | },
11 | containerVertical: {
12 | alignItems: 'flex-end',
13 | flexDirection: 'row'
14 | },
15 | containerHorizontal: {
16 | alignItems: 'flex-start'
17 | }
18 | })
19 |
20 | export type DataItem = {
21 | value: number
22 | label: string
23 | }
24 |
25 | export type RenderBarProps = {
26 | position: number
27 | barSize: number
28 | label: string
29 | mounted: Animated.SharedValue
30 | }
31 |
32 | export type Props = {
33 | data: DataItem[]
34 | style?: StyleProp
35 | minValue?: number
36 | maxValue?: number
37 | maxHeight: number
38 | minHeight?: number
39 | normalize?: boolean
40 | defaultNormValue?: number
41 | minNormValue?: number
42 | horizontal?: boolean
43 | renderBar: (props: RenderBarProps) => React.ReactElement
44 | }
45 |
46 | const Barchart: FC = function ({
47 | data,
48 | style,
49 | minHeight = 0,
50 | minValue = 0,
51 | maxValue = 1,
52 | maxHeight,
53 | normalize = true,
54 | // should be between 0 and 1
55 | defaultNormValue = 0.5,
56 | minNormValue = undefined,
57 | horizontal = false,
58 | renderBar
59 | }) {
60 | const values = data.map((x) => x.value)
61 | const [normMinValue, normMaxValue] = [
62 | minNormValue ?? Math.min(...values),
63 | Math.max(...values)
64 | ]
65 | // Needed to smooth at first and see the animation on mount
66 | const mounted = useSharedValue(false)
67 | useEffect(() => {
68 | mounted.value = true
69 | }, [mounted])
70 |
71 | return (
72 |
80 | {data.map(({ value, label }, i) => {
81 | const _value = normalize
82 | ? normalizeValue(
83 | value,
84 | normMinValue,
85 | normMaxValue,
86 | defaultNormValue
87 | )
88 | : value
89 | const _minValue = normalize ? 0 : minValue
90 | const _maxValue = normalize ? 1 : maxValue
91 |
92 | const _barSize = interpolate(
93 | _value,
94 | [_minValue, _maxValue],
95 | [minHeight, maxHeight],
96 | Animated.Extrapolate.CLAMP
97 | )
98 |
99 | const _props: RenderBarProps = {
100 | position: i,
101 | barSize: _barSize,
102 | mounted,
103 | label
104 | }
105 | return (
106 |
113 | {renderBar(_props)}
114 |
115 | )
116 | })}
117 |
118 | )
119 | }
120 |
121 | export { Barchart }
122 |
--------------------------------------------------------------------------------
/ios/RnAnimationsPlayground.xcodeproj/xcshareddata/xcschemes/RnAnimationsPlayground-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/screens/animations/CalendarScreen/WeeklyCalendar/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffectSkipFirst } from '@src/hooks'
2 | import React, { FC, useEffect, useState } from 'react'
3 | import {
4 | StyleProp,
5 | StyleSheet,
6 | TextStyle,
7 | useWindowDimensions,
8 | View
9 | } from 'react-native'
10 | import Animated, {
11 | useAnimatedStyle,
12 | useSharedValue
13 | } from 'react-native-reanimated'
14 | import {
15 | Direction, Paginate,
16 | RenderProps
17 | } from '../../PaginateScreen/Paginate'
18 | import { DayItem, Dot } from './DayItem'
19 | import { getDate, getNextWeek, getPreviousWeek, getWeekDays } from './utils'
20 |
21 | export type { Dot }
22 | export { WeeklyCalendar }
23 |
24 | const styles = StyleSheet.create({
25 | header: {
26 | flexWrap: 'wrap',
27 | flexDirection: 'row',
28 | justifyContent: 'space-evenly'
29 | }
30 | })
31 |
32 | export type Props = {
33 | currentDate: Date
34 | onDateChanged: (date: Date) => void
35 | backgroundColor?: string
36 | selectedColor?: string
37 | markedDates?: Map
38 | titleStyle?: StyleProp
39 | textStyle?: StyleProp
40 | onPressDate?: (date: Date) => void
41 | }
42 |
43 | const WeeklyCalendar: FC = function ({
44 | currentDate,
45 | onDateChanged,
46 | backgroundColor,
47 | selectedColor,
48 | markedDates,
49 | titleStyle,
50 | textStyle,
51 | onPressDate
52 | }) {
53 |
54 | const { width } = useWindowDimensions()
55 |
56 | const [_currentDate, setCurrentDate] = useState(currentDate)
57 | const currentMonth = _currentDate.getMonth()
58 | const currentWeek = getWeekDays(_currentDate)
59 | const prevWeek = getPreviousWeek(_currentDate)
60 | const nextWeek = getNextWeek(_currentDate)
61 |
62 | const weeks = [prevWeek, currentWeek, nextWeek]
63 |
64 | useEffect(() => {
65 | onDateChanged(_currentDate)
66 | }, [_currentDate, onDateChanged])
67 |
68 | useEffectSkipFirst(() => {
69 | setCurrentDate(currentDate)
70 | }, [currentDate])
71 |
72 |
73 | function onDoneMoving(direction: Direction) {
74 | const newDate = direction === 'right' ? prevWeek[0] : nextWeek[0]
75 | setCurrentDate(newDate)
76 | }
77 |
78 | const weeklyHeight = useSharedValue(80)
79 |
80 | const style = useAnimatedStyle(() => ({
81 | height: weeklyHeight.value
82 | }))
83 |
84 | function renderItem({ item }: RenderProps) {
85 | return (
86 | {
89 | weeklyHeight.value = nativeEvent.layout.height
90 | }}>
91 | {item.map((x, j) => {
92 | const _date = getDate(x)
93 | return (
94 | {
101 | setCurrentDate(x)
102 | if (onPressDate) onPressDate(x)
103 | }}
104 | isCurrent={getDate(x) === getDate(_currentDate)}
105 | />
106 | )
107 | })}
108 |
109 | )
110 | }
111 |
112 | return (
113 |
114 |
120 |
121 | )
122 | }
123 |
--------------------------------------------------------------------------------
/src/screens/playground/DragToSort/SortableItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react'
2 | import { Gesture, GestureDetector } from 'react-native-gesture-handler'
3 | import Animated, {
4 | useAnimatedStyle,
5 | useDerivedValue,
6 | useSharedValue,
7 | withSpring
8 | } from 'react-native-reanimated'
9 |
10 | export type Offset = {
11 | y: Animated.SharedValue
12 | }
13 |
14 | export type Props = {
15 | index: number
16 | offsets: Offset[]
17 | item: { width: number; height: number }
18 | }
19 |
20 | const SortableItem: FC = function ({
21 | children,
22 | index,
23 | offsets,
24 | item: { width, height }
25 | }) {
26 | const offset = offsets[index]
27 |
28 | const gestureActive = useSharedValue(false)
29 | const gestureFinishing = useSharedValue(false)
30 |
31 | const x = useSharedValue(0)
32 | const y = useSharedValue(offset.y.value)
33 | const safeOffsetY = useSharedValue(0)
34 | const translateX = useDerivedValue(() => x.value)
35 | const translateY = useDerivedValue(() => {
36 | if (gestureActive.value) {
37 | return y.value
38 | } else {
39 | return withSpring(offset.y.value)
40 | }
41 | })
42 |
43 | const gesture = Gesture.Pan()
44 | .onBegin(() => {
45 | gestureActive.value = true
46 | safeOffsetY.value = offset.y.value
47 | })
48 | .onUpdate((event) => {
49 | x.value = event.translationX
50 | y.value = safeOffsetY.value + event.translationY
51 | // Moving others
52 | const offsetY = Math.round(y.value / height) * height
53 | offsets.forEach((o, i) => {
54 | if (o.y.value === offsetY && i !== index) {
55 | const tmp = o.y.value
56 | o.y.value = offset.y.value
57 | offset.y.value = tmp
58 | }
59 | })
60 | //
61 | })
62 | .onEnd((event) => {
63 | gestureActive.value = false
64 | gestureFinishing.value = true
65 |
66 | // Move back into position
67 | x.value = withSpring(0, {
68 | stiffness: 100,
69 | mass: 1,
70 | damping: 10,
71 | overshootClamping: false,
72 | restSpeedThreshold: 0.001,
73 | restDisplacementThreshold: 0.001,
74 | velocity: event.velocityX
75 | })
76 | y.value = withSpring(
77 | offset.y.value,
78 | {
79 | stiffness: 100,
80 | mass: 1,
81 | damping: 10,
82 | overshootClamping: false,
83 | restSpeedThreshold: 0.001,
84 | restDisplacementThreshold: 0.001,
85 | velocity: event.velocityY
86 | },
87 | () => (gestureFinishing.value = false)
88 | )
89 | })
90 |
91 | const style = useAnimatedStyle(() => ({
92 | zIndex: gestureActive.value || gestureFinishing.value ? 100 : 0,
93 | transform: [
94 | { translateX: translateX.value },
95 | { translateY: translateY.value }
96 | // { scale: withSpring(gestureActive.value ? 1.1 : 1) },
97 | ]
98 | }))
99 |
100 | return (
101 |
102 |
114 | {children}
115 |
116 |
117 | )
118 | }
119 |
120 | export { SortableItem }
121 |
--------------------------------------------------------------------------------
/src/screens/animations/BarchartScreen/Barchart/BarItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react'
2 | import { StyleSheet, Text, View } from 'react-native'
3 | import Animated, {
4 | interpolate,
5 | useAnimatedStyle,
6 | useDerivedValue,
7 | withTiming
8 | } from 'react-native-reanimated'
9 |
10 | const TEXT_HEIGHT = 30
11 | const TEXT_WIDTH = 40
12 |
13 | const styles = StyleSheet.create({
14 | bar: {
15 | borderRadius: 20
16 | },
17 | labelText: {
18 | fontSize: 12
19 | // height: 20
20 | },
21 | textContainer: {
22 | justifyContent: 'center',
23 | alignItems: 'center'
24 | }
25 | })
26 |
27 | type BarProps = {
28 | value: number
29 | label: string
30 | barColor: string
31 | labelColor: string
32 | maxValue: number
33 | minValue: number
34 | minHeight: number
35 | maxHeight: number
36 | animate?: boolean
37 | mounted: Animated.SharedValue
38 | horizontal: boolean
39 | }
40 |
41 | export const VerticalBarItem: FC = function ({
42 | value,
43 | label,
44 | barColor,
45 | labelColor,
46 | minValue,
47 | maxValue,
48 | minHeight,
49 | maxHeight,
50 | animate,
51 | mounted
52 | }) {
53 | const _barSize = interpolate(
54 | value,
55 | [minValue, maxValue],
56 | [minHeight, maxHeight - TEXT_HEIGHT],
57 | Animated.Extrapolate.CLAMP
58 | )
59 |
60 | const barSize = useDerivedValue(() => {
61 | return animate
62 | ? mounted.value
63 | ? withTiming(_barSize, { duration: 300 })
64 | : 0
65 | : _barSize
66 | })
67 |
68 | const animatedStyle = useAnimatedStyle(() => ({
69 | height: barSize.value
70 | }))
71 |
72 | return (
73 |
74 |
81 |
82 |
85 | {label}
86 |
87 |
88 |
89 | )
90 | }
91 |
92 | export const HorizontalBarItem: FC = function ({
93 | value,
94 | label,
95 | barColor,
96 | labelColor,
97 | minValue,
98 | maxValue,
99 | minHeight,
100 | maxHeight,
101 | animate,
102 | mounted
103 | }) {
104 | // const _height = useSharedValue(0)
105 |
106 | const _barSize = interpolate(
107 | value,
108 | [minValue, maxValue],
109 | [minHeight, maxHeight - TEXT_WIDTH],
110 | Animated.Extrapolate.CLAMP
111 | )
112 |
113 | const barSize = useDerivedValue(() => {
114 | return animate
115 | ? mounted.value
116 | ? withTiming(_barSize, { duration: 300 })
117 | : 0
118 | : _barSize
119 | })
120 |
121 | const animatedStyle = useAnimatedStyle(() => ({
122 | width: barSize.value
123 | // width: barSize.value,
124 | // height: 10
125 | }))
126 |
127 | return (
128 |
129 |
130 |
133 | {label}
134 |
135 |
136 |
143 |
144 | )
145 | }
146 |
--------------------------------------------------------------------------------
/src/screens/animations/ZoomableLineChart/ZoomableChart/index.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/theme'
2 | import * as shape from 'd3-shape'
3 | import React, { useEffect, useState } from 'react'
4 | import { StyleProp, ViewStyle } from 'react-native'
5 | import {
6 | GestureDetector,
7 | PanGesture,
8 | PinchGesture
9 | } from 'react-native-gesture-handler'
10 | import Animated from 'react-native-reanimated'
11 | import { parse, Path as RPath } from 'react-native-redash'
12 | import Svg, { Circle, Defs, Marker } from 'react-native-svg'
13 | import { buildGraph } from '../../LinechartScreen/Linechart/utils'
14 | import { AnimatedG, AnimatedPath } from './AnimatedSvg'
15 | import { useZoomableChart } from './utils'
16 | export { useZoomableChart }
17 | export { ZoomableChart }
18 |
19 | type CurveType = shape.CurveFactory | shape.CurveFactoryLineOnly
20 |
21 | export type UseZoomableProps = {
22 | pinchGesture: PinchGesture
23 | panGesture: PanGesture
24 | translateX: Readonly>
25 | scale: Readonly>
26 | }
27 |
28 | export type Props = {
29 | width: number
30 | height: number
31 | data: [number, number][]
32 | curve?: CurveType
33 | showDots?: boolean
34 | style?: StyleProp
35 | } & UseZoomableProps
36 |
37 | const ZoomableChart = function ({
38 | width,
39 | height,
40 | data,
41 | curve = shape.curveMonotoneX,
42 | style,
43 | showDots = false,
44 | panGesture,
45 | pinchGesture,
46 | translateX,
47 | scale
48 | }: Props) {
49 | const [path, setPath] = useState(() =>
50 | parse(buildGraph(data, width, height, { curve }).path)
51 | )
52 |
53 | panGesture.minDistance(3).maxPointers(1)
54 |
55 | useEffect(() => {
56 | const graph = buildGraph(data, width, height, { curve })
57 | setPath(parse(graph.path))
58 | }, [data, width, height, curve])
59 |
60 | //https://docs.swmansion.com/react-native-gesture-handler/docs/api/gesture-handlers/pan-gh
61 | return (
62 |
68 |
69 |
73 |
83 |
105 |
106 |
107 |
108 |
109 | )
110 | }
111 |
--------------------------------------------------------------------------------
/src/screens/animations/DraggableList/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Button } from '@src/components'
3 | import { CARD_HEIGHT, CARD_WIDTH } from '@src/components/Card'
4 | import { useTopBarBtnPress, useTopBarHeight, useUniqueID } from '@src/hooks'
5 | import { Colors } from '@src/theme'
6 | import React, { useRef, useState } from 'react'
7 | import { useWindowDimensions, View } from 'react-native'
8 | import { NavigationFunctionComponent as RNNFC } from 'react-native-navigation'
9 | import { CardItem } from './CardItem'
10 | import { DefaultItem, DraggableList, DraggableListRef, RenderProps } from './DraggableList'
11 | import styles from './styles'
12 |
13 | type Card = DefaultItem & {
14 | id: string
15 | color: string
16 | }
17 | const CARDS: Card[] = [
18 | {
19 | id: 'item-0',
20 | color: Colors.primary,
21 | },
22 | {
23 | id: 'item-1',
24 | color: Colors.secondary
25 | }
26 | ]
27 |
28 | export type Props = {}
29 |
30 | const DraggableListScreen: RNNFC = function ({ componentId }) {
31 |
32 | const { height } = useWindowDimensions()
33 |
34 | const [cards, setCards] = useState(CARDS)
35 | const listRef = useRef()
36 |
37 | const [verticalOnly, setVerticalOnly] = useState(false)
38 | const [disabled, setDisabled] = useState(false)
39 |
40 | const { getID } = useUniqueID(CARDS.length)
41 | const topbarHeight = useTopBarHeight()
42 |
43 | useTopBarBtnPress(componentId, (event) => {
44 | if (event.buttonId === 'add') {
45 | setCards((old) => [
46 | ...old,
47 | { color: Colors.primary, id: getID('item-') }
48 | ])
49 | }
50 | })
51 |
52 | function onDelete(itemID: string | number) {
53 | setCards((old) => [...old.filter((x) => x.id !== itemID)])
54 | }
55 |
56 | function _renderItem({ data, item }: RenderProps) {
57 | return (
58 | onDelete(data.id)}
62 | />
63 | )
64 | }
65 |
66 | return (
67 |
68 |
69 | x.id}
74 | config={{
75 | spacingY: 30,
76 | spacingEnd: 0,
77 | defaultItemHeight: CARD_HEIGHT,
78 | defaultItemWidth: CARD_WIDTH,
79 | verticalOnly: verticalOnly,
80 | disabled: disabled,
81 | minHeight: height - topbarHeight
82 | }}
83 | />
84 |
85 |
86 |
87 |
110 |
111 | )
112 | }
113 |
114 | DraggableListScreen.options = {
115 | topBar: {
116 | title: {
117 | text: 'Draggable List'
118 | },
119 | rightButtons: [
120 | {
121 | id: 'add',
122 | color: Colors.primary,
123 | icon: require('@img/icons/plus-20.png')
124 | }
125 | ]
126 | }
127 | }
128 |
129 | export { DraggableListScreen }
130 |
--------------------------------------------------------------------------------
/src/screens/animations/ModalDemoScreen/ModalScreen.tsx:
--------------------------------------------------------------------------------
1 | import { InsetUtils } from '@src/insets'
2 | import { Colors } from '@src/theme'
3 | import React, { useEffect } from 'react'
4 | import {
5 | KeyboardAvoidingView,
6 | Platform,
7 | SafeAreaView,
8 | StyleSheet
9 | } from 'react-native'
10 | import { BaseButton, TextInput } from 'react-native-gesture-handler'
11 | import {
12 | Navigation,
13 | NavigationFunctionComponent as RNNFC,
14 | OptionsModalPresentationStyle
15 | } from 'react-native-navigation'
16 | import Animated, {
17 | Easing,
18 | runOnJS,
19 | useAnimatedStyle,
20 | useSharedValue,
21 | withTiming,
22 | WithTimingConfig
23 | } from 'react-native-reanimated'
24 |
25 | const HEIGHT = 150
26 |
27 | const styles = StyleSheet.create({
28 | container: {
29 | flex: 1
30 | },
31 | box: {
32 | position: 'absolute',
33 | bottom: 0,
34 | left: 20,
35 | right: 20,
36 | height: HEIGHT,
37 | backgroundColor: Colors.white,
38 | borderRadius: 20,
39 | justifyContent: 'center',
40 | alignItems: 'center'
41 | },
42 | textInput: {
43 | fontSize: 20,
44 | color: Colors.primary
45 | }
46 | })
47 |
48 | const BOTTOM_INSET = InsetUtils.getInsets().bottom
49 | const CORRECTED_HEIGHT = HEIGHT + BOTTOM_INSET
50 | const PADDING_BOTTOM = BOTTOM_INSET === 0 ? 20 : 0
51 |
52 | const CONFIG: WithTimingConfig = {
53 | duration: 200,
54 | easing: Easing.inOut(Easing.cubic)
55 | }
56 |
57 | export type Props = {}
58 |
59 | const ModalScreen: RNNFC = function ({ componentId }) {
60 | const translateY = useSharedValue(HEIGHT)
61 |
62 | const onPressBackground = () => {
63 | 'worklet'
64 | translateY.value = CORRECTED_HEIGHT
65 | }
66 |
67 | /**
68 | * Needed because of how runOnJS works.
69 | * See https://docs.swmansion.com/react-native-reanimated/docs/api/miscellaneous/runOnJS
70 | */
71 | const _dimissModal = () => {
72 | Navigation.dismissModal(componentId)
73 | }
74 |
75 | const onDoneAnimating = (finished?: boolean) => {
76 | 'worklet'
77 | if (!finished) return
78 | if (translateY.value === CORRECTED_HEIGHT) {
79 | runOnJS(_dimissModal)()
80 | }
81 | }
82 |
83 | const style = useAnimatedStyle(() => ({
84 | transform: [
85 | {
86 | translateY: withTiming(
87 | translateY.value,
88 | CONFIG,
89 | onDoneAnimating
90 | )
91 | }
92 | ]
93 | }))
94 |
95 | useEffect(() => {
96 | translateY.value = -PADDING_BOTTOM
97 | }, [translateY])
98 |
99 | return (
100 |
106 |
107 |
111 |
112 |
116 |
117 |
118 |
119 |
120 | )
121 | }
122 |
123 | ModalScreen.options = {
124 | layout: {
125 | backgroundColor: 'rgba(0, 0, 0, 0.1)',
126 | componentBackgroundColor: 'rgba(0, 0, 0, 0.1)'
127 | },
128 | animations: {
129 | push: {
130 | waitForRender: false
131 | },
132 | showModal: {
133 | alpha: {
134 | from: 0,
135 | to: 1,
136 | duration: 200
137 | }
138 | },
139 | dismissModal: {
140 | alpha: {
141 | from: 1,
142 | to: 0,
143 | duration: 200
144 | }
145 | }
146 | },
147 | modalPresentationStyle: OptionsModalPresentationStyle.overCurrentContext
148 | }
149 |
150 | export { ModalScreen }
151 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/index'
2 | import React from 'react'
3 | import {
4 | Platform,
5 | SafeAreaView,
6 | ScrollView,
7 | StyleSheet,
8 | Text,
9 | View
10 | } from 'react-native'
11 | import { RectButton } from 'react-native-gesture-handler'
12 | import {
13 | Navigation,
14 | NavigationFunctionComponent as RNFC
15 | } from 'react-native-navigation'
16 |
17 | const styles = StyleSheet.create({
18 | scrollView: {
19 | backgroundColor: Colors.background
20 | },
21 | goToBtn: {
22 | alignSelf: 'stretch',
23 | backgroundColor: Colors.white,
24 | height: 70,
25 | justifyContent: 'center'
26 | },
27 | text: {
28 | paddingLeft: 10
29 | },
30 | headerContainer: {
31 | backgroundColor: Colors.primary
32 | },
33 | headerText: {
34 | color: Colors.white,
35 | marginLeft: 10,
36 | fontSize: 18,
37 | marginVertical: 3
38 | }
39 | })
40 |
41 | const SCREENS = [
42 | {
43 | title: 'Reanimated utils'
44 | },
45 | {
46 | name: 'WorkletScreen',
47 | title: 'Worklets'
48 | },
49 | {
50 | name: 'Worklet2Screen',
51 | title: 'Worklets 2'
52 | },
53 | {
54 | name: 'GestureScreen',
55 | title: 'Gesture'
56 | },
57 | {
58 | name: 'DragToSortScreen',
59 | title: ' Drag to Sort'
60 | },
61 | {
62 | title: 'Animations'
63 | },
64 | {
65 | name: 'DraggableListScreen',
66 | title: 'Draggable List'
67 | },
68 | {
69 | name: 'GalleryScreen',
70 | title: 'Images Gallery'
71 | },
72 | {
73 | name: 'BarChartScreen',
74 | title: 'BarChart'
75 | },
76 | {
77 | name: 'CalendarScreen',
78 | title: 'Calendar'
79 | },
80 | {
81 | name: 'PaginateScreen',
82 | title: 'Paginate'
83 | },
84 | {
85 | name: 'SeletectableListScreen',
86 | title: 'Selectable List'
87 | },
88 | {
89 | name: 'TopbarMenuScreen',
90 | title: 'Topbar Menu'
91 | },
92 | {
93 | name: 'ViewPagerHeaderScreen',
94 | title: 'ViewPager Header'
95 | },
96 | {
97 | name: 'LineGraphScreen',
98 | title: 'Line Graph'
99 | },
100 | {
101 | name: 'ZoomableLineChartScreen',
102 | title: 'Zoomable Linechart'
103 | },
104 | {
105 | name: 'ModalDemoScreen',
106 | title: 'Modal Demo'
107 | }
108 | // {
109 | // name: 'DynamicItemsScreen',
110 | // title: 'Dynamic Items'
111 | // }
112 | ]
113 |
114 | type Props = {}
115 |
116 | const App: RNFC = function ({ componentId }) {
117 | function goToScreen(screen: string) {
118 | Navigation.push(componentId, {
119 | component: {
120 | name: screen
121 | }
122 | })
123 | }
124 |
125 | return (
126 | <>
127 |
128 |
131 | {SCREENS.map((x) => {
132 | if (x.name)
133 | return (
134 | goToScreen(x.name)}>
138 | {x.title}
139 |
140 | )
141 | else
142 | return (
143 |
146 |
147 | {x.title}
148 |
149 |
150 | )
151 | })}
152 |
153 |
154 | >
155 | )
156 | }
157 |
158 | App.options = {
159 | topBar: {
160 | title: {
161 | text: 'RN Animations',
162 | alignment: Platform.select({ ios: 'center', android: undefined })
163 | },
164 | backButton: { visible: false }
165 | }
166 | }
167 |
168 | export default App
169 |
--------------------------------------------------------------------------------
/src/screens/animations/LinechartScreen/Linechart/index.tsx:
--------------------------------------------------------------------------------
1 | import { clamp } from '@src/animUtils'
2 | import { Colors } from '@src/theme'
3 | import React, { FC } from 'react'
4 | import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
5 | import { Gesture, GestureDetector } from 'react-native-gesture-handler'
6 | import Animated, {
7 | useAnimatedProps,
8 | useSharedValue
9 | } from 'react-native-reanimated'
10 | import { parse, Path as RPath, serialize } from 'react-native-redash'
11 | import Svg, { Color, Path } from 'react-native-svg'
12 | import { Cursor } from './Cursor'
13 | import { buildGraph, Config, GraphData } from './utils'
14 |
15 | const AnimatedPath = Animated.createAnimatedComponent(Path)
16 |
17 | type LineProps = {
18 | path: RPath
19 | color?: Color
20 | width?: number
21 | }
22 |
23 | const Line: FC = function ({
24 | path,
25 | color = Colors.primary,
26 | width = 2
27 | }) {
28 | const animatedProps = useAnimatedProps(() => {
29 | return {
30 | d: serialize(path)
31 | }
32 | })
33 | return (
34 |
40 | )
41 | }
42 |
43 | export type DataPoint = {
44 | x: number
45 | y: number
46 | }
47 |
48 | export type LineGraph = Omit & { path: RPath }
49 |
50 | export type LineItem = {
51 | data?: DataPoint[]
52 | graph?: LineGraph
53 | color?: Color
54 | config?: Config
55 | currentValue?: Animated.SharedValue
56 | }
57 |
58 | export type LinechartProps = {
59 | lines: LineItem[]
60 | height: number
61 | width: number
62 | containerStyle?: StyleProp
63 | style?: StyleProp
64 | config?: Config
65 | showCursor?: boolean
66 | }
67 |
68 | const Linechart: FC = function ({
69 | lines,
70 | width,
71 | height,
72 | style,
73 | containerStyle,
74 | config,
75 | showCursor = true,
76 | children
77 | }) {
78 | const _graphs = lines.map((x) => {
79 | if (x.graph) return x.graph
80 | if (x.data) {
81 | const { path, ...rest } = buildGraph(
82 | x.data.map((p) => [p.x, p.y]),
83 | width,
84 | height,
85 | x.config ?? config
86 | )
87 | return { path: parse(path), ...rest }
88 | }
89 | throw new Error('Either `graph` or `data` must be specified')
90 | })
91 |
92 | const xPosition = useSharedValue(0)
93 | const yPosition = useSharedValue(height)
94 |
95 | const gesture = Gesture.Pan()
96 | .onBegin((event) => {
97 | xPosition.value = clamp(event.x, 0, width)
98 | yPosition.value = clamp(event.y, 0, height)
99 | })
100 | .onUpdate((event) => {
101 | xPosition.value = clamp(event.x, 0, width)
102 | yPosition.value = clamp(event.y, 0, height)
103 | })
104 |
105 | return (
106 |
107 |
113 | {showCursor && (
114 |
115 |
116 |
118 | {_graphs.map((x, i) => (
119 |
130 | ))}
131 |
132 |
133 |
134 | )}
135 |
136 | )
137 | }
138 |
139 | export { Linechart }
140 |
--------------------------------------------------------------------------------
/src/screens/animations/BarchartScreen/Barchart/index.tsx:
--------------------------------------------------------------------------------
1 | import { Colors } from '@src/theme'
2 | import { normalizeValue } from '@src/utils'
3 | import React, { FC, useEffect } from 'react'
4 | import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
5 | import { useSharedValue } from 'react-native-reanimated'
6 | import { HorizontalBarItem, VerticalBarItem } from './BarItem'
7 |
8 | const styles = StyleSheet.create({
9 | container: {
10 | flex: 1,
11 | justifyContent: 'space-evenly'
12 | },
13 | containerVertical: {
14 | alignItems: 'flex-end',
15 | flexDirection: 'row'
16 | },
17 | containerHorizontal: {
18 | alignItems: 'flex-start'
19 | }
20 | })
21 |
22 | export type DataItem = {
23 | value: number
24 | label: string
25 | }
26 |
27 | export type Props = {
28 | data: DataItem[]
29 | style?: StyleProp
30 | labelColor?: string
31 | barColor?: string
32 | minValue?: number
33 | maxValue?: number
34 | maxHeight: number
35 | minHeight?: number
36 | normalize?: boolean
37 | defaultNormValue?: number
38 | animate?: boolean
39 | minNormValue?: number
40 | horizontal?: boolean
41 | }
42 |
43 | const Barchart: FC = function ({
44 | data,
45 | style,
46 | labelColor = Colors.primary,
47 | barColor = Colors.secondary,
48 | minHeight = 0,
49 | minValue = 0,
50 | maxValue = 1,
51 | maxHeight,
52 | normalize = true,
53 | // should be between 0 and 1
54 | defaultNormValue = 0.5,
55 | minNormValue = undefined,
56 | animate = true,
57 | horizontal = false
58 | }) {
59 | const values = data.map((x) => x.value)
60 | const [normMinValue, normMaxValue] = [
61 | minNormValue ?? Math.min(...values),
62 | Math.max(...values)
63 | ]
64 | // Needed to smooth at first and see the animation on mount
65 | const mounted = useSharedValue(false)
66 | useEffect(() => {
67 | mounted.value = true
68 | }, [mounted])
69 |
70 | return (
71 |
79 | {data.map(({ value, label }, i) =>
80 | horizontal ? (
81 |
106 | ) : (
107 |
132 | )
133 | )}
134 |
135 | )
136 | }
137 |
138 | export { Barchart }
139 |
--------------------------------------------------------------------------------
/src/screens/animations/Gallery/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@src/components'
2 | import { Colors } from '@src/theme'
3 | import React, { useState } from 'react'
4 | import { Image, Text, useWindowDimensions, View } from 'react-native'
5 | import { BaseButton } from 'react-native-gesture-handler'
6 | import { NavigationFunctionComponent as RNNFC } from 'react-native-navigation'
7 | import { GallerySlider } from './GallerySlider'
8 | import { styles } from './styles'
9 |
10 | export type Props = {}
11 |
12 | export type ImageType = { id: number; uri: number; color: string }
13 |
14 | const ALBUM_1: ImageType[] = [
15 | {
16 | id: 1,
17 | uri: require('@img/choubs_1.jpg'),
18 | color: Colors.primary
19 | },
20 | {
21 | id: 2,
22 | uri: require('@img/choubs_2.jpg'),
23 | color: Colors.secondary
24 | },
25 | {
26 | id: 3,
27 | uri: require('@img/choubs_3.jpg'),
28 | color: Colors.tertiary
29 | }
30 | ]
31 |
32 | const ALBUM_2: ImageType[] = [
33 | {
34 | id: 4,
35 | uri: require('@img/choubs_2.jpg'),
36 | color: Colors.primary
37 | },
38 | {
39 | id: 5,
40 | uri: require('@img/choubs_3.jpg'),
41 | color: Colors.secondary
42 | }
43 | ]
44 |
45 | const GalleryScreen: RNNFC = function ({}) {
46 | const { width } = useWindowDimensions()
47 |
48 | const [height] = useState(400)
49 | const [currentPage, setCurrentPage] = useState(0)
50 | const [images, setImages] = useState(ALBUM_1)
51 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
52 | const [_currentAlbum, setCurrentAlbum] = useState<1 | 2>(1)
53 |
54 | return (
55 |
56 | setCurrentPage(index)}
60 | renderItem={(item) => (
61 | {
65 | console.log('pressed: ', item.id, item.uri)
66 | }}>
67 |
71 | console.log('error: ', error.nativeEvent)
72 | }
73 | />
74 |
75 | )}
76 | />
77 |
78 |
84 |
85 | Curent page: {currentPage}
86 |
87 |
88 |
89 |
90 |
95 | setImages((old) => [
96 | ...old.filter((_, i) => i !== currentPage)
97 | ])
98 | }
99 | />
100 | {
105 | setCurrentAlbum((old) => {
106 | const currAlbum = old === 1 ? 2 : 1
107 | setImages(currAlbum === 1 ? ALBUM_2 : ALBUM_1)
108 | return currAlbum
109 | })
110 | }}
111 | />
112 | {images.length < ALBUM_1.length && (
113 |
118 | setImages((old) => {
119 | const prevIds = old.map((x) => x.id)
120 | const toAdd = ALBUM_1.filter(
121 | (x) => !prevIds.includes(x.id)
122 | )[0]
123 | return [...old, toAdd]
124 | })
125 | }
126 | />
127 | )}
128 |
129 |
130 | )
131 | }
132 | GalleryScreen.options = {
133 | topBar: {
134 | title: {
135 | text: 'Images Gallery'
136 | }
137 | }
138 | }
139 |
140 | export { GalleryScreen }
141 |
--------------------------------------------------------------------------------
/src/screens/animations/BarchartScreen/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@src/components'
2 | import { Colors } from '@src/theme'
3 | import React, { useCallback, useState } from 'react'
4 | import { StyleSheet, View } from 'react-native'
5 | import { NavigationFunctionComponent as RNNFC } from 'react-native-navigation'
6 | import { Barchart, DataItem } from './Barchart'
7 | import { Barchart as BarchartCustom, RenderBarProps } from './BarchartCustom'
8 | import { CustomBarItem } from './BarchartCustom/CustomBarItem'
9 | import { CONFIG, DATA } from './data'
10 |
11 | const BARCHART_HEIGHT = 200
12 |
13 | const styles = StyleSheet.create({
14 | container: {
15 | flex: 1,
16 | alignItems: 'center'
17 | },
18 | barchartContainer: {
19 | height: BARCHART_HEIGHT,
20 | borderWidth: StyleSheet.hairlineWidth,
21 | borderColor: Colors.secondary,
22 | width: 300
23 | },
24 | btnsContainer: {
25 | position: 'absolute',
26 | bottom: 0,
27 | height: 120,
28 | left: 0,
29 | right: 0,
30 | flexWrap: 'wrap',
31 | flexDirection: 'row',
32 | justifyContent: 'space-evenly',
33 | alignItems: 'center'
34 | },
35 | btn: {
36 | marginTop: 20,
37 | height: 40,
38 | width: 110
39 | },
40 | labelText: { fontSize: 14, textAlign: 'center' }
41 | })
42 |
43 | export type Props = {}
44 |
45 | const BarChartScreen: RNNFC = function ({}) {
46 | const [data, setData] = useState(CONFIG.data)
47 | const [animate, setAnimate] = useState(CONFIG.animate)
48 | const [horizontal, setHorizontal] = useState(false)
49 | function same() {
50 | setData((old) => [
51 | ...old.map((x) => ({
52 | ...x,
53 | value: 10
54 | }))
55 | ])
56 | }
57 |
58 | function random() {
59 | setData((old) => [
60 | ...old.map((x) => ({
61 | ...x,
62 | value: Math.random()
63 | }))
64 | ])
65 | }
66 |
67 | const _renderBar = useCallback(
68 | (props: RenderBarProps) => {
69 | return (
70 |
77 | )
78 | },
79 | [animate]
80 | )
81 |
82 | return (
83 |
84 |
85 |
96 |
97 |
98 |
109 |
110 |
111 |
117 |
123 | setData(DATA)}
128 | />
129 | setAnimate((old) => !old)}
134 | />
135 | setHorizontal((old) => !old)}
140 | />
141 |
142 |
143 | )
144 | }
145 |
146 | BarChartScreen.options = {
147 | topBar: {
148 | title: {
149 | text: 'Barchart'
150 | }
151 | }
152 | }
153 |
154 | export { BarChartScreen }
155 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
6 | "lib": [
7 | "es6",
8 | "ES2019"
9 | ] /* Specify library files to be included in the compilation. */,
10 | "allowJs": true /* Allow javascript files to be compiled. */,
11 | // "checkJs": true, /* Report errors in .js files. */
12 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "sourceMap": true, /* Generates corresponding '.map' file. */
15 | // "outFile": "./", /* Concatenate and emit output to single file. */
16 | // "outDir": "./", /* Redirect output structure to the directory. */
17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18 | // "removeComments": true, /* Do not emit comments to output. */
19 | "noEmit": true /* Do not emit outputs. */,
20 | // "incremental": true, /* Enable incremental compilation */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true /* Enable all strict type-checking options. */,
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
33 |
34 | /* Additional Checks */
35 | // "noUnusedLocals": true, /* Report errors on unused locals. */
36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39 |
40 | /* Module Resolution Options */
41 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
42 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
43 | "paths": {
44 | "@src/*": ["src/*"],
45 | "@img/*": ["img/*"]
46 | } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
48 | // "typeRoots": [], /* List of folders to include type definitions from. */
49 | // "types": [], /* Type declaration files to be included in compilation. */
50 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
51 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
53 |
54 | /* Source Map Options */
55 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
56 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
59 |
60 | /* Experimental Options */
61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
63 | "skipLibCheck": true
64 | },
65 | "exclude": [
66 | "node_modules",
67 | "babel.config.js",
68 | "metro.config.js",
69 | "jest.config.js"
70 | ]
71 | }
72 |
--------------------------------------------------------------------------------
/src/screens/animations/PaginateScreen/Paginate/Page.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react'
2 | import { StyleSheet } from 'react-native'
3 | import { Gesture, GestureDetector } from 'react-native-gesture-handler'
4 | import Animated, {
5 | runOnJS,
6 | useAnimatedStyle,
7 | useDerivedValue,
8 | withSpring,
9 | WithSpringConfig
10 | } from 'react-native-reanimated'
11 |
12 | export type Direction = 'left' | 'right'
13 |
14 | function stiffnessFromTension(oValue: number) {
15 | return (oValue - 30) * 3.62 + 194
16 | }
17 |
18 | function dampingFromFriction(oValue: number) {
19 | return (oValue - 8) * 3 + 25
20 | }
21 | const SPRING_CONFIG: WithSpringConfig = {
22 | stiffness: stiffnessFromTension(400),
23 | damping: dampingFromFriction(50),
24 | mass: 5,
25 | overshootClamping: true,
26 | restDisplacementThreshold: 0.01,
27 | restSpeedThreshold: 0.01
28 | }
29 |
30 | function updateOffsets(
31 | offsets: Offset[],
32 | direction: Direction,
33 | initOffsets: number[]
34 | ) {
35 | 'worklet'
36 |
37 | offsets.map((offset) => {
38 | if (direction === 'right')
39 | offset.position.value =
40 | offset.position.value === 1
41 | ? 2
42 | : offset.position.value === 2
43 | ? 0
44 | : 1
45 | else
46 | offset.position.value =
47 | offset.position.value === 1
48 | ? 0
49 | : offset.position.value === 2
50 | ? 1
51 | : 2
52 | offset.x.value = initOffsets[offset.position.value] // offset.position.value * width//
53 | offset.translateX.value = 0
54 | })
55 | }
56 |
57 | export type Offset = {
58 | x: Animated.SharedValue
59 | translateX: Animated.SharedValue
60 | position: Animated.SharedValue
61 | }
62 |
63 | export type Props = {
64 | offset: Offset
65 | offsets: Offset[]
66 | width: Animated.SharedValue
67 | height: Animated.SharedValue
68 | onDoneMoving?: (direction: Direction) => void
69 | initPositions: number[]
70 | }
71 |
72 | const Page: FC = function ({
73 | offset,
74 | width,
75 | height,
76 | children,
77 | offsets,
78 | onDoneMoving,
79 | initPositions
80 | }) {
81 | const { x, translateX } = offset
82 |
83 | const _translateX = useDerivedValue(() => x.value + translateX.value)
84 |
85 | const style = useAnimatedStyle(() => ({
86 | transform: [{ translateX: _translateX.value }],
87 | width: width.value,
88 | height: height.value
89 | }))
90 |
91 | // TODO: improve performances at it seems to be laggier than previous RNGH API
92 | const gesture = Gesture.Pan()
93 | .activeOffsetY([-1, 1])
94 | .onUpdate((event) => {
95 | offsets.map((_x) => (_x.translateX.value = event.translationX))
96 | })
97 | .onEnd((event) => {
98 | const velocityThresh = Math.abs(event.velocityX) > 100
99 | const swipeThresh = width.value / 2 < Math.abs(event.translationX)
100 | const shouldSwap = velocityThresh || swipeThresh
101 |
102 | const config = SPRING_CONFIG
103 | config.velocity = event.velocityX
104 |
105 | const moveRight = event.translationX > 0
106 |
107 | if (!shouldSwap) {
108 | offsets.map(
109 | (_x) => (_x.translateX.value = withSpring(0, config))
110 | )
111 | } else {
112 | if (moveRight)
113 | offsets.map(
114 | (_x, i) =>
115 | (_x.translateX.value = withSpring(
116 | width.value,
117 | config,
118 | () => {
119 | if (i !== 0) return
120 | updateOffsets(
121 | offsets,
122 | 'right',
123 | initPositions
124 | )
125 | if (onDoneMoving)
126 | runOnJS(onDoneMoving)('right')
127 | }
128 | ))
129 | )
130 | else
131 | offsets.map(
132 | (_x, i) =>
133 | (_x.translateX.value = withSpring(
134 | -width.value,
135 | config,
136 | () => {
137 | if (i !== 0) return
138 | updateOffsets(
139 | offsets,
140 | 'left',
141 | initPositions
142 | )
143 | if (onDoneMoving)
144 | runOnJS(onDoneMoving)('left')
145 | }
146 | ))
147 | )
148 | }
149 | })
150 |
151 | return (
152 |
153 |
155 | {children}
156 |
157 |
158 | )
159 | }
160 |
161 | export { Page }
162 |
--------------------------------------------------------------------------------