diff --git a/.eslintrc.json b/.eslintrc.json index 95c4ca679f..5fde07df5d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "root": true, "extends": [ "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", @@ -15,7 +16,7 @@ "project": ["./tsconfig.json"] }, "env": { "browser": true, "node": true, "jest/globals": true }, - "plugins": ["jest"], + "plugins": ["react", "jest"], "ignorePatterns": ["packages/react-native-gesture-handler/lib/**/*"], "rules": { // removed in new jest-eslint-plugin, referenced in satya config @@ -26,12 +27,16 @@ "warn", { "assertFunctionNames": ["expect*", "assert*"] } ], + "jest/no-conditional-expect": "warn", + // temporary, remove after we type internals better "@typescript-eslint/restrict-template-expressions": "warn", "@typescript-eslint/no-unsafe-member-access": "warn", "@typescript-eslint/no-unsafe-call": "warn", "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/ban-types": "warn", // common @@ -39,6 +44,11 @@ "import/named": "off", "react/sort-comp": "off", "react/no-unused-prop-types": "warn", + "react-hooks/exhaustive-deps": "warn", + "@typescript-eslint/no-floating-promises": "warn", + "@eslint-react/no-missing-component-display-name": "warn", + "@eslint-react/no-nested-components": "warn", + "@eslint-react/no-nested-component-definitions": "warn", "prefer-const": [ "error", { @@ -49,6 +59,13 @@ "error", { "argsIgnorePattern": "^_" } ], + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/no-misused-promises": "warn", + "@eslint-react/no-array-index-key": "warn", + "@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn", + "@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn", "no-redeclare": "off", "@typescript-eslint/no-redeclare": "error", "no-use-before-define": "off", @@ -61,6 +78,7 @@ } ], "curly": "error", - "spaced-comment": "error" + "spaced-comment": "error", + "no-alert": "warn" } } diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 4fa12b5e19..141d7d52e9 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -34,7 +34,7 @@ jobs: cache: 'yarn' - name: Install node dependencies working-directory: ${{ matrix.working-directory }} - run: yarn install --immutable && yarn postinstall + run: yarn install --immutable - name: Build app working-directory: ${{ matrix.working-directory }}/android run: ./gradlew assembleDebug --console=plain -PreactNativeArchitectures=arm64-v8a diff --git a/.github/workflows/ios-build.yml b/.github/workflows/ios-build.yml index 20c00fe1c8..939b7d8e67 100644 --- a/.github/workflows/ios-build.yml +++ b/.github/workflows/ios-build.yml @@ -36,7 +36,7 @@ jobs: cache: 'yarn' - name: Install node dependencies working-directory: ${{ matrix.working-directory }} - run: yarn install --immutable && yarn postinstall + run: yarn install --immutable - name: Install pods if: ${{ matrix.working-directory == 'apps/BasicExample' }} working-directory: ${{ matrix.working-directory }}/ios diff --git a/.github/workflows/static-example-apps-checks.yml b/.github/workflows/static-example-apps-checks.yml index ae41957c8e..f6a1a50ccc 100644 --- a/.github/workflows/static-example-apps-checks.yml +++ b/.github/workflows/static-example-apps-checks.yml @@ -26,14 +26,11 @@ jobs: with: node-version: 18 cache: 'yarn' - - name: Install root node dependencies - run: yarn --immutable && yarn prepare - - name: Install ${{ matrix.working-directory }} app node dependencies - working-directory: ${{ matrix.working-directory }} - run: yarn + - name: Install node dependencies + run: yarn --immutable && yarn postinstall - name: Check types working-directory: ${{ matrix.working-directory }} - run: yarn tsc --noEmit + run: yarn ts-check - name: Lint working-directory: ${{ matrix.working-directory }} - run: yarn lint-check + run: yarn lint-js diff --git a/.github/workflows/static-root-checks.yml b/.github/workflows/static-root-checks.yml index 693f71a0d9..568a84daa2 100644 --- a/.github/workflows/static-root-checks.yml +++ b/.github/workflows/static-root-checks.yml @@ -26,13 +26,13 @@ jobs: node-version: 18 cache: 'yarn' - name: Install node dependencies - run: yarn + run: yarn --immutable && yarn postinstall - name: Check types working-directory: ${{ env.WORKING_DIRECTORY }} - run: yarn tsc --noEmit + run: yarn ts-check - name: Lint working-directory: ${{ env.WORKING_DIRECTORY }} - run: yarn lint:js-root + run: yarn lint-js - name: Check for circular dependencies working-directory: ${{ env.WORKING_DIRECTORY }} run: yarn circular-dependency-check diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 890ce5832c..3e266ad060 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,3 +1,6 @@ { - "*.(js|jsx|ts|tsx)": ["yarn eslint", "yarn prettier --write"] + "**/*.{ts,tsx}": "yarn format:js", + "packages/react-native-gesture-handler/android/**/*.kt": "yarn format:android", + "packages/react-native-gesture-handler/apple/**/*.{h,m,mm,cpp}": "yarn format:apple", + "packages/react-native-gesture-handler/src/specs/*.ts": "yarn sync-architectures" } diff --git a/apps/BasicExample/.eslintrc.js b/apps/BasicExample/.eslintrc.js deleted file mode 100644 index e7aceba517..0000000000 --- a/apps/BasicExample/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: '../../.eslintrc.json', -}; diff --git a/apps/BasicExample/package.json b/apps/BasicExample/package.json index c1f0cda47e..18a09b7f24 100644 --- a/apps/BasicExample/package.json +++ b/apps/BasicExample/package.json @@ -8,7 +8,9 @@ "lint": "eslint .", "start": "react-native start", "test": "jest", - "postinstall": "" + "ts-check": "yarn tsc --noEmit", + "lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src/**/*.{js,jsx,ts,tsx}'", + "format-js": "prettier --write --list-different './src/**/*.{js,jsx,ts,tsx}'" }, "dependencies": { "react": "19.0.0", @@ -27,13 +29,20 @@ "@react-native/metro-config": "0.79.0", "@react-native/typescript-config": "0.79.0", "@types/jest": "^29.5.13", - "@types/react": "^19.0.0", + "@types/react": "^19.0.12", "@types/react-test-renderer": "^19.0.0", - "eslint": "^8.19.0", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.57.0", + "eslint-config-satya164": "3.3.0", + "eslint-import-resolver-babel-module": "^5.2.0", + "eslint-plugin-jest": "27.4.3", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react": "^7.37.5", "jest": "^29.6.3", - "prettier": "2.8.8", + "prettier": "3.3.3", "react-test-renderer": "19.0.0", - "typescript": "5.0.4" + "typescript": "~5.8.3" }, "engines": { "node": ">=18" diff --git a/apps/BasicExample/src/Navigator.tsx b/apps/BasicExample/src/Navigator.tsx index 74f6a4720e..0a8ad50b78 100644 --- a/apps/BasicExample/src/Navigator.tsx +++ b/apps/BasicExample/src/Navigator.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-native/no-inline-styles */ import React, { useEffect, useState } from 'react'; import { View, Text, Pressable, BackHandler } from 'react-native'; @@ -55,7 +54,7 @@ export default class Navigator { goBack() { if (this.history.length === 1) { - throw "Can't go back, no history"; + throw new Error("Can't go back, no history"); } this.history.pop(); this.setCurrentRoute(this.history[this.history.length - 1]); @@ -79,6 +78,7 @@ export default class Navigator { this.setCurrentRoute = setCurrentRoute; useEffect(() => { + // eslint-disable-next-line @typescript-eslint/unbound-method return BackHandler.addEventListener('hardwareBackPress', this.backHandler) .remove; }, []); diff --git a/apps/CommonApp/.eslintrc.js b/apps/CommonApp/.eslintrc.js deleted file mode 100644 index 187894b6af..0000000000 --- a/apps/CommonApp/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: '@react-native', -}; diff --git a/apps/CommonApp/.prettierrc.js b/apps/CommonApp/.prettierrc.js deleted file mode 100644 index 2b540746a7..0000000000 --- a/apps/CommonApp/.prettierrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - arrowParens: 'avoid', - bracketSameLine: true, - bracketSpacing: false, - singleQuote: true, - trailingComma: 'all', -}; diff --git a/apps/CommonApp/App.tsx b/apps/CommonApp/App.tsx index 1beb997b19..8eec79603b 100644 --- a/apps/CommonApp/App.tsx +++ b/apps/CommonApp/App.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, { useEffect } from 'react'; import { Text, View, @@ -13,7 +13,7 @@ import { StackNavigationProp, StackScreenProps, } from '@react-navigation/stack'; -import {NavigationContainer, ParamListBase} from '@react-navigation/native'; +import { NavigationContainer, ParamListBase } from '@react-navigation/native'; import { GestureHandlerRootView, RectButton, @@ -24,7 +24,7 @@ import OverflowParent from './src/release_tests/overflowParent'; import DoublePinchRotate from './src/release_tests/doubleScalePinchAndRotate'; import DoubleDraggable from './src/release_tests/doubleDraggable'; import GesturizedPressable from './src/release_tests/gesturizedPressable'; -import {ComboWithGHScroll} from './src/release_tests/combo'; +import { ComboWithGHScroll } from './src/release_tests/combo'; import { TouchablesIndex, TouchableExample, @@ -43,10 +43,10 @@ import TwoFingerPan from './src/release_tests/twoFingerPan'; import SvgCompatibility from './src/release_tests/svg'; import NestedText from './src/release_tests/nestedText'; -import {PinchableBox} from './src/recipes/scaleAndRotate'; +import { PinchableBox } from './src/recipes/scaleAndRotate'; import PanAndScroll from './src/recipes/panAndScroll'; -import {BottomSheet} from './src/showcase/bottomSheet'; +import { BottomSheet } from './src/showcase/bottomSheet'; import Swipeables from './src/showcase/swipeable'; import ChatHeads from './src/showcase/chatHeads'; @@ -79,8 +79,8 @@ import Pressable from './src/new_api/pressable'; import EmptyExample from './src/empty/EmptyExample'; import RectButtonBorders from './src/release_tests/rectButton'; -import {ListWithHeader} from './src/ListWithHeader'; -import {COLORS} from './src/common'; +import { ListWithHeader } from './src/ListWithHeader'; +import { COLORS } from './src/common'; import MacosDraggable from './src/simple/draggable'; import Tap from './src/simple/tap'; @@ -88,7 +88,7 @@ import LongPressExample from './src/simple/longPress'; import ManualExample from './src/simple/manual'; import SimpleFling from './src/simple/fling'; -import {Icon} from '@swmansion/icons'; +import { Icon } from '@swmansion/icons'; interface Example { name: string; @@ -103,23 +103,23 @@ interface ExamplesSection { const EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'Empty', - data: [{name: 'Empty Example', component: EmptyExample}], + data: [{ name: 'Empty Example', component: EmptyExample }], }, { sectionTitle: 'New api', data: [ - {name: 'Ball with velocity', component: VelocityTest}, - {name: 'Camera', component: Camera}, - {name: 'Transformations', component: Transformations}, - {name: 'Overlap', component: Overlap}, - {name: 'Bottom Sheet', component: BottomSheetNewApi}, - {name: 'Calculator', component: Calculator}, - {name: 'Chat Heads', component: ChatHeadsNewApi}, - {name: 'Drag and drop', component: DragNDrop}, - {name: 'New Swipeable', component: Swipeable}, - {name: 'Pressable', component: Pressable}, - {name: 'Hover', component: Hover}, - {name: 'Hoverable icons', component: HoverableIcons}, + { name: 'Ball with velocity', component: VelocityTest }, + { name: 'Camera', component: Camera }, + { name: 'Transformations', component: Transformations }, + { name: 'Overlap', component: Overlap }, + { name: 'Bottom Sheet', component: BottomSheetNewApi }, + { name: 'Calculator', component: Calculator }, + { name: 'Chat Heads', component: ChatHeadsNewApi }, + { name: 'Drag and drop', component: DragNDrop }, + { name: 'New Swipeable', component: Swipeable }, + { name: 'Pressable', component: Pressable }, + { name: 'Hover', component: Hover }, + { name: 'Hoverable icons', component: HoverableIcons }, { name: 'Horizontal Drawer (Reanimated 2 & RNGH 2)', component: BetterHorizontalDrawer, @@ -133,11 +133,11 @@ const EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'Basic examples', data: [ - {name: 'Draggable', component: Draggable}, - {name: 'Multitap', component: MultiTap}, - {name: 'Bouncing box', component: BouncingBox}, - {name: 'Pan responder', component: PanResponder}, - {name: 'Horizontal drawer', component: HorizontalDrawer}, + { name: 'Draggable', component: Draggable }, + { name: 'Multitap', component: MultiTap }, + { name: 'Bouncing box', component: BouncingBox }, + { name: 'Pan responder', component: PanResponder }, + { name: 'Horizontal drawer', component: HorizontalDrawer }, { name: 'Pager & drawer', component: PagerAndDrawer, @@ -148,22 +148,22 @@ const EXAMPLES: ExamplesSection[] = [ component: ForceTouch, unsupportedPlatforms: new Set(['web', 'android', 'macos']), }, - {name: 'Fling', component: Fling}, + { name: 'Fling', component: Fling }, ], }, { sectionTitle: 'Recipes', data: [ - {name: 'Pinch & rotate', component: PinchableBox}, - {name: 'Pan & scroll', component: PanAndScroll}, + { name: 'Pinch & rotate', component: PinchableBox }, + { name: 'Pan & scroll', component: PanAndScroll }, ], }, { sectionTitle: 'Showcase', data: [ - {name: 'Bottom sheet', component: BottomSheet}, - {name: 'Swipeables', component: Swipeables}, - {name: 'Chat heads', component: ChatHeads}, + { name: 'Bottom sheet', component: BottomSheet }, + { name: 'Swipeables', component: Swipeables }, + { name: 'Chat heads', component: ChatHeads }, ], }, { @@ -194,33 +194,33 @@ const EXAMPLES: ExamplesSection[] = [ name: 'Svg integration with Gesture Handler', component: SvgCompatibility, }, - {name: 'Double pinch & rotate', component: DoublePinchRotate}, - {name: 'Double draggable', component: DoubleDraggable}, - {name: 'Rows', component: Rows}, - {name: 'Nested Fling', component: NestedFling}, + { name: 'Double pinch & rotate', component: DoublePinchRotate }, + { name: 'Double draggable', component: DoubleDraggable }, + { name: 'Rows', component: Rows }, + { name: 'Nested Fling', component: NestedFling }, { name: 'Combo', component: ComboWithGHScroll, unsupportedPlatforms: new Set(['web']), }, - {name: 'Touchables', component: TouchablesIndex as React.ComponentType}, - {name: 'MouseButtons', component: MouseButtons}, + { name: 'Touchables', component: TouchablesIndex as React.ComponentType }, + { name: 'MouseButtons', component: MouseButtons }, { name: 'ContextMenu', component: ContextMenu, unsupportedPlatforms: new Set(['android', 'ios', 'macos']), }, - {name: 'PointerType', component: PointerType}, - {name: 'Reanimated Drawer Layout', component: ReanimatedDrawerLayout}, - {name: 'Swipeable Reanimation', component: SwipeableReanimation}, - {name: 'RectButton (borders)', component: RectButtonBorders}, - {name: 'Gesturized pressable', component: GesturizedPressable}, + { name: 'PointerType', component: PointerType }, + { name: 'Reanimated Drawer Layout', component: ReanimatedDrawerLayout }, + { name: 'Swipeable Reanimation', component: SwipeableReanimation }, + { name: 'RectButton (borders)', component: RectButtonBorders }, + { name: 'Gesturized pressable', component: GesturizedPressable }, { name: 'Web styles reset', component: WebStylesResetExample, unsupportedPlatforms: new Set(['android', 'ios', 'macos']), }, - {name: 'Stylus data', component: StylusData}, + { name: 'Stylus data', component: StylusData }, { name: 'Two finger Pan', component: TwoFingerPan, @@ -236,11 +236,11 @@ const EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'Simple', data: [ - {name: 'Simple Draggable', component: MacosDraggable}, - {name: 'Tap', component: Tap}, - {name: 'LongPress', component: LongPressExample}, - {name: 'Manual', component: ManualExample}, - {name: 'Simple Fling', component: SimpleFling}, + { name: 'Simple Draggable', component: MacosDraggable }, + { name: 'Tap', component: Tap }, + { name: 'LongPress', component: LongPressExample }, + { name: 'Manual', component: ManualExample }, + { name: 'Simple Fling', component: SimpleFling }, ], }, ]; @@ -250,7 +250,7 @@ const LAST_EXAMPLE_KEY = 'lastExample'; type RootStackParamList = { Home: undefined; - TouchableExample: {item: string}; + TouchableExample: { item: string }; } & { [Screen: string]: undefined; }; @@ -277,17 +277,19 @@ export default function App() { }}> - {EXAMPLES.flatMap(({data}) => data).flatMap(({name, component}) => ( - component} - options={{title: name}} - /> - ))} + {EXAMPLES.flatMap(({ data }) => data).flatMap( + ({ name, component }) => ( + component} + options={{ title: name }} + /> + ) + )} @@ -297,20 +299,20 @@ export default function App() { function navigate( navigation: StackNavigationProp, - dest: string, + dest: string ) { AsyncStorage.setItem(LAST_EXAMPLE_KEY, dest); navigation.navigate(dest); } -function MainScreen({navigation}: StackScreenProps) { +function MainScreen({ navigation }: StackScreenProps) { useEffect(() => { AsyncStorage.multiGet([OPEN_LAST_EXAMPLE_KEY, LAST_EXAMPLE_KEY]).then( ([openLastExample, lastExample]) => { if (openLastExample[1] === 'true' && lastExample[1]) { navigate(navigation, lastExample[1]); } - }, + } ); // we only want to run this effect once // eslint-disable-next-line react-hooks/exhaustive-deps @@ -321,16 +323,16 @@ function MainScreen({navigation}: StackScreenProps) { example.name} + keyExtractor={(example) => example.name} ListHeaderComponent={OpenLastExampleSetting} - renderItem={({item}) => ( + renderItem={({ item }) => ( navigate(navigation, name)} + onPressItem={(name) => navigate(navigation, name)} enabled={!item.unsupportedPlatforms?.has(Platform.OS)} /> )} - renderSectionHeader={({section: {sectionTitle}}) => ( + renderSectionHeader={({ section: { sectionTitle } }) => ( {sectionTitle} )} ItemSeparatorComponent={() => } @@ -343,7 +345,7 @@ function OpenLastExampleSetting() { const [openLastExample, setOpenLastExample] = React.useState(false); useEffect(() => { - AsyncStorage.getItem(OPEN_LAST_EXAMPLE_KEY).then(value => { + AsyncStorage.getItem(OPEN_LAST_EXAMPLE_KEY).then((value) => { setOpenLastExample(value === 'true'); }); }, []); @@ -380,7 +382,7 @@ interface MainScreenItemProps { enabled: boolean; } -function MainScreenItem({name, onPressItem, enabled}: MainScreenItemProps) { +function MainScreenItem({ name, onPressItem, enabled }: MainScreenItemProps) { return ( =18" diff --git a/apps/CommonApp/src/ListWithHeader/Header.tsx b/apps/CommonApp/src/ListWithHeader/Header.tsx index 8d4c05d065..4a529f03a3 100644 --- a/apps/CommonApp/src/ListWithHeader/Header.tsx +++ b/apps/CommonApp/src/ListWithHeader/Header.tsx @@ -13,7 +13,9 @@ import Animated, { } from 'react-native-reanimated'; import { COLORS } from '../common'; +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires const SIGNET = require('./signet.png'); +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires const TEXT = require('./text.png'); export const HEADER_HEIGHT = diff --git a/apps/CommonApp/src/ListWithHeader/ListWithHeader.tsx b/apps/CommonApp/src/ListWithHeader/ListWithHeader.tsx index 51f5516822..acd8709e56 100644 --- a/apps/CommonApp/src/ListWithHeader/ListWithHeader.tsx +++ b/apps/CommonApp/src/ListWithHeader/ListWithHeader.tsx @@ -97,46 +97,50 @@ interface ScrollComponentWithOffsetProps extends ScrollViewProps { scrollOffset: SharedValue; animatedScrollEnabled: SharedValue; dragGesture: GestureType; + ref?: React.RefObject; } -const ScrollComponentWithOffset = React.forwardRef( - (props: ScrollComponentWithOffsetProps, ref: any) => { - const scrollRef = useAnimatedRef(); - const scrollViewOffset = useScrollViewOffset(scrollRef); - - useAnimatedReaction( - () => { - return scrollViewOffset.value; - }, - (offset) => { - props.scrollOffset.value = offset; - } - ); +const ScrollComponentWithOffset = ({ + ref, + ...props +}: ScrollComponentWithOffsetProps) => { + const scrollRef = useAnimatedRef(); + const scrollViewOffset = useScrollViewOffset(scrollRef); + + useAnimatedReaction( + () => { + return scrollViewOffset.value; + }, + (offset) => { + props.scrollOffset.value = offset; + } + ); - useEffect(() => { + useEffect(() => { + if (ref) { ref.current = scrollRef.current; - }, [ref, scrollRef]); + } + }, [ref, scrollRef]); - const scrollProps = useAnimatedProps(() => { - return { - scrollEnabled: props.animatedScrollEnabled.value, - }; - }); + const scrollProps = useAnimatedProps(() => { + return { + scrollEnabled: props.animatedScrollEnabled.value, + }; + }); - const scrollGesture = Gesture.Native() - .disallowInterruption(true) - .simultaneousWithExternalGesture(props.dragGesture); + const scrollGesture = Gesture.Native() + .disallowInterruption(true) + .simultaneousWithExternalGesture(props.dragGesture); - return ( - - - - ); - } -); + return ( + + + + ); +}; diff --git a/apps/CommonApp/src/basic/horizontalDrawer/index.tsx b/apps/CommonApp/src/basic/horizontalDrawer/index.tsx index a99ba85ba2..1f78ffb3c3 100644 --- a/apps/CommonApp/src/basic/horizontalDrawer/index.tsx +++ b/apps/CommonApp/src/basic/horizontalDrawer/index.tsx @@ -8,9 +8,11 @@ import { TextInput, } from 'react-native'; -import { RectButton } from 'react-native-gesture-handler'; - -import { DrawerLayout, DrawerType } from 'react-native-gesture-handler'; +import { + RectButton, + DrawerLayout, + DrawerType, +} from 'react-native-gesture-handler'; const TYPES: DrawerType[] = ['front', 'back', 'back', 'slide']; const PARALLAX = [false, false, true, false]; diff --git a/apps/CommonApp/src/basic/panResponder/index.tsx b/apps/CommonApp/src/basic/panResponder/index.tsx index e17928f784..59b5d4ff00 100644 --- a/apps/CommonApp/src/basic/panResponder/index.tsx +++ b/apps/CommonApp/src/basic/panResponder/index.tsx @@ -134,10 +134,6 @@ class PanResponderExample extends Component<{}, { style: CircleStyles }> { } export default class Example extends Component { - onClick = () => { - // eslint-disable-next-line no-alert - window.alert("I'm so touched"); - }; render() { return ( diff --git a/apps/CommonApp/src/new_api/betterHorizontalDrawer/BetterHorizonatalDrawer.tsx b/apps/CommonApp/src/new_api/betterHorizontalDrawer/BetterHorizonatalDrawer.tsx index 950505b7d8..8389a6ee42 100644 --- a/apps/CommonApp/src/new_api/betterHorizontalDrawer/BetterHorizonatalDrawer.tsx +++ b/apps/CommonApp/src/new_api/betterHorizontalDrawer/BetterHorizonatalDrawer.tsx @@ -177,430 +177,426 @@ export interface DrawerLayoutController { close: () => void; } -export const DrawerLayout = React.forwardRef< - DrawerLayoutController, - BetterDrawerLayoutProps ->( - ( - { - drawerWidth = 200, - drawerPosition = 'left', - drawerType = 'front', - edgeWidth = 20, - minSwipeDistance = 3, - overlayColor = 'rgba(0, 0, 0, 0.7)', - drawerLockMode = 'unlocked', - enableTrackpadTwoFingerGesture = false, - keyboardDismissMode, - statusBarAnimation, - hideStatusBar, - drawerBackgroundColor, - drawerContainerStyle, - contentContainerStyle, - children, - renderNavigationView, - onDrawerClose, - onDrawerOpen, - onDrawerSlide, - onDrawerStateChanged, - }: BetterDrawerLayoutProps, - ref - ) => { - const animationConfig = { damping: 30, stiffness: 250 }; - - const fromLeft = drawerPosition === 'left'; - const drawerSlide = drawerType !== 'back'; - const containerSlide = drawerType !== 'front'; - - // setting NaN as a starting value allows to tell when the value gets changes - // for the first time - const [containerWidth, setContainerWidth] = useState(Number.NaN); - const [drawerVisible, setDrawerVisible] = useState(false); - - const drawerState = useSharedValue(BetterDrawerState.IDLE); - // between 0 and drawerWidth (drawer on the left) or -drawerWidth and 0 (drawer on the right) - const drawerOffset = useSharedValue(0); - // stores value of the offset at the start of the gesture - const drawerSavedOffset = useSharedValue(0); - // stores the translation that is supposed to be ignored (user tried to - // drag while animation was running) - const ignoredOffset = useSharedValue(0); - // stores the x coordinate of the drag starting point (to ignore dragging on the overlay) - const dragStartPosition = useSharedValue(0); - // between 0 and 1, 0 - closed, 1 - opened - const openingProgress = useDerivedValue(() => { - if (fromLeft) { - return drawerOffset.value / drawerWidth; - } else { - return -drawerOffset.value / drawerWidth; - } - }, [drawerOffset, containerWidth, drawerWidth, fromLeft]); - - // we rely on row and row-reverse flex directions to position the drawer - // properly. Apparently for RTL these are flipped which requires us to use - // the opposite setting for the drawer to appear from left or right - // according to the drawerPosition prop - const reverseContentDirection = I18nManager.isRTL ? fromLeft : !fromLeft; - - // set the drawer to closed position when the props change to prevent it from - // opening or moving on the screen - useEffect(() => { - drawerOffset.value = 0; - drawerSavedOffset.value = 0; - - setDrawerVisible(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [drawerWidth, drawerPosition, drawerType]); - - // measure the container - function handleContainerLayout({ nativeEvent }: LayoutChangeEvent) { - setContainerWidth(nativeEvent.layout.width); +export const DrawerLayout = ({ + ref, + drawerWidth = 200, + drawerPosition = 'left', + drawerType = 'front', + edgeWidth = 20, + minSwipeDistance = 3, + overlayColor = 'rgba(0, 0, 0, 0.7)', + drawerLockMode = 'unlocked', + enableTrackpadTwoFingerGesture = false, + keyboardDismissMode, + statusBarAnimation, + hideStatusBar, + drawerBackgroundColor, + drawerContainerStyle, + contentContainerStyle, + children, + renderNavigationView, + onDrawerClose, + onDrawerOpen, + onDrawerSlide, + onDrawerStateChanged, +}: BetterDrawerLayoutProps & { + ref?: React.RefObject; +}) => { + const animationConfig = { damping: 30, stiffness: 250 }; + + const fromLeft = drawerPosition === 'left'; + const drawerSlide = drawerType !== 'back'; + const containerSlide = drawerType !== 'front'; + + // setting NaN as a starting value allows to tell when the value gets changes + // for the first time + const [containerWidth, setContainerWidth] = useState(Number.NaN); + const [drawerVisible, setDrawerVisible] = useState(false); + + const drawerState = useSharedValue(BetterDrawerState.IDLE); + // between 0 and drawerWidth (drawer on the left) or -drawerWidth and 0 (drawer on the right) + const drawerOffset = useSharedValue(0); + // stores value of the offset at the start of the gesture + const drawerSavedOffset = useSharedValue(0); + // stores the translation that is supposed to be ignored (user tried to + // drag while animation was running) + const ignoredOffset = useSharedValue(0); + // stores the x coordinate of the drag starting point (to ignore dragging on the overlay) + const dragStartPosition = useSharedValue(0); + // between 0 and 1, 0 - closed, 1 - opened + const openingProgress = useDerivedValue(() => { + if (fromLeft) { + return drawerOffset.value / drawerWidth; + } else { + return -drawerOffset.value / drawerWidth; } + }, [drawerOffset, containerWidth, drawerWidth, fromLeft]); + + // we rely on row and row-reverse flex directions to position the drawer + // properly. Apparently for RTL these are flipped which requires us to use + // the opposite setting for the drawer to appear from left or right + // according to the drawerPosition prop + const reverseContentDirection = I18nManager.isRTL ? fromLeft : !fromLeft; + + // set the drawer to closed position when the props change to prevent it from + // opening or moving on the screen + useEffect(() => { + drawerOffset.value = 0; + drawerSavedOffset.value = 0; + + setDrawerVisible(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [drawerWidth, drawerPosition, drawerType]); + + // measure the container + function handleContainerLayout({ nativeEvent }: LayoutChangeEvent) { + setContainerWidth(nativeEvent.layout.width); + } - function onDragStart() { - if (keyboardDismissMode === 'on-drag') { - Keyboard.dismiss(); - } - - // this is required in addition to the similar call below, because the gesture - // doesn't change `drawerVisible` state to prevent re-render during gesture - // so when dragging from closed it wouldn't hide the status bar - if (hideStatusBar) { - StatusBar.setHidden(true, statusBarAnimation ?? 'slide'); - } + function onDragStart() { + if (keyboardDismissMode === 'on-drag') { + Keyboard.dismiss(); } - function setState(newState: BetterDrawerState, willShow: boolean) { - if (hideStatusBar) { - StatusBar.setHidden(willShow, statusBarAnimation ?? 'slide'); - } - - // dispach events - if (drawerState.value !== newState || drawerVisible !== willShow) { - // send state change event only when the state changed or the visibility of the - // drawer (for example when drawer is in SETTLING state after opening and the user - // taps on the overlay the state is still settling, but willShow is now false) - onDrawerStateChanged?.(newState, willShow); - } + // this is required in addition to the similar call below, because the gesture + // doesn't change `drawerVisible` state to prevent re-render during gesture + // so when dragging from closed it wouldn't hide the status bar + if (hideStatusBar) { + StatusBar.setHidden(true, statusBarAnimation ?? 'slide'); + } + } - if (drawerVisible !== willShow) { - setDrawerVisible(willShow); - } + function setState(newState: BetterDrawerState, willShow: boolean) { + if (hideStatusBar) { + StatusBar.setHidden(willShow, statusBarAnimation ?? 'slide'); + } - if (newState === BetterDrawerState.IDLE) { - if (willShow) { - onDrawerOpen?.(); - } else { - onDrawerClose?.(); - } - } + // dispach events + if (drawerState.value !== newState || drawerVisible !== willShow) { + // send state change event only when the state changed or the visibility of the + // drawer (for example when drawer is in SETTLING state after opening and the user + // taps on the overlay the state is still settling, but willShow is now false) + onDrawerStateChanged?.(newState, willShow); + } - drawerState.value = newState; + if (drawerVisible !== willShow) { + setDrawerVisible(willShow); } - function open() { - 'worklet'; - if (fromLeft && drawerOffset.value < drawerWidth) { - // drawer is on the left and is not fully opened - runOnJS(setState)(BetterDrawerState.SETTLING, true); - - drawerOffset.value = withSpring( - drawerWidth, - animationConfig, - (finished) => { - drawerSavedOffset.value = drawerOffset.value; - if (finished) { - // animation cannot be interrupted by a drag, but can be by - // calling close or open (through tap or a controller) - runOnJS(setState)(BetterDrawerState.IDLE, true); - } - } - ); - } else if (!fromLeft && drawerOffset.value > -drawerWidth) { - // drawer is on the right and is not fully opened - runOnJS(setState)(BetterDrawerState.SETTLING, true); - - drawerOffset.value = withSpring( - -drawerWidth, - animationConfig, - (finished) => { - drawerSavedOffset.value = drawerOffset.value; - if (finished) { - // animation cannot be interrupted by a drag, but can be by - // calling close or open (through tap or a controller) - runOnJS(setState)(BetterDrawerState.IDLE, true); - } - } - ); + if (newState === BetterDrawerState.IDLE) { + if (willShow) { + onDrawerOpen?.(); } else { - // drawer is fully opened - runOnJS(setState)(BetterDrawerState.IDLE, true); + onDrawerClose?.(); } } - function close() { - 'worklet'; - if (fromLeft && drawerOffset.value > 0) { - // drawer is on the left and is not fully closed - runOnJS(setState)(BetterDrawerState.SETTLING, false); + drawerState.value = newState; + } - drawerOffset.value = withSpring(0, animationConfig, (finished) => { + function open() { + 'worklet'; + if (fromLeft && drawerOffset.value < drawerWidth) { + // drawer is on the left and is not fully opened + runOnJS(setState)(BetterDrawerState.SETTLING, true); + + drawerOffset.value = withSpring( + drawerWidth, + animationConfig, + (finished) => { drawerSavedOffset.value = drawerOffset.value; if (finished) { // animation cannot be interrupted by a drag, but can be by // calling close or open (through tap or a controller) - runOnJS(setState)(BetterDrawerState.IDLE, false); + runOnJS(setState)(BetterDrawerState.IDLE, true); } - }); - } else if (!fromLeft && drawerOffset.value < 0) { - // drawer is on the right and is not fully closed - runOnJS(setState)(BetterDrawerState.SETTLING, false); - - drawerOffset.value = withSpring(0, animationConfig, (finished) => { + } + ); + } else if (!fromLeft && drawerOffset.value > -drawerWidth) { + // drawer is on the right and is not fully opened + runOnJS(setState)(BetterDrawerState.SETTLING, true); + + drawerOffset.value = withSpring( + -drawerWidth, + animationConfig, + (finished) => { drawerSavedOffset.value = drawerOffset.value; if (finished) { // animation cannot be interrupted by a drag, but can be by // calling close or open (through tap or a controller) - runOnJS(setState)(BetterDrawerState.IDLE, false); + runOnJS(setState)(BetterDrawerState.IDLE, true); } - }); - } else { - // drawer is fully closed - runOnJS(setState)(BetterDrawerState.IDLE, false); - } + } + ); + } else { + // drawer is fully opened + runOnJS(setState)(BetterDrawerState.IDLE, true); } + } - // gestureOrientation is 1 if the expected gesture is from left to right and - // -1 otherwise e.g. when drawer is on the left and is closed we expect left - // to right gesture, thus orientation will be 1. - const gestureOrientation = (fromLeft ? 1 : -1) * (drawerVisible ? -1 : 1); - - // When drawer is closed we want the hitSlop to be horizontally shorter than - // the container size by the value of SLOP. This will make it only activate - // when gesture happens not further than SLOP away from the edge - const hitSlop = fromLeft - ? { left: 0, width: drawerVisible ? undefined : edgeWidth } - : { right: 0, width: drawerVisible ? undefined : edgeWidth }; - - // *** THIS IS THE LARGE COMMENT ABOVE *** - // - // While closing the drawer when user starts gesture outside of its area (in greyed - // out part of the window), we want the drawer to follow only once finger reaches the - // edge of the drawer. - // E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by - // dots. The touch gesture starts at '*' and moves left, touch path is indicated by - // an arrow pointing left - // 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+ - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..| - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| - // +---------------+ +---------------+ +---------------+ +---------------+ - // - // For the above to work properly we define animated value that will keep - // start position of the gesture. Then we use that value to calculate how - // much we need to subtract from the dragX. If the gesture started on the - // greyed out area we take the distance from the edge of the drawer to the - // start position. Otherwise we don't subtract at all and the drawer be - // pulled back as soon as you start the pan. - // - // This is used only when drawerType is "front" - // - - const pan = Gesture.Pan(); - pan.failOffsetY([-15, 15]); - pan.hitSlop(hitSlop); - pan.activeOffsetX(gestureOrientation * minSwipeDistance); - pan.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture); - pan.enabled( - drawerLockMode !== 'locked-closed' && drawerLockMode !== 'locked-open' - ); - pan.onStart((event) => { - 'worklet'; - ignoredOffset.value = 0; - dragStartPosition.value = event.x; - }); - pan.onUpdate((event) => { - 'worklet'; - if (drawerState.value === BetterDrawerState.IDLE) { - runOnJS(setState)(BetterDrawerState.DRAGGING, drawerVisible); - runOnJS(onDragStart)(); - } - - if (drawerState.value === BetterDrawerState.DRAGGING) { - let newOffset = - drawerSavedOffset.value + event.translationX - ignoredOffset.value; - - if (fromLeft) { - // refer to the large comment above - if ( - drawerType === 'front' && - event.translationX < 0 && - drawerOffset.value > 0 - ) { - newOffset += dragStartPosition.value - drawerWidth; - } + function close() { + 'worklet'; + if (fromLeft && drawerOffset.value > 0) { + // drawer is on the left and is not fully closed + runOnJS(setState)(BetterDrawerState.SETTLING, false); - // clamp the offset so the drawer does not move away from the edge - newOffset = Math.max(0, Math.min(drawerWidth, newOffset)); - } else { - // refer to the large comment above - if ( - drawerType === 'front' && - event.translationX > 0 && - drawerOffset.value < 0 - ) { - newOffset += - dragStartPosition.value - (containerWidth - drawerWidth); - } + drawerOffset.value = withSpring(0, animationConfig, (finished) => { + drawerSavedOffset.value = drawerOffset.value; + if (finished) { + // animation cannot be interrupted by a drag, but can be by + // calling close or open (through tap or a controller) + runOnJS(setState)(BetterDrawerState.IDLE, false); + } + }); + } else if (!fromLeft && drawerOffset.value < 0) { + // drawer is on the right and is not fully closed + runOnJS(setState)(BetterDrawerState.SETTLING, false); - // clamp the offset so the drawer does not move away from the edge - newOffset = Math.max(-drawerWidth, Math.min(0, newOffset)); + drawerOffset.value = withSpring(0, animationConfig, (finished) => { + drawerSavedOffset.value = drawerOffset.value; + if (finished) { + // animation cannot be interrupted by a drag, but can be by + // calling close or open (through tap or a controller) + runOnJS(setState)(BetterDrawerState.IDLE, false); } + }); + } else { + // drawer is fully closed + runOnJS(setState)(BetterDrawerState.IDLE, false); + } + } + + // gestureOrientation is 1 if the expected gesture is from left to right and + // -1 otherwise e.g. when drawer is on the left and is closed we expect left + // to right gesture, thus orientation will be 1. + const gestureOrientation = (fromLeft ? 1 : -1) * (drawerVisible ? -1 : 1); + + // When drawer is closed we want the hitSlop to be horizontally shorter than + // the container size by the value of SLOP. This will make it only activate + // when gesture happens not further than SLOP away from the edge + const hitSlop = fromLeft + ? { left: 0, width: drawerVisible ? undefined : edgeWidth } + : { right: 0, width: drawerVisible ? undefined : edgeWidth }; + + // *** THIS IS THE LARGE COMMENT ABOVE *** + // + // While closing the drawer when user starts gesture outside of its area (in greyed + // out part of the window), we want the drawer to follow only once finger reaches the + // edge of the drawer. + // E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by + // dots. The touch gesture starts at '*' and moves left, touch path is indicated by + // an arrow pointing left + // 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+ + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..| + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........| + // +---------------+ +---------------+ +---------------+ +---------------+ + // + // For the above to work properly we define animated value that will keep + // start position of the gesture. Then we use that value to calculate how + // much we need to subtract from the dragX. If the gesture started on the + // greyed out area we take the distance from the edge of the drawer to the + // start position. Otherwise we don't subtract at all and the drawer be + // pulled back as soon as you start the pan. + // + // This is used only when drawerType is "front" + // + + const pan = Gesture.Pan(); + pan.failOffsetY([-15, 15]); + pan.hitSlop(hitSlop); + pan.activeOffsetX(gestureOrientation * minSwipeDistance); + pan.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture); + pan.enabled( + drawerLockMode !== 'locked-closed' && drawerLockMode !== 'locked-open' + ); + pan.onStart((event) => { + 'worklet'; + ignoredOffset.value = 0; + dragStartPosition.value = event.x; + }); + pan.onUpdate((event) => { + 'worklet'; + if (drawerState.value === BetterDrawerState.IDLE) { + runOnJS(setState)(BetterDrawerState.DRAGGING, drawerVisible); + runOnJS(onDragStart)(); + } - drawerOffset.value = newOffset; + if (drawerState.value === BetterDrawerState.DRAGGING) { + let newOffset = + drawerSavedOffset.value + event.translationX - ignoredOffset.value; - // send event if there is a listener - if (onDrawerSlide !== undefined) { - runOnJS(onDrawerSlide)(openingProgress.value); + if (fromLeft) { + // refer to the large comment above + if ( + drawerType === 'front' && + event.translationX < 0 && + drawerOffset.value > 0 + ) { + newOffset += dragStartPosition.value - drawerWidth; } - } else { - // drawerState is SETTLING, save the translation to ignore it later - ignoredOffset.value = event.translationX; - } - }); - pan.onEnd((_event) => { - 'worklet'; - if (drawerState.value === BetterDrawerState.DRAGGING) { - // update offsets and animations only when the drag was not ignored - drawerSavedOffset.value = drawerOffset.value; - // if the drawer was dragged more than half of its width open it, - // otherwise close it - if (fromLeft) { - if (drawerOffset.value > drawerWidth / 2) { - open(); - } else { - close(); - } - } else { - if (drawerOffset.value < -drawerWidth / 2) { - open(); - } else { - close(); - } + // clamp the offset so the drawer does not move away from the edge + newOffset = Math.max(0, Math.min(drawerWidth, newOffset)); + } else { + // refer to the large comment above + if ( + drawerType === 'front' && + event.translationX > 0 && + drawerOffset.value < 0 + ) { + newOffset += dragStartPosition.value - (containerWidth - drawerWidth); } + + // clamp the offset so the drawer does not move away from the edge + newOffset = Math.max(-drawerWidth, Math.min(0, newOffset)); } - }); - const dynamicDrawerStyles = { - backgroundColor: drawerBackgroundColor, - width: drawerWidth, - }; + drawerOffset.value = newOffset; - const drawerStyle = useAnimatedStyle(() => { - let translateX = 0; + // send event if there is a listener + if (onDrawerSlide !== undefined) { + runOnJS(onDrawerSlide)(openingProgress.value); + } + } else { + // drawerState is SETTLING, save the translation to ignore it later + ignoredOffset.value = event.translationX; + } + }); + pan.onEnd((_event) => { + 'worklet'; + if (drawerState.value === BetterDrawerState.DRAGGING) { + // update offsets and animations only when the drag was not ignored + drawerSavedOffset.value = drawerOffset.value; - if (drawerSlide) { - // drawer is supposed to be moved with the gesture (in this case - // drawer is anchored to be off the screen when not opened) - if (fromLeft) { - translateX = -drawerWidth; + // if the drawer was dragged more than half of its width open it, + // otherwise close it + if (fromLeft) { + if (drawerOffset.value > drawerWidth / 2) { + open(); } else { - translateX = containerWidth; + close(); } - translateX += drawerOffset.value; } else { - // drawer is stationary (in this case drawer is below the content - // so it's anchored left edge to left edge or right to right) - if (fromLeft) { - translateX = 0; + if (drawerOffset.value < -drawerWidth / 2) { + open(); } else { - translateX = containerWidth - drawerWidth; + close(); } } + } + }); - // if the drawer is not visible move it off the screen to prevent it - // from intercepting touch events on Android - if (drawerOffset.value === 0) { - translateX = 10000; + const dynamicDrawerStyles = { + backgroundColor: drawerBackgroundColor, + width: drawerWidth, + }; + + const drawerStyle = useAnimatedStyle(() => { + let translateX = 0; + + if (drawerSlide) { + // drawer is supposed to be moved with the gesture (in this case + // drawer is anchored to be off the screen when not opened) + if (fromLeft) { + translateX = -drawerWidth; + } else { + translateX = containerWidth; + } + translateX += drawerOffset.value; + } else { + // drawer is stationary (in this case drawer is below the content + // so it's anchored left edge to left edge or right to right) + if (fromLeft) { + translateX = 0; + } else { + translateX = containerWidth - drawerWidth; } + } - return { - flexDirection: reverseContentDirection ? 'row-reverse' : 'row', - transform: [{ translateX }], - }; - }); + // if the drawer is not visible move it off the screen to prevent it + // from intercepting touch events on Android + if (drawerOffset.value === 0) { + translateX = 10000; + } - const containerStyle = useAnimatedStyle(() => { - let translateX = 0; + return { + flexDirection: reverseContentDirection ? 'row-reverse' : 'row', + transform: [{ translateX }], + }; + }); - if (containerSlide) { - // the container should be moved with the gesture - translateX = drawerOffset.value; - } + const containerStyle = useAnimatedStyle(() => { + let translateX = 0; - return { - transform: [{ translateX }], - }; - }); + if (containerSlide) { + // the container should be moved with the gesture + translateX = drawerOffset.value; + } - if (ref !== null) { - // ref is set, create a controller and pass it - const controller: DrawerLayoutController = { - open: () => { - open(); - }, - close: () => { - close(); - }, - }; + return { + transform: [{ translateX }], + }; + }); - if (typeof ref === 'function') { - ref(controller); - } else { - ref.current = controller; - } + if (ref !== null) { + // ref is set, create a controller and pass it + const controller: DrawerLayoutController = { + open: () => { + open(); + }, + close: () => { + close(); + }, + }; + + if (typeof ref === 'function') { + // @ts-ignore ref is function + ref(controller); + } else { + // @ts-ignore ref is not undefined + ref.current = controller; } + } - return ( - - - - {children} - - - - - {renderNavigationView(openingProgress)} - + return ( + + + + {children} + - - ); - } -); + + + {renderNavigationView(openingProgress)} + + + + ); +}; const styles = StyleSheet.create({ drawerContainer: { diff --git a/apps/CommonApp/src/new_api/bottom_sheet/index.tsx b/apps/CommonApp/src/new_api/bottom_sheet/index.tsx index 1218963588..4e98d6d103 100644 --- a/apps/CommonApp/src/new_api/bottom_sheet/index.tsx +++ b/apps/CommonApp/src/new_api/bottom_sheet/index.tsx @@ -17,7 +17,7 @@ import Animated, { useSharedValue, withSpring, } from 'react-native-reanimated'; -import { LoremIpsum } from '../../../src/common'; +import { LoremIpsum } from '../../common'; const HEADER_HEIGTH = 50; const windowHeight = Dimensions.get('window').height; diff --git a/apps/CommonApp/src/new_api/camera/AnimatedCameraView.tsx b/apps/CommonApp/src/new_api/camera/AnimatedCameraView.tsx index 4e0450c834..df2b06ae98 100644 --- a/apps/CommonApp/src/new_api/camera/AnimatedCameraView.tsx +++ b/apps/CommonApp/src/new_api/camera/AnimatedCameraView.tsx @@ -1,55 +1,55 @@ import React from 'react'; -import { CameraView, useCameraPermissions } from 'expo-camera'; +import { CameraView, useCameraPermissions, CameraViewProps } from 'expo-camera'; import { Button, StyleSheet, Text, View } from 'react-native'; import Animated, { - SharedValue, + AnimateProps, useAnimatedProps, } from 'react-native-reanimated'; const AnimatedCameraView = Animated.createAnimatedComponent(CameraView); -interface CameraProps { - facing: 'front' | 'back'; - zoom: SharedValue; +interface CameraProps extends Omit { + ref?: React.RefObject< + React.Component, any, any> + >; + zoom: Animated.SharedValue; } -const Camera = React.forwardRef( - (props: CameraProps, ref: React.Ref) => { - const [permission, requestPermission] = useCameraPermissions(); +const Camera = ({ ref, ...props }: CameraProps) => { + const [permission, requestPermission] = useCameraPermissions(); - const animatedProps = useAnimatedProps(() => { - return { - zoom: props.zoom.value - 1, - }; - }); + const animatedProps = useAnimatedProps(() => { + return { + zoom: props.zoom.value - 1, + }; + }); - if (!permission) { - return ; - } - - if (!permission.granted) { - return ( - - - We need your permission to show the camera - -