Skip to content

Commit

Permalink
Rewrite examples to TS (software-mansion#1724)
Browse files Browse the repository at this point in the history
## Description

We want to transition smoothly into TS so we start by adding types to our examples so we can show how to use Reanimated.

### Things found while rewriting

- `ExtrapolationExample` uses `interpolate` with an object as extrapolation type, it isn't typed
- `useAnimatedGestureHandler` should take gesture handlers as T, not their event types - for discussion

## Changes

- Added TS parser & new rules to Eslint config
- Added `ts-check` yarn script
- Added specific Eslint ignores to TS test file

## Test code and steps to reproduce

N/A, Example app still works.
  • Loading branch information
jakub-gonet authored Feb 22, 2021
1 parent 46c8bc2 commit 35218c0
Show file tree
Hide file tree
Showing 55 changed files with 969 additions and 293 deletions.
31 changes: 23 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
module.exports = {
parser: 'babel-eslint',
root: true,
parser: '@typescript-eslint/parser',
extends: [
'standard',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/flowtype',
'prettier/react',
'prettier/standard',
'plugin:import/typescript',
],
plugins: ['react', 'react-native', 'import', 'jest'],
plugins: ['react', 'react-native', 'import', 'jest', '@typescript-eslint'],
env: {
'react-native/react-native': true,
'jest/globals': true,
},
rules: {
'import/no-unresolved': 2,
'react/jsx-uses-vars': 2,
'react/jsx-uses-react': 2,
},
settings: {
'import/core-modules': ['react-native-reanimated'],
'import/resolver': {
'babel-module': {},
},
},
rules: {
'import/no-unresolved': 'error',
'react/jsx-uses-vars': 'error',
'react/jsx-uses-react': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off', // TODO consider enabling this (currently it reports styles defined at the bottom of the file)
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description',
'ts-expect-error': 'allow-with-description',
},
],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
};
2 changes: 1 addition & 1 deletion Example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ buck-out/

# Expo
/.expo
/web-build
/web-build
9 changes: 7 additions & 2 deletions Example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"test": "jest",
"link-web": "npm link react-native-reanimated",
"web": "expo start --web || expo start --web --web-only",
"lint": "eslint ."
"lint-check": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check src/",
"ts-check": "yarn tsc"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28",
Expand All @@ -35,13 +36,17 @@
"react-native-screens": "^2.17.1",
"react-native-status-bar-height": "^2.4.0",
"react-native-svg": "^12.1.0",
"react-native-web": "^0.14.7"
"react-native-web": "^0.14.7",
"typescript": "^4.1.5"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/plugin-proposal-optional-chaining": "^7.12.13",
"@babel/runtime": "^7.12.9",
"@react-native-community/eslint-config": "^2.0.0",
"@types/d3-shape": "^2.0.0",
"@types/react": "^17.0.2",
"@types/react-native": "^0.63.50",
"babel-jest": "^26.6.3",
"babel-plugin-module-resolver": "^3.2.0",
"eslint": "^7.14.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import Animated, {
Extrapolate,
interpolate,
} from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';

const data = [
{ artist: 'Nirvana', song: 'Smells Like Teen Spirit' },
Expand All @@ -37,14 +40,14 @@ const BIG_BALL_SIZE = 200;
const SMALL_BALL_SIZE = 50;
const INNER_BALL_SIZE = BIG_BALL_SIZE - SMALL_BALL_SIZE * 2;

function ScrollExample() {
function ScrollExample(): React.ReactElement {
const position = useSharedValue(0);
const animatedRef = useAnimatedRef();
const animatedRef = useAnimatedRef<Animated.ScrollView>();

const itemTotalSize = ITEM_SIZE.size + ITEM_SIZE.margin * 2;
const borderMargin = SCREEN_WIDTH / 2 - itemTotalSize / 2 + ITEM_SIZE.margin;

const scrollToNearestItem = (offset) => {
const scrollToNearestItem = (offset: number) => {
'worklet';
let minDistance;
let minDistanceIndex = 0;
Expand All @@ -71,7 +74,18 @@ function ScrollExample() {
},
});

const gestureHandler = useAnimatedGestureHandler({
type Vector2D = {
x: number;
y: number;
};
type AnimatedGHContext = {
start: Vector2D;
last: Vector2D;
};
const gestureHandler = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
AnimatedGHContext
>({
onStart: (e, ctx) => {
ctx.start = { x: e.x, y: e.y };
ctx.last = ctx.start;
Expand Down Expand Up @@ -109,7 +123,7 @@ function ScrollExample() {
);
scrollTo(animatedRef, position.value, 0, false);
},
onEnd: (e, ctx) => {
onEnd: (_e, _ctx) => {
scrollToNearestItem(position.value);
},
});
Expand All @@ -125,7 +139,11 @@ function ScrollExample() {
onScroll={scrollHandler}>
{data.map(({ artist, song }, i) => {
const uas = useAnimatedStyle(() => {
const style = {};
const style: {
opacity?: number;
marginLeft?: number;
marginRight?: number;
} = {};
const itemDistance =
Math.abs(position.value - i * itemTotalSize) / itemTotalSize;
let opacity = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Animated, {
import { View, Button } from 'react-native';
import React from 'react';

export default function AnimatedStyleUpdateExample(props) {
function AnimatedStyleUpdateExample(): React.ReactElement {
const randomWidth = useSharedValue(10);

const config = {
Expand Down Expand Up @@ -42,3 +42,5 @@ export default function AnimatedStyleUpdateExample(props) {
</View>
);
}

export default AnimatedStyleUpdateExample;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import Svg, { Path } from 'react-native-svg';
import * as shape from 'd3-shape';
import { IconProp } from '@fortawesome/fontawesome-svg-core';

const { width, height } = Dimensions.get('window');

Expand Down Expand Up @@ -88,14 +89,22 @@ const styles = StyleSheet.create({
},
});

type ButtonProps = {
item: IconProp;
index: number;
activeIndex: Animated.SharedValue<number>;
width: number;
position: number;
readonly indicatorPosition: Animated.SharedValue<number>;
};
function Button({
item,
index,
activeIndex,
width,
position,
indicatorPosition,
}) {
}: ButtonProps) {
const staticIconStyle = useAnimatedStyle(() => {
const visibility = interpolate(
indicatorPosition.value,
Expand Down Expand Up @@ -125,7 +134,13 @@ function Button({
);
}

function ActiveIcon({ item, index, activeIndex, width }) {
type ActiveIconProps = {
item: IconProp;
index: number;
activeIndex: Animated.SharedValue<number>;
width: number;
};
function ActiveIcon({ item, index, activeIndex, width }: ActiveIconProps) {
const circleIconStyle = useAnimatedStyle(() => {
const isActive = index === activeIndex.value;
const yOffset = isActive ? 0 : 80;
Expand Down Expand Up @@ -160,7 +175,7 @@ function ActiveIcon({ item, index, activeIndex, width }) {
);
}

const Bar = () => {
function Bar() {
const activeIndex = useSharedValue(0);

const indicatorPosition = useDerivedValue(() => {
Expand Down Expand Up @@ -208,7 +223,7 @@ const Bar = () => {
})}
</View>
);
};
}

const tabBarStyles = StyleSheet.create({
container: {
Expand All @@ -223,13 +238,13 @@ const tabBarStyles = StyleSheet.create({
},
});

const TabBar = () => {
function TabBar(): React.ReactElement {
return (
<View style={tabBarStyles.container}>
<View style={tabBarStyles.dummyPusher} />
<Bar />
</View>
);
};
}

export default TabBar;
44 changes: 35 additions & 9 deletions Example/src/App.js → Example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { FlatList, StyleSheet, Text, View, LogBox } from 'react-native';

import { RectButton, ScrollView } from 'react-native-gesture-handler';

import { createStackNavigator } from '@react-navigation/stack';
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import { NavigationContainer } from '@react-navigation/native';

import Reanimated1 from '../reanimated1/App';
Expand All @@ -22,10 +25,11 @@ import AnimatedTabBarExample from './AnimatedTabBarExample';
import LightboxExample from './LightboxExample';
import LiquidSwipe from './LiquidSwipe';
import ScrollExample from './AnimatedScrollExample';

LogBox.ignoreLogs(['Calling `getNode()`']);

const SCREENS = {
type Screens = Record<string, { screen: React.ComponentType; title?: string }>;

const SCREENS: Screens = {
AnimatedStyleUpdate: {
screen: AnimatedStyleUpdateExample,
title: '🆕 Animated Style Update',
Expand Down Expand Up @@ -84,7 +88,13 @@ const SCREENS = {
},
};

function MainScreen({ navigation, setUseRea2 }) {
type RootStackParams = { Home: undefined } & { [key: string]: undefined };
type MainScreenProps = {
navigation: StackNavigationProp<RootStackParams, 'Home'>;
setUseRea2: (useRea2: boolean) => void;
};

function MainScreen({ navigation, setUseRea2 }: MainScreenProps) {
const data = Object.keys(SCREENS).map((key) => ({ key }));
return (
<FlatList
Expand All @@ -104,11 +114,21 @@ function MainScreen({ navigation, setUseRea2 }) {
);
}

export function ItemSeparator() {
export function ItemSeparator(): React.ReactElement {
return <View style={styles.separator} />;
}

export function MainScreenItem({ item, onPressItem, screens }) {
type Item = { key: string };
type MainScreenItemProps = {
item: Item;
onPressItem: ({ key }: Item) => void;
screens: Screens;
};
export function MainScreenItem({
item,
onPressItem,
screens,
}: MainScreenItemProps): React.ReactElement {
const { key } = item;
return (
<RectButton style={styles.button} onPress={() => onPressItem(item)}>
Expand All @@ -117,7 +137,11 @@ export function MainScreenItem({ item, onPressItem, screens }) {
);
}

function LaunchReanimated1({ setUseRea2 }) {
function LaunchReanimated1({
setUseRea2,
}: {
setUseRea2: (useRea2: boolean) => void;
}) {
return (
<>
<ItemSeparator />
Expand All @@ -130,7 +154,7 @@ function LaunchReanimated1({ setUseRea2 }) {

const Stack = createStackNavigator();

const Reanimated2 = (setUseRea2) => (
const Reanimated2 = (setUseRea2: (useRea2: boolean) => void) => (
<Stack.Navigator>
<Stack.Screen
name="Home"
Expand All @@ -148,7 +172,7 @@ const Reanimated2 = (setUseRea2) => (
</Stack.Navigator>
);

export default function App() {
function App(): React.ReactElement {
const [useRea2, setUseRea2] = React.useState(true);

return (
Expand Down Expand Up @@ -178,3 +202,5 @@ export const styles = StyleSheet.create({
backgroundColor: '#fff',
},
});

export default App;
Loading

0 comments on commit 35218c0

Please sign in to comment.