diff --git a/apps/common-app/App.tsx b/apps/common-app/App.tsx index 4edb8aad23..3b9e9b0607 100644 --- a/apps/common-app/App.tsx +++ b/apps/common-app/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Text, View, StyleSheet, Platform, Dimensions } from 'react-native'; import { createStackNavigator, @@ -13,233 +13,17 @@ import { } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import AsyncStorage from '@react-native-async-storage/async-storage'; -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 { - TouchablesIndex, - TouchableExample, -} from './src/release_tests/touchables'; -import NestedFling from './src/release_tests/nestedFling'; -import MouseButtons from './src/release_tests/mouseButtons'; -import ContextMenu from './src/release_tests/contextMenu'; -import NestedTouchables from './src/release_tests/nestedTouchables'; -import NestedPressables from './src/release_tests/nestedPressables'; -import NestedButtons from './src/release_tests/nestedButtons'; -import PointerType from './src/release_tests/pointerType'; -import NestedGestureHandlerRootViewWithModal from './src/release_tests/nestedGHRootViewWithModal'; -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 PanAndScroll from './src/recipes/panAndScroll'; - -import { BottomSheet } from './src/showcase/bottomSheet'; -import ChatHeads from './src/showcase/chatHeads'; -import Draggable from './src/basic/draggable'; -import MultiTap from './src/basic/multitap'; -import BouncingBox from './src/basic/bouncing'; -import PanResponder from './src/basic/panResponder'; -import PagerAndDrawer from './src/basic/pagerAndDrawer'; -import ForceTouch from './src/basic/forcetouch'; -import Fling from './src/basic/fling'; -import WebStylesResetExample from './src/release_tests/webStylesReset'; -import StylusData from './src/release_tests/StylusData'; -import ReanimatedDrawerLayout from './src/release_tests/reanimatedDrawerLayout'; - -import Camera from './src/new_api/camera'; -import Transformations from './src/new_api/transformations'; -import Overlap from './src/new_api/overlap'; -import Calculator from './src/new_api/calculator'; -import BottomSheetNewApi from './src/new_api/bottom_sheet'; -import ChatHeadsNewApi from './src/new_api/chat_heads'; -import DragNDrop from './src/new_api/drag_n_drop'; -import ManualGestures from './src/new_api/manualGestures/index'; -import Hover from './src/new_api/hover'; -import HoverableIcons from './src/new_api/hoverable_icons'; -import VelocityTest from './src/new_api/velocityTest'; -import Swipeable from './src/new_api/swipeable'; -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 MacosDraggable from './src/simple/draggable'; -import Tap from './src/simple/tap'; -import LongPressExample from './src/simple/longPress'; -import ManualExample from './src/simple/manual'; -import SimpleFling from './src/simple/fling'; - -import FlatListExample from './src/components/flatlist'; -import ScrollViewExample from './src/components/scrollview'; -import ButtonsExample from './src/components/buttons'; -import SwitchTextInputExample from './src/components/switchAndInput'; - import { Icon } from '@swmansion/icons'; -interface Example { - name: string; - component: React.ComponentType; - unsupportedPlatforms?: Set; -} -interface ExamplesSection { - sectionTitle: string; - data: Example[]; -} - -const EXAMPLES: ExamplesSection[] = [ - { - sectionTitle: 'Empty', - 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: 'Manual gestures', - component: ManualGestures, - }, - ], - }, - { - sectionTitle: 'Components', - data: [ - { name: 'FlatList example', component: FlatListExample }, - { name: 'ScrollView example', component: ScrollViewExample }, - { name: 'Buttons example', component: ButtonsExample }, - { name: 'Switch & TextInput', component: SwitchTextInputExample }, - ], - }, - { - sectionTitle: 'Basic examples', - data: [ - { name: 'Draggable', component: Draggable }, - { name: 'Multitap', component: MultiTap }, - { name: 'Bouncing box', component: BouncingBox }, - { name: 'Pan responder', component: PanResponder }, - { - name: 'Pager & drawer', - component: PagerAndDrawer, - unsupportedPlatforms: new Set(['web', 'ios', 'macos']), - }, - { - name: 'Force touch', - component: ForceTouch, - unsupportedPlatforms: new Set(['web', 'android', 'macos']), - }, - { name: 'Fling', component: Fling }, - ], - }, - { - sectionTitle: 'Recipes', - data: [ - { name: 'Pinch & rotate', component: PinchableBox }, - { name: 'Pan & scroll', component: PanAndScroll }, - ], - }, - { - sectionTitle: 'Showcase', - data: [ - { name: 'Bottom sheet', component: BottomSheet }, - { name: 'Chat heads', component: ChatHeads }, - ], - }, - { - sectionTitle: 'Release tests', - data: [ - { - name: 'Views overflowing parents - issue #1532', - component: OverflowParent, - }, - { - name: 'Modals with nested GHRootViews - issue #139', - component: NestedGestureHandlerRootViewWithModal, - }, - { - name: 'Nested Touchables - issue #784', - component: NestedTouchables as React.ComponentType, - }, - { - name: 'Nested Pressables - issue #2980', - component: NestedPressables as React.ComponentType, - }, - { - name: 'Nested buttons (sound & ripple)', - component: NestedButtons, - unsupportedPlatforms: new Set(['web', 'ios', 'macos']), - }, - { - name: 'Svg integration with Gesture Handler', - component: SvgCompatibility, - }, - { name: 'Double pinch & rotate', component: DoublePinchRotate }, - { name: 'Double draggable', component: DoubleDraggable }, - { 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: 'ContextMenu', - component: ContextMenu, - unsupportedPlatforms: new Set(['android', 'ios', 'macos']), - }, - { name: 'PointerType', component: PointerType }, - { name: 'Reanimated Drawer Layout', component: ReanimatedDrawerLayout }, - { 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: 'Two finger Pan', - component: TwoFingerPan, - unsupportedPlatforms: new Set(['android', 'macos']), - }, - { - name: 'Nested Text', - component: NestedText, - unsupportedPlatforms: new Set(['macos']), - }, - ], - }, - { - 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 }, - ], - }, -]; - +import { OLD_EXAMPLES } from './src/legacy'; +import { NEW_EXAMPLES } from './src/new_api'; +import { TouchableExample } from './src/legacy/release_tests/touchables'; +import { ListWithHeader } from './src/ListWithHeader'; const OPEN_LAST_EXAMPLE_KEY = 'openLastExample'; +const SHOW_LEGACY_EXAMPLES_KEY = 'showLegacyExamples'; const LAST_EXAMPLE_KEY = 'lastExample'; type RootStackParamList = { @@ -250,8 +34,8 @@ type RootStackParamList = { }; const Stack = createStackNavigator(); - export default function App() { + const [showLegacyVersion, setShowLegacyVersion] = useState(false); return ( @@ -273,129 +57,168 @@ export default function App() { options={{ headerShown: false }} component={MainScreen} /> - {EXAMPLES.flatMap(({ data }) => data).flatMap( - ({ name, component }) => ( + {(showLegacyVersion ? OLD_EXAMPLES : NEW_EXAMPLES) + .flatMap(({ data }) => data) + .flatMap(({ name, component }) => ( component} options={{ title: name }} /> - ) - )} + ))} ); -} -function navigate( - navigation: StackNavigationProp, - dest: string -) { - AsyncStorage.setItem(LAST_EXAMPLE_KEY, dest); - navigation.navigate(dest); -} + function navigate( + navigation: StackNavigationProp, + dest: string + ) { + AsyncStorage.setItem(LAST_EXAMPLE_KEY, dest); + navigation.navigate(dest); + } -function MainScreen({ navigation }: StackScreenProps) { - const insets = useSafeAreaInsets(); + function MainScreen({ navigation }: StackScreenProps) { + const insets = useSafeAreaInsets(); - useEffect(() => { - void AsyncStorage.multiGet([OPEN_LAST_EXAMPLE_KEY, LAST_EXAMPLE_KEY]).then( - ([openLastExample, lastExample]) => { - if (openLastExample[1] === 'true' && lastExample[1]) { + useEffect(() => { + void AsyncStorage.multiGet([ + OPEN_LAST_EXAMPLE_KEY, + LAST_EXAMPLE_KEY, + SHOW_LEGACY_EXAMPLES_KEY, + ]).then(([openLastExample, lastExample, showLegacyExamples]) => { + const showLegacy = showLegacyExamples[1] === 'true'; + if (showLegacyExamples[1] != null) { + setShowLegacyVersion(showLegacy); + } + if ( + showLegacy === showLegacyVersion && + openLastExample[1] === 'true' && + lastExample[1] + ) { navigate(navigation, lastExample[1]); } return; - } - ); - // we only want to run this effect once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }); + // we only want to run this effect once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return ( - - example.name} - ListHeaderComponent={OpenLastExampleSetting} - contentContainerStyle={{ - paddingBottom: insets.bottom, - paddingTop: insets.top, - }} - renderItem={({ item }) => ( - navigate(navigation, name)} - enabled={!item.unsupportedPlatforms?.has(Platform.OS)} - /> - )} - renderSectionHeader={({ section: { sectionTitle } }) => ( - {sectionTitle} - )} - ItemSeparatorComponent={() => } - /> - - ); -} - -function OpenLastExampleSetting() { - const [openLastExample, setOpenLastExample] = React.useState(false); + return ( + + example.name} + ListHeaderComponent={Settings} + contentContainerStyle={{ + paddingBottom: insets.bottom, + paddingTop: insets.top, + }} + renderItem={({ item }) => ( + navigate(navigation, name)} + enabled={!item.unsupportedPlatforms?.has(Platform.OS)} + /> + )} + renderSectionHeader={({ section: { sectionTitle } }) => ( + {sectionTitle} + )} + ItemSeparatorComponent={() => } + /> + + ); + } - useEffect(() => { - void AsyncStorage.getItem(OPEN_LAST_EXAMPLE_KEY).then((value) => { - setOpenLastExample(value === 'true'); - return; - }); - }, []); + function Settings() { + const [openLastExample, setOpenLastExample] = useState(false); - function updateSetting(value: boolean) { - AsyncStorage.setItem(OPEN_LAST_EXAMPLE_KEY, value.toString()); - setOpenLastExample(value); - } + useEffect(() => { + void AsyncStorage.getItem(OPEN_LAST_EXAMPLE_KEY).then((value) => { + setOpenLastExample(value === 'true'); + return; + }); + }, []); - return ( - { - updateSetting(!openLastExample); - }}> - - Open last example on launch - { - updateSetting(!openLastExample); - }} - /> + function updateKeepSetting(value: boolean) { + AsyncStorage.setItem(OPEN_LAST_EXAMPLE_KEY, value.toString()); + setOpenLastExample(value); + } + function updateVersionSetting(value: boolean) { + AsyncStorage.setItem(SHOW_LEGACY_EXAMPLES_KEY, value.toString()); + AsyncStorage.removeItem(LAST_EXAMPLE_KEY); + setShowLegacyVersion(value); + } + return ( + + { + updateKeepSetting(!openLastExample); + }}> + + + Open last example on launch + + { + updateKeepSetting(!openLastExample); + }} + /> + + + { + updateVersionSetting(!showLegacyVersion); + }}> + + + Show legacy version examples + + { + updateVersionSetting(!showLegacyVersion); + }} + /> + + - - ); -} + ); + } -interface MainScreenItemProps { - name: string; - onPressItem: (name: string) => void; - enabled: boolean; -} + interface MainScreenItemProps { + name: string; + onPressItem: (name: string) => void; + enabled: boolean; + } -function MainScreenItem({ name, onPressItem, enabled }: MainScreenItemProps) { - return ( - onPressItem(name)}> - {name} - {Platform.OS !== 'macos' && enabled && ( - - )} - - ); + function MainScreenItem({ name, onPressItem, enabled }: MainScreenItemProps) { + return ( + onPressItem(name)}> + {name} + {Platform.OS !== 'macos' && enabled && ( + + )} + + ); + } } const styles = StyleSheet.create({ @@ -421,32 +244,32 @@ const styles = StyleSheet.create({ text: { color: 'black', }, + aligned: { + textAlign: 'center', + alignSelf: 'center', + }, list: {}, separator: { height: 2, }, button: { flex: 1, - height: 50, - paddingVertical: 10, + height: 48, + paddingVertical: 12, paddingHorizontal: 20, flexDirection: 'row', backgroundColor: '#fff', alignItems: 'center', justifyContent: 'space-between', }, - buttonContent: { + settingsButton: { flex: 1, + paddingHorizontal: 20, flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - autoOpenSetting: { - margin: 16, - borderRadius: 16, + borderRadius: 10, backgroundColor: '#eef0ff', - paddingHorizontal: 16, - justifyContent: 'space-between', + padding: 8, + justifyContent: 'center', elevation: 8, ...(Platform.OS !== 'macos' ? { @@ -457,8 +280,20 @@ const styles = StyleSheet.create({ } : {}), }, + settingsButtonContent: { + flex: 1, + width: '100%', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, unavailableExample: { backgroundColor: 'rgb(220, 220, 220)', opacity: 0.3, }, + settings: { + flexDirection: 'row', + gap: 24, + margin: 24, + }, }); diff --git a/apps/common-app/src/common.tsx b/apps/common-app/src/common.tsx index 0a7436e840..46aa1aed87 100644 --- a/apps/common-app/src/common.tsx +++ b/apps/common-app/src/common.tsx @@ -1,10 +1,102 @@ -import React from 'react'; -import { Text, StyleSheet, ViewStyle, StyleProp } from 'react-native'; +import React, { + RefObject, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; +import { + Text, + StyleSheet, + ViewStyle, + StyleProp, + View, + Platform, +} from 'react-native'; +import Animated, { + Easing, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +export interface Example { + name: string; + component: React.ComponentType; + unsupportedPlatforms?: Set; +} + +export interface ExamplesSection { + sectionTitle: string; + data: Example[]; +} + +/* eslint-disable react-native/no-unused-styles */ +export const commonStyles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 150, + width: 150, + borderRadius: 20, + marginBottom: 30, + }, + ball: { + height: 120, + width: 120, + borderRadius: 60, + marginBottom: 100, + // @ts-expect-error `grab` is correct value for `cursor` property + cursor: 'grab', + }, + instructions: { + marginTop: 8, + textAlign: 'center', + paddingHorizontal: 16, + }, + subcontainer: { + flex: 1, + width: '100%', + gap: 8, + alignItems: 'center', + justifyContent: 'center', + borderBottomWidth: 1, + }, + header: { + fontSize: 24, + marginVertical: 10, + justifyContent: 'center', + textAlign: 'center', + }, +}); +/* eslint-enable react-native/no-unused-styles */ const styles = StyleSheet.create({ lipsum: { padding: 10, }, + info_container: { + padding: 16, + backgroundColor: '#F5F7FA', + borderRadius: 12, + marginVertical: 12, + borderWidth: 1, + borderColor: '#E2E8F0', + }, + info_body: { + fontSize: 15, + lineHeight: 22, + color: '#4A5568', + }, + feedback: { + marginTop: 20, + fontSize: 16, + fontWeight: '600', + }, }); type Props = { @@ -25,9 +117,99 @@ export class LoremIpsum extends React.Component { } } +interface InfoSectionProps { + description: string; +} + +export function InfoSection({ description }: InfoSectionProps) { + return ( + + {description} + + ); +} + +type FeedbackProps = { + duration?: number; + ref?: RefObject; +}; + +export type FeedbackHandle = { + showMessage: (message: string) => void; +}; + +export const Feedback = ({ duration = 1000, ref }: FeedbackProps) => { + const [text, setText] = useState('Feedback'); + const timerRef = useRef(null); + + const opacity = useSharedValue(0); + + const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); + + const displayMessage = useCallback( + (message: string) => { + console.log(message); + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + setText(message); + opacity.value = withTiming(1, { + duration: 100, + easing: Easing.out(Easing.ease), + }); + + timerRef.current = setTimeout(() => { + opacity.value = withTiming(0, { + duration: 500, + easing: Easing.out(Easing.ease), + }); + timerRef.current = null; + }, duration) as unknown as number; + }, + [duration, opacity] + ); + + useImperativeHandle(ref, () => ({ + showMessage: (message: string) => { + displayMessage(message); + }, + })); + + useEffect(() => { + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, []); + + if (!text) { + return null; + } + + return ( + + {text} + + ); +}; + export const COLORS = { offWhite: '#f8f9ff', headerSeparator: '#eef0ff', + PURPLE: '#b58df1', + NAVY: '#001A72', + RED: '#A41623', + YELLOW: '#F2AF29', + GREEN: '#0F956F', + GRAY: '#ADB1C2', + KINDA_RED: '#FFB2AD', + KINDA_YELLOW: '#FFF096', + KINDA_GREEN: '#C4E7DB', + KINDA_BLUE: '#A0D5EF', }; const LOREM_IPSUM = ` diff --git a/apps/common-app/src/new_api/camera/AnimatedCameraView.macos.tsx b/apps/common-app/src/common_assets/AnimatedCameraView/AnimatedCameraView.macos.tsx similarity index 100% rename from apps/common-app/src/new_api/camera/AnimatedCameraView.macos.tsx rename to apps/common-app/src/common_assets/AnimatedCameraView/AnimatedCameraView.macos.tsx diff --git a/apps/common-app/src/new_api/camera/AnimatedCameraView.tsx b/apps/common-app/src/common_assets/AnimatedCameraView/AnimatedCameraView.tsx similarity index 100% rename from apps/common-app/src/new_api/camera/AnimatedCameraView.tsx rename to apps/common-app/src/common_assets/AnimatedCameraView/AnimatedCameraView.tsx diff --git a/apps/common-app/src/new_api/hoverable_icons/freeze.png b/apps/common-app/src/common_assets/hoverable_icons/freeze.png similarity index 100% rename from apps/common-app/src/new_api/hoverable_icons/freeze.png rename to apps/common-app/src/common_assets/hoverable_icons/freeze.png diff --git a/apps/common-app/src/new_api/hoverable_icons/gh.png b/apps/common-app/src/common_assets/hoverable_icons/gh.png similarity index 100% rename from apps/common-app/src/new_api/hoverable_icons/gh.png rename to apps/common-app/src/common_assets/hoverable_icons/gh.png diff --git a/apps/common-app/src/new_api/hoverable_icons/rea.png b/apps/common-app/src/common_assets/hoverable_icons/rea.png similarity index 100% rename from apps/common-app/src/new_api/hoverable_icons/rea.png rename to apps/common-app/src/common_assets/hoverable_icons/rea.png diff --git a/apps/common-app/src/new_api/hoverable_icons/screens.png b/apps/common-app/src/common_assets/hoverable_icons/screens.png similarity index 100% rename from apps/common-app/src/new_api/hoverable_icons/screens.png rename to apps/common-app/src/common_assets/hoverable_icons/screens.png diff --git a/apps/common-app/src/new_api/hoverable_icons/svg.png b/apps/common-app/src/common_assets/hoverable_icons/svg.png similarity index 100% rename from apps/common-app/src/new_api/hoverable_icons/svg.png rename to apps/common-app/src/common_assets/hoverable_icons/svg.png diff --git a/apps/common-app/src/components/buttons/index.tsx b/apps/common-app/src/components/buttons/index.tsx deleted file mode 100644 index d64cfc4cab..0000000000 --- a/apps/common-app/src/components/buttons/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { StyleSheet, Text } from 'react-native'; -import { - BaseButton, - BorderlessButton, - GestureHandlerRootView, - RectButton, -} from 'react-native-gesture-handler'; - -type ButtonWrapperProps = { - ButtonComponent: - | typeof BaseButton - | typeof RectButton - | typeof BorderlessButton; - - color: string; -}; - -function ButtonWrapper({ ButtonComponent, color }: ButtonWrapperProps) { - return ( - console.log(`[${ButtonComponent.name}] onPress`)} - onLongPress={() => { - console.log(`[${ButtonComponent.name}] onLongPress`); - }}> - {ButtonComponent.name} - - ); -} - -export default function ButtonsExample() { - return ( - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'space-around', - }, - button: { - width: 200, - height: 50, - borderRadius: 15, - - display: 'flex', - alignItems: 'center', - justifyContent: 'space-around', - }, - - buttonText: { - color: 'white', - fontSize: 16, - }, -}); diff --git a/apps/common-app/src/empty/EmptyExample.tsx b/apps/common-app/src/empty/index.tsx similarity index 100% rename from apps/common-app/src/empty/EmptyExample.tsx rename to apps/common-app/src/empty/index.tsx diff --git a/apps/common-app/src/basic/bouncing/index.tsx b/apps/common-app/src/legacy/basic/bouncing/index.tsx similarity index 98% rename from apps/common-app/src/basic/bouncing/index.tsx rename to apps/common-app/src/legacy/basic/bouncing/index.tsx index 99a8668518..406fe357e0 100644 --- a/apps/common-app/src/basic/bouncing/index.tsx +++ b/apps/common-app/src/legacy/basic/bouncing/index.tsx @@ -11,7 +11,7 @@ import { RotationGestureHandlerStateChangeEvent, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { USE_NATIVE_DRIVER } from '../../../config'; class Snappable extends Component>> { private onGestureEvent?: (event: PanGestureHandlerGestureEvent) => void; diff --git a/apps/common-app/src/basic/draggable/index.tsx b/apps/common-app/src/legacy/basic/draggable/index.tsx similarity index 96% rename from apps/common-app/src/basic/draggable/index.tsx rename to apps/common-app/src/legacy/basic/draggable/index.tsx index 6a2b45f96a..653f55e460 100644 --- a/apps/common-app/src/basic/draggable/index.tsx +++ b/apps/common-app/src/legacy/basic/draggable/index.tsx @@ -9,8 +9,8 @@ import { ScrollView, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; -import { LoremIpsum } from '../../common'; +import { USE_NATIVE_DRIVER } from '../../../config'; +import { LoremIpsum } from '../../../common'; type DraggableBoxProps = { minDist?: number; diff --git a/apps/common-app/src/basic/fling/index.tsx b/apps/common-app/src/legacy/basic/fling/index.tsx similarity index 100% rename from apps/common-app/src/basic/fling/index.tsx rename to apps/common-app/src/legacy/basic/fling/index.tsx diff --git a/apps/common-app/src/basic/forcetouch/index.tsx b/apps/common-app/src/legacy/basic/forcetouch/index.tsx similarity index 96% rename from apps/common-app/src/basic/forcetouch/index.tsx rename to apps/common-app/src/legacy/basic/forcetouch/index.tsx index 7a86ea4898..f7b2542887 100644 --- a/apps/common-app/src/basic/forcetouch/index.tsx +++ b/apps/common-app/src/legacy/basic/forcetouch/index.tsx @@ -7,7 +7,7 @@ import { ForceTouchGestureHandlerStateChangeEvent, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { USE_NATIVE_DRIVER } from '../../../config'; export default class Example extends Component { private force = new Animated.Value(0); diff --git a/apps/common-app/src/basic/multitap/index.tsx b/apps/common-app/src/legacy/basic/multitap/index.tsx similarity index 98% rename from apps/common-app/src/basic/multitap/index.tsx rename to apps/common-app/src/legacy/basic/multitap/index.tsx index 0409385e98..eea51e7055 100644 --- a/apps/common-app/src/basic/multitap/index.tsx +++ b/apps/common-app/src/legacy/basic/multitap/index.tsx @@ -10,7 +10,7 @@ import { TapGestureHandlerStateChangeEvent, } from 'react-native-gesture-handler'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; interface PressBoxProps { setDuration?: (duration: number) => void; diff --git a/apps/common-app/src/basic/pagerAndDrawer/index.android.tsx b/apps/common-app/src/legacy/basic/pagerAndDrawer/index.android.tsx similarity index 100% rename from apps/common-app/src/basic/pagerAndDrawer/index.android.tsx rename to apps/common-app/src/legacy/basic/pagerAndDrawer/index.android.tsx diff --git a/apps/common-app/src/basic/pagerAndDrawer/index.tsx b/apps/common-app/src/legacy/basic/pagerAndDrawer/index.tsx similarity index 100% rename from apps/common-app/src/basic/pagerAndDrawer/index.tsx rename to apps/common-app/src/legacy/basic/pagerAndDrawer/index.tsx diff --git a/apps/common-app/src/basic/panResponder/index.tsx b/apps/common-app/src/legacy/basic/panResponder/index.tsx similarity index 98% rename from apps/common-app/src/basic/panResponder/index.tsx rename to apps/common-app/src/legacy/basic/panResponder/index.tsx index 59b5d4ff00..474d4cd00c 100644 --- a/apps/common-app/src/basic/panResponder/index.tsx +++ b/apps/common-app/src/legacy/basic/panResponder/index.tsx @@ -12,7 +12,7 @@ import { import { ScrollView } from 'react-native-gesture-handler'; import { DraggableBox } from '../draggable'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; const CIRCLE_SIZE = 80; diff --git a/apps/common-app/src/legacy/index.tsx b/apps/common-app/src/legacy/index.tsx new file mode 100644 index 0000000000..5b3a42f67c --- /dev/null +++ b/apps/common-app/src/legacy/index.tsx @@ -0,0 +1,191 @@ +import OverflowParent from './release_tests/overflowParent'; +import DoublePinchRotate from './release_tests/doubleScalePinchAndRotate'; +import DoubleDraggable from './release_tests/doubleDraggable'; +import GesturizedPressable from './release_tests/gesturizedPressable'; +import { ComboWithGHScroll } from './release_tests/combo'; +import { TouchablesIndex } from './release_tests/touchables'; +import NestedFling from './release_tests/nestedFling'; +import MouseButtons from './release_tests/mouseButtons'; +import ContextMenu from './release_tests/contextMenu'; +import NestedTouchables from './release_tests/nestedTouchables'; +import NestedPressables from './release_tests/nestedPressables'; +import NestedButtons from './release_tests/nestedButtons'; +import PointerType from './release_tests/pointerType'; +import NestedGestureHandlerRootViewWithModal from './release_tests/nestedGHRootViewWithModal'; +import TwoFingerPan from './release_tests/twoFingerPan'; +import SvgCompatibility from './release_tests/svg'; +import NestedText from './release_tests/nestedText'; + +import { PinchableBox } from './recipes/scaleAndRotate'; +import PanAndScroll from './recipes/panAndScroll'; + +import { BottomSheet } from './showcase/bottomSheet'; +import ChatHeads from './showcase/chatHeads'; + +import Draggable from './basic/draggable'; +import MultiTap from './basic/multitap'; +import BouncingBox from './basic/bouncing'; +import PanResponder from './basic/panResponder'; +import PagerAndDrawer from './basic/pagerAndDrawer'; +import ForceTouch from './basic/forcetouch'; +import Fling from './basic/fling'; +import WebStylesResetExample from './release_tests/webStylesReset'; +import StylusData from './release_tests/StylusData'; + +import Camera from './v2_api/camera'; +import Transformations from './v2_api/transformations'; +import Overlap from './v2_api/overlap'; +import Calculator from './v2_api/calculator'; +import BottomSheetNewApi from './v2_api/bottom_sheet'; +import ChatHeadsNewApi from './v2_api/chat_heads'; +import DragNDrop from './v2_api/drag_n_drop'; +import ManualGestures from './v2_api/manualGestures/index'; +import Hover from './v2_api/hover'; +import HoverableIcons from './v2_api/hoverable_icons'; +import VelocityTest from './v2_api/velocityTest'; +import Pressable from './v2_api/pressable'; + +import RectButtonBorders from './release_tests/rectButton'; + +import MacosDraggable from './simple/draggable'; +import Tap from './simple/tap'; +import LongPressExample from './simple/longPress'; +import ManualExample from './simple/manual'; +import SimpleFling from './simple/fling'; + +import { ExamplesSection } from '../common'; +import EmptyExample from '../empty'; +export const OLD_EXAMPLES: ExamplesSection[] = [ + { + sectionTitle: 'Empty', + 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: 'Pressable', component: Pressable }, + { name: 'Hover', component: Hover }, + { name: 'Hoverable icons', component: HoverableIcons }, + { + name: 'Manual gestures', + component: ManualGestures, + }, + ], + }, + { + sectionTitle: 'Basic examples', + data: [ + { name: 'Draggable', component: Draggable }, + { name: 'Multitap', component: MultiTap }, + { name: 'Bouncing box', component: BouncingBox }, + { name: 'Pan responder', component: PanResponder }, + { + name: 'Pager & drawer', + component: PagerAndDrawer, + unsupportedPlatforms: new Set(['web', 'ios', 'macos']), + }, + { + name: 'Force touch', + component: ForceTouch, + unsupportedPlatforms: new Set(['web', 'android', 'macos']), + }, + { name: 'Fling', component: Fling }, + ], + }, + { + sectionTitle: 'Recipes', + data: [ + { name: 'Pinch & rotate', component: PinchableBox }, + { name: 'Pan & scroll', component: PanAndScroll }, + ], + }, + { + sectionTitle: 'Showcase', + data: [ + { name: 'Bottom sheet', component: BottomSheet }, + { name: 'Chat heads', component: ChatHeads }, + ], + }, + { + sectionTitle: 'Release tests', + data: [ + { + name: 'Views overflowing parents - issue #1532', + component: OverflowParent, + }, + { + name: 'Modals with nested GHRootViews - issue #139', + component: NestedGestureHandlerRootViewWithModal, + }, + { + name: 'Nested Touchables - issue #784', + component: NestedTouchables as React.ComponentType, + }, + { + name: 'Nested Pressables - issue #2980', + component: NestedPressables as React.ComponentType, + }, + { + name: 'Nested buttons (sound & ripple)', + component: NestedButtons, + unsupportedPlatforms: new Set(['web', 'ios', 'macos']), + }, + { + name: 'Svg integration with Gesture Handler', + component: SvgCompatibility, + }, + { name: 'Double pinch & rotate', component: DoublePinchRotate }, + { name: 'Double draggable', component: DoubleDraggable }, + { 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: 'ContextMenu', + component: ContextMenu, + unsupportedPlatforms: new Set(['android', 'ios', 'macos']), + }, + { name: 'PointerType', component: PointerType }, + { 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: 'Two finger Pan', + component: TwoFingerPan, + unsupportedPlatforms: new Set(['android', 'macos']), + }, + { + name: 'Nested Text', + component: NestedText, + unsupportedPlatforms: new Set(['macos']), + }, + ], + }, + { + 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 }, + ], + }, +]; diff --git a/apps/common-app/src/recipes/panAndScroll/index.tsx b/apps/common-app/src/legacy/recipes/panAndScroll/index.tsx similarity index 96% rename from apps/common-app/src/recipes/panAndScroll/index.tsx rename to apps/common-app/src/legacy/recipes/panAndScroll/index.tsx index b31f52faeb..d796a10530 100644 --- a/apps/common-app/src/recipes/panAndScroll/index.tsx +++ b/apps/common-app/src/legacy/recipes/panAndScroll/index.tsx @@ -9,8 +9,8 @@ import { LegacyScrollView, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; -import { LoremIpsum } from '../../common'; +import { USE_NATIVE_DRIVER } from '../../../config'; +import { LoremIpsum } from '../../../common'; const windowWidth = Dimensions.get('window').width; const circleRadius = 30; diff --git a/apps/common-app/src/recipes/scaleAndRotate/index.tsx b/apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx similarity index 99% rename from apps/common-app/src/recipes/scaleAndRotate/index.tsx rename to apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx index 352fb7ae73..c39e8df98f 100644 --- a/apps/common-app/src/recipes/scaleAndRotate/index.tsx +++ b/apps/common-app/src/legacy/recipes/scaleAndRotate/index.tsx @@ -14,7 +14,7 @@ import { RotationGestureHandlerStateChangeEvent, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { USE_NATIVE_DRIVER } from '../../../config'; export class PinchableBox extends React.Component { private panRef = React.createRef(); diff --git a/apps/common-app/src/recipes/scaleAndRotate/swm.svg b/apps/common-app/src/legacy/recipes/scaleAndRotate/swm.svg similarity index 100% rename from apps/common-app/src/recipes/scaleAndRotate/swm.svg rename to apps/common-app/src/legacy/recipes/scaleAndRotate/swm.svg diff --git a/apps/common-app/src/recipes/scaleAndRotate/swmansion.png b/apps/common-app/src/legacy/recipes/scaleAndRotate/swmansion.png similarity index 100% rename from apps/common-app/src/recipes/scaleAndRotate/swmansion.png rename to apps/common-app/src/legacy/recipes/scaleAndRotate/swmansion.png diff --git a/apps/common-app/src/release_tests/StylusData/index.tsx b/apps/common-app/src/legacy/release_tests/StylusData/index.tsx similarity index 97% rename from apps/common-app/src/release_tests/StylusData/index.tsx rename to apps/common-app/src/legacy/release_tests/StylusData/index.tsx index 023d7f7441..15bb86df33 100644 --- a/apps/common-app/src/release_tests/StylusData/index.tsx +++ b/apps/common-app/src/legacy/release_tests/StylusData/index.tsx @@ -8,7 +8,7 @@ import Animated, { } from 'react-native-reanimated'; // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const GH = require('../../new_api/hoverable_icons/gh.png'); +const GH = require('../../../common_assets/hoverable_icons/gh.png'); export default function StylusData() { const scaleFactor = useSharedValue(0); diff --git a/apps/common-app/src/release_tests/combo/InfoButton.tsx b/apps/common-app/src/legacy/release_tests/combo/InfoButton.tsx similarity index 100% rename from apps/common-app/src/release_tests/combo/InfoButton.tsx rename to apps/common-app/src/legacy/release_tests/combo/InfoButton.tsx diff --git a/apps/common-app/src/release_tests/combo/index.tsx b/apps/common-app/src/legacy/release_tests/combo/index.tsx similarity index 99% rename from apps/common-app/src/release_tests/combo/index.tsx rename to apps/common-app/src/legacy/release_tests/combo/index.tsx index b0ca00733c..b7e4981e4d 100644 --- a/apps/common-app/src/release_tests/combo/index.tsx +++ b/apps/common-app/src/legacy/release_tests/combo/index.tsx @@ -27,7 +27,7 @@ import { DraggableBox } from '../../basic/draggable'; import { PinchableBox } from '../../recipes/scaleAndRotate'; import { PressBox } from '../../basic/multitap'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; import { InfoButton } from './InfoButton'; const WrappedSlider = createNativeWrapper(Slider, { diff --git a/apps/common-app/src/release_tests/contextMenu/index.tsx b/apps/common-app/src/legacy/release_tests/contextMenu/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/contextMenu/index.tsx rename to apps/common-app/src/legacy/release_tests/contextMenu/index.tsx diff --git a/apps/common-app/src/release_tests/doubleDraggable/index.tsx b/apps/common-app/src/legacy/release_tests/doubleDraggable/index.tsx similarity index 92% rename from apps/common-app/src/release_tests/doubleDraggable/index.tsx rename to apps/common-app/src/legacy/release_tests/doubleDraggable/index.tsx index ba2745ed71..a51d76356d 100644 --- a/apps/common-app/src/release_tests/doubleDraggable/index.tsx +++ b/apps/common-app/src/legacy/release_tests/doubleDraggable/index.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { StyleSheet, View } from 'react-native'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; import { DraggableBox } from '../../basic/draggable/index'; export default class Example extends Component { diff --git a/apps/common-app/src/release_tests/doubleScalePinchAndRotate/index.tsx b/apps/common-app/src/legacy/release_tests/doubleScalePinchAndRotate/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/doubleScalePinchAndRotate/index.tsx rename to apps/common-app/src/legacy/release_tests/doubleScalePinchAndRotate/index.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/androidRippleExample.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/androidRippleExample.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/androidRippleExample.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/androidRippleExample.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/delayedPressExample.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/delayedPressExample.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/delayedPressExample.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/delayedPressExample.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/functionalStylesExample.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/functionalStylesExample.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/functionalStylesExample.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/functionalStylesExample.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/hitSlopExample.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/hitSlopExample.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/hitSlopExample.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/hitSlopExample.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/hoverDelayExample.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/hoverDelayExample.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/hoverDelayExample.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/hoverDelayExample.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/index.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/index.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/index.tsx diff --git a/apps/common-app/src/release_tests/gesturizedPressable/testingBase.tsx b/apps/common-app/src/legacy/release_tests/gesturizedPressable/testingBase.tsx similarity index 100% rename from apps/common-app/src/release_tests/gesturizedPressable/testingBase.tsx rename to apps/common-app/src/legacy/release_tests/gesturizedPressable/testingBase.tsx diff --git a/apps/common-app/src/release_tests/mouseButtons/index.tsx b/apps/common-app/src/legacy/release_tests/mouseButtons/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/mouseButtons/index.tsx rename to apps/common-app/src/legacy/release_tests/mouseButtons/index.tsx diff --git a/apps/common-app/src/release_tests/nestedButtons/index.tsx b/apps/common-app/src/legacy/release_tests/nestedButtons/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/nestedButtons/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedButtons/index.tsx diff --git a/apps/common-app/src/release_tests/nestedFling/index.tsx b/apps/common-app/src/legacy/release_tests/nestedFling/index.tsx similarity index 98% rename from apps/common-app/src/release_tests/nestedFling/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedFling/index.tsx index 17ecd45e7e..574080c75b 100644 --- a/apps/common-app/src/release_tests/nestedFling/index.tsx +++ b/apps/common-app/src/legacy/release_tests/nestedFling/index.tsx @@ -7,7 +7,7 @@ import { FlingGestureHandlerStateChangeEvent, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { USE_NATIVE_DRIVER } from '../../../config'; const windowWidth = Dimensions.get('window').width; const circleRadius = 30; diff --git a/apps/common-app/src/release_tests/nestedGHRootViewWithModal/index.tsx b/apps/common-app/src/legacy/release_tests/nestedGHRootViewWithModal/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/nestedGHRootViewWithModal/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedGHRootViewWithModal/index.tsx diff --git a/apps/common-app/src/release_tests/nestedPressables/index.tsx b/apps/common-app/src/legacy/release_tests/nestedPressables/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/nestedPressables/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedPressables/index.tsx diff --git a/apps/common-app/src/release_tests/nestedText/index.tsx b/apps/common-app/src/legacy/release_tests/nestedText/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/nestedText/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedText/index.tsx diff --git a/apps/common-app/src/release_tests/nestedTouchables/index.tsx b/apps/common-app/src/legacy/release_tests/nestedTouchables/index.tsx similarity index 97% rename from apps/common-app/src/release_tests/nestedTouchables/index.tsx rename to apps/common-app/src/legacy/release_tests/nestedTouchables/index.tsx index a8b51ad916..bc2e982d74 100644 --- a/apps/common-app/src/release_tests/nestedTouchables/index.tsx +++ b/apps/common-app/src/legacy/release_tests/nestedTouchables/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet, Text } from 'react-native'; import { TouchableOpacity, ScrollView } from 'react-native-gesture-handler'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; export default function Example() { return ( diff --git a/apps/common-app/src/release_tests/overflowParent/index.tsx b/apps/common-app/src/legacy/release_tests/overflowParent/index.tsx similarity index 98% rename from apps/common-app/src/release_tests/overflowParent/index.tsx rename to apps/common-app/src/legacy/release_tests/overflowParent/index.tsx index 6f3705e9bc..02007796c6 100644 --- a/apps/common-app/src/release_tests/overflowParent/index.tsx +++ b/apps/common-app/src/legacy/release_tests/overflowParent/index.tsx @@ -7,7 +7,7 @@ import { State, TapGestureHandler, } from 'react-native-gesture-handler'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { USE_NATIVE_DRIVER } from '../../../config'; export default function Example() { const translationX = useRef(new Animated.Value(0)).current; diff --git a/apps/common-app/src/release_tests/pointerType/index.tsx b/apps/common-app/src/legacy/release_tests/pointerType/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/pointerType/index.tsx rename to apps/common-app/src/legacy/release_tests/pointerType/index.tsx diff --git a/apps/common-app/src/release_tests/rectButton/index.tsx b/apps/common-app/src/legacy/release_tests/rectButton/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/rectButton/index.tsx rename to apps/common-app/src/legacy/release_tests/rectButton/index.tsx diff --git a/apps/common-app/src/release_tests/svg/index.tsx b/apps/common-app/src/legacy/release_tests/svg/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/svg/index.tsx rename to apps/common-app/src/legacy/release_tests/svg/index.tsx diff --git a/apps/common-app/src/release_tests/touchables/index.tsx b/apps/common-app/src/legacy/release_tests/touchables/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/touchables/index.tsx rename to apps/common-app/src/legacy/release_tests/touchables/index.tsx diff --git a/apps/common-app/src/release_tests/twoFingerPan/index.tsx b/apps/common-app/src/legacy/release_tests/twoFingerPan/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/twoFingerPan/index.tsx rename to apps/common-app/src/legacy/release_tests/twoFingerPan/index.tsx diff --git a/apps/common-app/src/release_tests/webStylesReset/index.tsx b/apps/common-app/src/legacy/release_tests/webStylesReset/index.tsx similarity index 100% rename from apps/common-app/src/release_tests/webStylesReset/index.tsx rename to apps/common-app/src/legacy/release_tests/webStylesReset/index.tsx diff --git a/apps/common-app/src/showcase/bottomSheet/index.tsx b/apps/common-app/src/legacy/showcase/bottomSheet/index.tsx similarity index 98% rename from apps/common-app/src/showcase/bottomSheet/index.tsx rename to apps/common-app/src/legacy/showcase/bottomSheet/index.tsx index e76bfab58d..86facab218 100644 --- a/apps/common-app/src/showcase/bottomSheet/index.tsx +++ b/apps/common-app/src/legacy/showcase/bottomSheet/index.tsx @@ -16,8 +16,8 @@ import { PanGestureHandlerGestureEvent, } from 'react-native-gesture-handler'; -import { LoremIpsum } from '../../common'; -import { USE_NATIVE_DRIVER } from '../../config'; +import { LoremIpsum } from '../../../common'; +import { USE_NATIVE_DRIVER } from '../../../config'; type BottomSheetState = { lastSnap: number; diff --git a/apps/common-app/src/showcase/chatHeads/index.tsx b/apps/common-app/src/legacy/showcase/chatHeads/index.tsx similarity index 100% rename from apps/common-app/src/showcase/chatHeads/index.tsx rename to apps/common-app/src/legacy/showcase/chatHeads/index.tsx diff --git a/apps/common-app/src/simple/draggable/index.tsx b/apps/common-app/src/legacy/simple/draggable/index.tsx similarity index 100% rename from apps/common-app/src/simple/draggable/index.tsx rename to apps/common-app/src/legacy/simple/draggable/index.tsx diff --git a/apps/common-app/src/simple/fling/index.tsx b/apps/common-app/src/legacy/simple/fling/index.tsx similarity index 100% rename from apps/common-app/src/simple/fling/index.tsx rename to apps/common-app/src/legacy/simple/fling/index.tsx diff --git a/apps/common-app/src/simple/longPress/index.tsx b/apps/common-app/src/legacy/simple/longPress/index.tsx similarity index 100% rename from apps/common-app/src/simple/longPress/index.tsx rename to apps/common-app/src/legacy/simple/longPress/index.tsx diff --git a/apps/common-app/src/simple/manual/index.tsx b/apps/common-app/src/legacy/simple/manual/index.tsx similarity index 100% rename from apps/common-app/src/simple/manual/index.tsx rename to apps/common-app/src/legacy/simple/manual/index.tsx diff --git a/apps/common-app/src/simple/tap/index.tsx b/apps/common-app/src/legacy/simple/tap/index.tsx similarity index 100% rename from apps/common-app/src/simple/tap/index.tsx rename to apps/common-app/src/legacy/simple/tap/index.tsx diff --git a/apps/common-app/src/new_api/bottom_sheet/index.tsx b/apps/common-app/src/legacy/v2_api/bottom_sheet/index.tsx similarity index 99% rename from apps/common-app/src/new_api/bottom_sheet/index.tsx rename to apps/common-app/src/legacy/v2_api/bottom_sheet/index.tsx index 4e98d6d103..bfe8dc2fc6 100644 --- a/apps/common-app/src/new_api/bottom_sheet/index.tsx +++ b/apps/common-app/src/legacy/v2_api/bottom_sheet/index.tsx @@ -17,7 +17,7 @@ import Animated, { useSharedValue, withSpring, } from 'react-native-reanimated'; -import { LoremIpsum } from '../../common'; +import { LoremIpsum } from '../../../common'; const HEADER_HEIGTH = 50; const windowHeight = Dimensions.get('window').height; diff --git a/apps/common-app/src/new_api/calculator/index.tsx b/apps/common-app/src/legacy/v2_api/calculator/index.tsx similarity index 100% rename from apps/common-app/src/new_api/calculator/index.tsx rename to apps/common-app/src/legacy/v2_api/calculator/index.tsx diff --git a/apps/common-app/src/new_api/camera/index.tsx b/apps/common-app/src/legacy/v2_api/camera/index.tsx similarity index 98% rename from apps/common-app/src/new_api/camera/index.tsx rename to apps/common-app/src/legacy/v2_api/camera/index.tsx index 7ee8fc6e28..ee969fc4ac 100644 --- a/apps/common-app/src/new_api/camera/index.tsx +++ b/apps/common-app/src/legacy/v2_api/camera/index.tsx @@ -10,7 +10,7 @@ import Animated, { withTiming, } from 'react-native-reanimated'; import { Circle, Svg } from 'react-native-svg'; -import AnimatedCameraView from './AnimatedCameraView'; +import AnimatedCameraView from '../../../common_assets/AnimatedCameraView/AnimatedCameraView'; const FILTERS = ['red', 'green', 'blue', 'yellow', 'orange', 'cyan']; const CAROUSEL_SIZE = 100; @@ -79,7 +79,6 @@ export default function Camera() { 1, Math.min(2, zoom.value * ((e.scaleChange - 1) * 0.2 + 1)) ); - console.log(zoom.value); }); const changeCameraGesture = Gesture.Tap() diff --git a/apps/common-app/src/new_api/chat_heads/index.tsx b/apps/common-app/src/legacy/v2_api/chat_heads/index.tsx similarity index 100% rename from apps/common-app/src/new_api/chat_heads/index.tsx rename to apps/common-app/src/legacy/v2_api/chat_heads/index.tsx diff --git a/apps/common-app/src/new_api/drag_n_drop/DragAndDrop.tsx b/apps/common-app/src/legacy/v2_api/drag_n_drop/DragAndDrop.tsx similarity index 100% rename from apps/common-app/src/new_api/drag_n_drop/DragAndDrop.tsx rename to apps/common-app/src/legacy/v2_api/drag_n_drop/DragAndDrop.tsx diff --git a/apps/common-app/src/new_api/drag_n_drop/Draggable.tsx b/apps/common-app/src/legacy/v2_api/drag_n_drop/Draggable.tsx similarity index 100% rename from apps/common-app/src/new_api/drag_n_drop/Draggable.tsx rename to apps/common-app/src/legacy/v2_api/drag_n_drop/Draggable.tsx diff --git a/apps/common-app/src/new_api/drag_n_drop/Tile.tsx b/apps/common-app/src/legacy/v2_api/drag_n_drop/Tile.tsx similarity index 100% rename from apps/common-app/src/new_api/drag_n_drop/Tile.tsx rename to apps/common-app/src/legacy/v2_api/drag_n_drop/Tile.tsx diff --git a/apps/common-app/src/new_api/drag_n_drop/constants.tsx b/apps/common-app/src/legacy/v2_api/drag_n_drop/constants.tsx similarity index 100% rename from apps/common-app/src/new_api/drag_n_drop/constants.tsx rename to apps/common-app/src/legacy/v2_api/drag_n_drop/constants.tsx diff --git a/apps/common-app/src/new_api/drag_n_drop/index.tsx b/apps/common-app/src/legacy/v2_api/drag_n_drop/index.tsx similarity index 100% rename from apps/common-app/src/new_api/drag_n_drop/index.tsx rename to apps/common-app/src/legacy/v2_api/drag_n_drop/index.tsx diff --git a/apps/common-app/src/new_api/hover/index.tsx b/apps/common-app/src/legacy/v2_api/hover/index.tsx similarity index 100% rename from apps/common-app/src/new_api/hover/index.tsx rename to apps/common-app/src/legacy/v2_api/hover/index.tsx diff --git a/apps/common-app/src/new_api/hoverable_icons/index.tsx b/apps/common-app/src/legacy/v2_api/hoverable_icons/index.tsx similarity index 85% rename from apps/common-app/src/new_api/hoverable_icons/index.tsx rename to apps/common-app/src/legacy/v2_api/hoverable_icons/index.tsx index 94991ade56..2db4966ddb 100644 --- a/apps/common-app/src/new_api/hoverable_icons/index.tsx +++ b/apps/common-app/src/legacy/v2_api/hoverable_icons/index.tsx @@ -13,15 +13,15 @@ import Animated, { import { Platform, StyleSheet } from 'react-native'; // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const SVG = require('./svg.png'); +const SVG = require('../../../common_assets/hoverable_icons/svg.png'); // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const FREEZE = require('./freeze.png'); +const FREEZE = require('../../../common_assets/hoverable_icons/freeze.png'); // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const REA = require('./rea.png'); +const REA = require('../../../common_assets/hoverable_icons/rea.png'); // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const GH = require('./gh.png'); +const GH = require('../../../common_assets/hoverable_icons/gh.png'); // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires -const SCREENS = require('./screens.png'); +const SCREENS = require('../../../common_assets/hoverable_icons/screens.png'); const images = [GH, REA, SCREENS, SVG, FREEZE]; const SIZE = 100; @@ -95,7 +95,7 @@ export default function Example() { return ( - + {images.map((source, index) => ( ))} @@ -116,4 +116,10 @@ const styles = StyleSheet.create({ height: SIZE, marginHorizontal: 8, }, + wrapper: { + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'row', + flexWrap: 'wrap', + }, }); diff --git a/apps/common-app/src/new_api/manualGestures/index.tsx b/apps/common-app/src/legacy/v2_api/manualGestures/index.tsx similarity index 100% rename from apps/common-app/src/new_api/manualGestures/index.tsx rename to apps/common-app/src/legacy/v2_api/manualGestures/index.tsx diff --git a/apps/common-app/src/new_api/overlap/index.tsx b/apps/common-app/src/legacy/v2_api/overlap/index.tsx similarity index 100% rename from apps/common-app/src/new_api/overlap/index.tsx rename to apps/common-app/src/legacy/v2_api/overlap/index.tsx diff --git a/apps/common-app/src/new_api/pressable/index.tsx b/apps/common-app/src/legacy/v2_api/pressable/index.tsx similarity index 100% rename from apps/common-app/src/new_api/pressable/index.tsx rename to apps/common-app/src/legacy/v2_api/pressable/index.tsx diff --git a/apps/common-app/src/new_api/transformations/index.tsx b/apps/common-app/src/legacy/v2_api/transformations/index.tsx similarity index 97% rename from apps/common-app/src/new_api/transformations/index.tsx rename to apps/common-app/src/legacy/v2_api/transformations/index.tsx index 794fbb2bbf..9909d9be42 100644 --- a/apps/common-app/src/new_api/transformations/index.tsx +++ b/apps/common-app/src/legacy/v2_api/transformations/index.tsx @@ -7,7 +7,7 @@ import Animated, { import { GestureDetector, Gesture } from 'react-native-gesture-handler'; // @ts-ignore it's an image -import SIGNET from '../../ListWithHeader/signet.png'; +import SIGNET from '../../../ListWithHeader/signet.png'; function Photo() { const translationX = useSharedValue(0); diff --git a/apps/common-app/src/new_api/velocityTest/index.tsx b/apps/common-app/src/legacy/v2_api/velocityTest/index.tsx similarity index 100% rename from apps/common-app/src/new_api/velocityTest/index.tsx rename to apps/common-app/src/legacy/v2_api/velocityTest/index.tsx diff --git a/apps/common-app/src/new_api/complicated/camera/capture.tsx b/apps/common-app/src/new_api/complicated/camera/capture.tsx new file mode 100644 index 0000000000..0bb9e4a7ce --- /dev/null +++ b/apps/common-app/src/new_api/complicated/camera/capture.tsx @@ -0,0 +1,73 @@ +import { StyleSheet } from 'react-native'; +import Animated, { + SharedValue, + useAnimatedProps, +} from 'react-native-reanimated'; +import Svg, { Circle } from 'react-native-svg'; + +const CAROUSEL_SIZE = 100; +const RECORD_INDICATOR_STROKE = 10; +const AnimatedCircle = Animated.createAnimatedComponent(Circle); + +interface CaptureButtonProps { + progress: SharedValue; +} + +export function CaptureButton(props: CaptureButtonProps) { + const radius = CAROUSEL_SIZE / 2; + const svgRadius = CAROUSEL_SIZE / 2 - RECORD_INDICATOR_STROKE * 0.5; + const circumference = svgRadius * 2 * Math.PI; + + const animatedProps = useAnimatedProps(() => { + const svgProgress = 100 - props.progress.value * 100; + return { + strokeDashoffset: svgRadius * Math.PI * 2 * (svgProgress / 100), + }; + }); + + return ( + + + + + + + ); +} + +const styles = StyleSheet.create({ + shutterContainer: { + position: 'absolute', + top: 0, + left: '50%', + width: CAROUSEL_SIZE, + height: CAROUSEL_SIZE, + transform: [{ translateX: -CAROUSEL_SIZE / 2 }], + }, + shutterButtonBackground: { + width: CAROUSEL_SIZE, + height: CAROUSEL_SIZE, + borderRadius: CAROUSEL_SIZE / 2, + borderWidth: RECORD_INDICATOR_STROKE, + borderColor: 'white', + position: 'absolute', + }, + shutterButtonRecordingIndicator: { + width: CAROUSEL_SIZE, + height: CAROUSEL_SIZE, + position: 'absolute', + top: 0, + left: '50%', + transform: [{ translateX: -CAROUSEL_SIZE / 2 }, { rotateZ: '-90deg' }], + }, +}); diff --git a/apps/common-app/src/new_api/complicated/camera/filters.tsx b/apps/common-app/src/new_api/complicated/camera/filters.tsx new file mode 100644 index 0000000000..25f89ba02a --- /dev/null +++ b/apps/common-app/src/new_api/complicated/camera/filters.tsx @@ -0,0 +1,68 @@ +import { StyleSheet, View } from 'react-native'; +import Animated, { + SharedValue, + useAnimatedStyle, +} from 'react-native-reanimated'; + +interface FilterCarouselProps { + filters: string[]; + selected: SharedValue; + filterSize: number; +} + +export function FilterCarousel(props: FilterCarouselProps) { + const style = useAnimatedStyle(() => { + return { + flexDirection: 'row', + position: 'absolute', + left: '50%', + gap: props.filterSize * 0.4, + transform: [ + { + translateX: + -props.filterSize / 2 - + props.filterSize * 1.4 * props.selected.value, + }, + ], + }; + }); + + return ( + + {props.filters.map((filter) => ( + + ))} + + ); +} + +interface FilterOverlayProps { + filters: string[]; + selected: SharedValue; +} + +export function FilterOverlay(props: FilterOverlayProps) { + const style = useAnimatedStyle(() => { + const progress = props.selected.value % 1; + + return { + opacity: 0.3 * (Math.abs(progress - 0.5) * 2), + backgroundColor: props.filters[Math.round(props.selected.value)], + }; + }); + + return ( + + ); +} diff --git a/apps/common-app/src/new_api/complicated/camera/index.tsx b/apps/common-app/src/new_api/complicated/camera/index.tsx new file mode 100644 index 0000000000..4ce75e8b26 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/camera/index.tsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + useLongPressGesture, + usePinchGesture, + useTapGesture, + useCompetingGestures, + GestureDetector, + useSimultaneousGestures, + usePanGesture, + useExclusiveGestures, +} from 'react-native-gesture-handler'; +import { runOnJS, useSharedValue, withTiming } from 'react-native-reanimated'; +import AnimatedCameraView from '../../../common_assets/AnimatedCameraView/AnimatedCameraView'; +import { COLORS } from '../../../common'; +import { FilterCarousel, FilterOverlay } from './filters'; +import { CaptureButton } from './capture'; + +const FILTERS = [ + COLORS.RED, + COLORS.GREEN, + COLORS.NAVY, + COLORS.YELLOW, + COLORS.KINDA_BLUE, +]; +const CAROUSEL_SIZE = 100; +const FILTER_SIZE = 60; +const VIDEO_DURATION = 20000; + +export default function Camera() { + const [facing, setFacing] = useState<'front' | 'back'>('back'); + const selectedFilter = useSharedValue(0); + const captureProgress = useSharedValue(0); + const zoom = useSharedValue(1); + + const filterChangeGesture = usePanGesture({ + onUpdate: (e) => { + selectedFilter.value -= e.changeX / FILTER_SIZE; + }, + onDeactivate: () => { + const nextFilter = Math.min( + FILTERS.length - 1, + Math.max(0, Math.round(selectedFilter.value)) + ); + selectedFilter.value = withTiming(nextFilter, { duration: 150 }); + }, + }); + + function takePhoto() { + alert("I didn't bother to implement this :)"); + } + + function startRecording() { + // no-op + } + + function stopRecording() { + alert("I didn't bother to implement this either :)"); + } + + const takePhotoGesture = useTapGesture({ + onDeactivate: () => { + runOnJS(takePhoto)(); + captureProgress.value = withTiming(0, { duration: 1000 }); + }, + }); + + const takeVideoGesture = useLongPressGesture({ + shouldCancelWhenOutside: false, + maxDistance: 10000, + onActivate: () => { + runOnJS(startRecording)(); + captureProgress.value = withTiming(1, { + duration: VIDEO_DURATION, + easing: (x) => x, + }); + }, + onDeactivate: () => { + runOnJS(stopRecording)(); + captureProgress.value = 0; + }, + }); + + const panZoomGesture = usePanGesture({ + shouldCancelWhenOutside: false, + requireToFail: filterChangeGesture, + onUpdate: (e) => { + zoom.value = Math.max(1, Math.min(2, zoom.value - e.changeY / 500)); + }, + }); + + const pinchZoomGesture = usePinchGesture({ + onUpdate: (e) => { + zoom.value = Math.max( + 1, + Math.min(2, zoom.value * ((e.scaleChange - 1) * 0.2 + 1)) + ); + }, + }); + + const changeCameraGesture = useTapGesture({ + numberOfTaps: 2, + onDeactivate: () => { + setFacing((f) => (f === 'back' ? 'front' : 'back')); + }, + disableReanimated: true, + }); + + const competingGesture = useCompetingGestures( + pinchZoomGesture, + changeCameraGesture + ); + const simultanousGesture = useSimultaneousGestures( + panZoomGesture, + takeVideoGesture + ); + const exclusiveGesture = useExclusiveGestures( + simultanousGesture, + takePhotoGesture + ); + return ( + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + carouselContainer: { + position: 'absolute', + left: 0, + bottom: 32, + height: CAROUSEL_SIZE, + width: '100%', + justifyContent: 'center', + }, +}); diff --git a/apps/common-app/src/new_api/complicated/chat_heads/index.tsx b/apps/common-app/src/new_api/complicated/chat_heads/index.tsx new file mode 100644 index 0000000000..f62546e024 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/chat_heads/index.tsx @@ -0,0 +1,211 @@ +import React, { useState } from 'react'; +import { StyleSheet, ImageStyle, LayoutChangeEvent } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; +import Animated, { + SharedValue, + useAnimatedStyle, + useDerivedValue, + useSharedValue, + withSpring, +} from 'react-native-reanimated'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { useHeaderHeight } from '@react-navigation/elements'; + +const CHAT_HEADS = [ + { imageUrl: 'https://avatars0.githubusercontent.com/u/379606?v=4&s=460' }, + { imageUrl: 'https://avatars3.githubusercontent.com/u/90494?v=4&s=460' }, + { imageUrl: 'https://avatars3.githubusercontent.com/u/726445?v=4&s=460' }, + { imageUrl: 'https://avatars.githubusercontent.com/u/15989228?v=4&s=460' }, +]; + +interface AnimatedOffset { + x: SharedValue; + y: SharedValue; +} + +interface FollowingChatHeadProps { + imageUri: string; + offset: AnimatedOffset; + offsetToFollow: AnimatedOffset; + style?: ImageStyle; +} + +function FollowingChatHead({ + imageUri, + style, + offset, + offsetToFollow, +}: FollowingChatHeadProps) { + useDerivedValue(() => { + offset.x.value = withSpring(offsetToFollow.x.value); + offset.y.value = withSpring(offsetToFollow.y.value); + }, []); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { translateX: offset.x.value }, + { translateY: offset.y.value }, + ], + }; + }); + + return ( + + ); +} + +function useOffsetAnimatedValue() { + return { + x: useSharedValue(0), + y: useSharedValue(0), + }; +} + +function clampToValues({ + value, + bottom, + top, +}: { + value: number; + bottom: number; + top: number; +}) { + 'worklet'; + return Math.max(bottom, Math.min(value, top)); +} + +const Example = () => { + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + const panOffset = useOffsetAnimatedValue(); + const mainChatHeadPosition = useOffsetAnimatedValue(); + const chatHeadsOffsets = CHAT_HEADS.map(useOffsetAnimatedValue); + const headerHeight = useHeaderHeight(); + + const onLayout = ({ nativeEvent }: LayoutChangeEvent) => { + const { width, height } = nativeEvent.layout; + setDimensions({ width, height }); + }; + + const panHandler = usePanGesture({ + onUpdate: (e) => { + panOffset.x.value = mainChatHeadPosition.x.value + e.translationX; + panOffset.y.value = mainChatHeadPosition.y.value + e.translationY; + }, + onDeactivate: (e) => { + const { height, width } = dimensions; + const velocityDragX = clampToValues({ + value: e.velocityX * 0.05, + bottom: -100, + top: 100, + }); + const velocityDragY = clampToValues({ + value: e.velocityY * 0.05, + bottom: -100, + top: 100, + }); + + const distFromTop = e.absoluteY + velocityDragY - headerHeight; + const distFromBottom = height + velocityDragY - e.absoluteY; + const distFromLeft = e.absoluteX + velocityDragX; + const distFromRight = width - e.absoluteX + velocityDragX; + + const minDist = Math.min( + distFromTop, + distFromBottom, + distFromLeft, + distFromRight + ); + + // drag to the edge + switch (minDist) { + case distFromTop: { + panOffset.y.value = withSpring(-IMAGE_SIZE / 2); + panOffset.x.value = withSpring(panOffset.x.value + velocityDragX); + mainChatHeadPosition.y.value = -IMAGE_SIZE / 2; + mainChatHeadPosition.x.value = panOffset.x.value; + break; + } + case distFromBottom: { + panOffset.y.value = withSpring(height - IMAGE_SIZE / 2); + panOffset.x.value = withSpring(panOffset.x.value + velocityDragX); + mainChatHeadPosition.y.value = height - IMAGE_SIZE / 2; + mainChatHeadPosition.x.value = panOffset.x.value; + break; + } + case distFromLeft: { + panOffset.x.value = withSpring(-IMAGE_SIZE / 2); + panOffset.y.value = withSpring(panOffset.y.value + velocityDragY); + mainChatHeadPosition.x.value = -IMAGE_SIZE / 2; + mainChatHeadPosition.y.value = panOffset.y.value; + break; + } + case distFromRight: { + panOffset.x.value = withSpring(width - IMAGE_SIZE / 2); + panOffset.y.value = withSpring(panOffset.y.value + velocityDragY); + mainChatHeadPosition.x.value = width - IMAGE_SIZE / 2; + mainChatHeadPosition.y.value = panOffset.y.value; + break; + } + } + }, + }); + + const headsComponents = CHAT_HEADS.map(({ imageUrl }, idx) => { + const headOffset = chatHeadsOffsets[idx]; + if (idx === 0) { + return ( + + + + ); + } + + return ( + + ); + }); + + return ( + + {/* we want ChatHead with gesture on top */} + {headsComponents.reverse()} + + ); +}; + +export default Example; + +const IMAGE_SIZE = 80; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + box: { + position: 'absolute', + width: IMAGE_SIZE, + height: IMAGE_SIZE, + borderColor: '#F5FCFF', + backgroundColor: 'plum', + borderRadius: IMAGE_SIZE / 2, + }, +}); diff --git a/apps/common-app/src/new_api/complicated/lock/index.tsx b/apps/common-app/src/new_api/complicated/lock/index.tsx new file mode 100644 index 0000000000..4299138091 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/lock/index.tsx @@ -0,0 +1,141 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + runOnJS, +} from 'react-native-reanimated'; +import { + GestureDetector, + useLongPressGesture, + usePinchGesture, + useRotationGesture, + useSimultaneousGestures, +} from 'react-native-gesture-handler'; +import { COLORS, commonStyles } from '../../../common'; + +export default function Lock() { + const rotation = useSharedValue(-Math.PI / 2); + const savedRotation = useSharedValue(-Math.PI / 2); + + const scale = useSharedValue(0.6); + const savedScale = useSharedValue(1); + + const [locked, setLocked] = useState(true); + const snapThreshold = 0.4; + const scaleThreshold = 0.1; + const minScale = 0.5; + const maxScale = 1; + const TWO_PI = 2 * Math.PI; + + // longPress to unlock + const confirm = useLongPressGesture({ + onActivate: () => { + if (savedRotation.value === 0 && scale.value === maxScale) { + runOnJS(setLocked)(false); + } + }, + }); + + const rotationGesture = useRotationGesture({ + onUpdate: (e) => { + rotation.value = savedRotation.value + e.rotation; + + if (!locked) { + runOnJS(setLocked)(true); + } + }, + onDeactivate: () => { + const nearestMultiple = Math.round(rotation.value / TWO_PI) * TWO_PI; + + if (Math.abs(rotation.value - nearestMultiple) < snapThreshold) { + rotation.value = withTiming(nearestMultiple, { duration: 300 }); + savedRotation.value = 0; + } else { + rotation.value = withTiming(-Math.PI / 2, { duration: 300 }); + savedRotation.value = -Math.PI / 2; + } + }, + simultaneousWith: confirm, + }); + + const pinchGesture = usePinchGesture({ + onUpdate: (e) => { + const value = savedScale.value * e.scale; + if (value < minScale || value > maxScale) { + return; + } + scale.value = value; + + if (!locked) { + runOnJS(setLocked)(true); + } + }, + onDeactivate: () => { + if (Math.abs(scale.value - maxScale) < scaleThreshold) { + scale.value = withTiming(maxScale, { duration: 300 }); + } else { + scale.value = withTiming(minScale, { duration: 300 }); + } + savedScale.value = scale.value; + }, + simultaneousWith: confirm, + }); + + const unlockingGesture = useSimultaneousGestures( + rotationGesture, + pinchGesture + ); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [ + { rotateZ: `${(rotation.value / Math.PI) * 180}deg` }, + { scale: scale.value }, + ], + })); + + return ( + + + + + + {locked ? '🔒' : '🔓'} + + + + + {locked ? 'Locked' : 'Unlocked!'} + + To unlock rotate 90 degrees clockwise, and scale to fill the square. + Then longPress to confirm unlocking. + + + ); +} + +const styles = StyleSheet.create({ + lockIcon: { + fontSize: 40, + color: '#fff', + fontWeight: 'bold', + }, + outerBox: { + height: 200, + width: 200, + backgroundColor: COLORS.GRAY, + borderRadius: 20, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 50, + }, + box: { + height: 200, + width: 200, + backgroundColor: COLORS.PURPLE, + borderRadius: 20, + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/apps/common-app/src/new_api/complicated/velocity_test/index.tsx b/apps/common-app/src/new_api/complicated/velocity_test/index.tsx new file mode 100644 index 0000000000..b1071b1f13 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/velocity_test/index.tsx @@ -0,0 +1,86 @@ +import { View } from 'react-native'; +import Animated, { + interpolateColor, + measure, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, + withDecay, + withTiming, +} from 'react-native-reanimated'; + +import React from 'react'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; +import { COLORS, commonStyles } from '../../../common'; + +const BOX_SIZE = 120; + +export default function App() { + const aref = useAnimatedRef(); + const offsetX = useSharedValue(0); + const offsetY = useSharedValue(0); + const isPressed = useSharedValue(false); + const colorProgress = useSharedValue(0); + + const pan = usePanGesture({ + onBegin: () => { + isPressed.value = true; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onUpdate: (event) => { + offsetX.value += event.changeX; + offsetY.value += event.changeY; + }, + onFinalize: (event) => { + isPressed.value = false; + colorProgress.value = withTiming(0, { + duration: 100, + }); + // If we can't get view size, just ignore it. Half of the view will be + // able to go outside the screen + const size = measure(aref) ?? { width: 0, height: 0 }; + + offsetX.value = withDecay({ + velocity: event.velocityX, + clamp: [-size.width / 2 + BOX_SIZE / 2, size.width / 2 - BOX_SIZE / 2], + rubberBandEffect: true, + rubberBandFactor: 0.75, + }); + + offsetY.value = withDecay({ + velocity: event.velocityY, + clamp: [ + -size.height / 2 + BOX_SIZE / 2, + size.height / 2 - BOX_SIZE / 2, + ], + rubberBandEffect: true, + rubberBandFactor: 0.75, + }); + }, + }); + + const animatedStyles = useAnimatedStyle(() => { + return { + transform: [ + { translateX: offsetX.value }, + { translateY: offsetY.value }, + { scale: withTiming(isPressed.value ? 1.2 : 1, { duration: 100 }) }, + ], + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/components/buttons/index.tsx b/apps/common-app/src/new_api/components/buttons/index.tsx new file mode 100644 index 0000000000..ac3512491d --- /dev/null +++ b/apps/common-app/src/new_api/components/buttons/index.tsx @@ -0,0 +1,99 @@ +import { RefObject, useRef } from 'react'; +import { COLORS, Feedback, FeedbackHandle } from '../../../common'; +import { StyleSheet, Text, View } from 'react-native'; +import { + BaseButton, + BorderlessButton, + GestureHandlerRootView, + RectButton, +} from 'react-native-gesture-handler'; + +type ButtonWrapperProps = { + ButtonComponent: + | typeof BaseButton + | typeof RectButton + | typeof BorderlessButton; + + color: string; + feedback?: RefObject; +}; + +function ButtonWrapper({ + ButtonComponent, + color, + feedback, +}: ButtonWrapperProps) { + return ( + + feedback?.current?.showMessage(`[${ButtonComponent.name}] onLongPress`) + } + onLongPress={() => { + feedback?.current?.showMessage(`[${ButtonComponent.name}] onLongPress`); + }}> + {ButtonComponent.name} + + ); +} + +export default function ButtonsExample() { + const feedbackRef1 = useRef(null); + const feedbackRef2 = useRef(null); + const feedbackRef3 = useRef(null); + + return ( + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'space-around', + }, + inner_container: { + alignItems: 'center', + justifyContent: 'space-around', + }, + button: { + width: 200, + height: 50, + borderRadius: 15, + + display: 'flex', + alignItems: 'center', + justifyContent: 'space-around', + }, + + buttonText: { + color: 'white', + fontSize: 16, + }, +}); diff --git a/apps/common-app/src/release_tests/reanimatedDrawerLayout/index.tsx b/apps/common-app/src/new_api/components/drawer/index.tsx similarity index 95% rename from apps/common-app/src/release_tests/reanimatedDrawerLayout/index.tsx rename to apps/common-app/src/new_api/components/drawer/index.tsx index beab287a41..d7fb9a5cbd 100644 --- a/apps/common-app/src/release_tests/reanimatedDrawerLayout/index.tsx +++ b/apps/common-app/src/new_api/components/drawer/index.tsx @@ -9,7 +9,7 @@ import ReanimatedDrawerLayout, { DrawerLayoutMethods, DrawerLockMode, } from 'react-native-gesture-handler/ReanimatedDrawerLayout'; -import { LoremIpsum } from '../../common'; +import { COLORS, LoremIpsum } from '../../../common'; const DrawerPage = ({ progress }: { progress?: SharedValue }) => { progress && console.log('Drawer opening progress:', progress); @@ -122,7 +122,7 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: 'center', alignItems: 'center', - backgroundColor: 'pink', + backgroundColor: COLORS.KINDA_BLUE, }, innerContainer: { flex: 1, @@ -133,8 +133,9 @@ const styles = StyleSheet.create({ }, box: { width: 150, - padding: 10, + padding: 20, + borderRadius: 10, paddingHorizontal: 5, - backgroundColor: 'pink', + backgroundColor: COLORS.PURPLE, }, }); diff --git a/apps/common-app/src/components/flatlist/index.tsx b/apps/common-app/src/new_api/components/flatlist/index.tsx similarity index 94% rename from apps/common-app/src/components/flatlist/index.tsx rename to apps/common-app/src/new_api/components/flatlist/index.tsx index 87b46d5baf..a5969dcd12 100644 --- a/apps/common-app/src/components/flatlist/index.tsx +++ b/apps/common-app/src/new_api/components/flatlist/index.tsx @@ -1,3 +1,4 @@ +import { COLORS } from '../../../common'; import React, { useRef, useState } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { @@ -51,7 +52,7 @@ const styles = StyleSheet.create({ paddingVertical: 16, }, item: { - backgroundColor: '#f9c2ff', + backgroundColor: COLORS.KINDA_BLUE, padding: 20, marginVertical: 8, marginHorizontal: 16, diff --git a/apps/common-app/src/components/scrollview/index.tsx b/apps/common-app/src/new_api/components/scrollview/index.tsx similarity index 94% rename from apps/common-app/src/components/scrollview/index.tsx rename to apps/common-app/src/new_api/components/scrollview/index.tsx index a640ca97ff..88b6158e61 100644 --- a/apps/common-app/src/components/scrollview/index.tsx +++ b/apps/common-app/src/new_api/components/scrollview/index.tsx @@ -1,3 +1,4 @@ +import { COLORS } from '../../../common'; import React, { useRef, useState } from 'react'; import { Text, StyleSheet, View } from 'react-native'; import { ScrollView, RefreshControl } from 'react-native-gesture-handler'; @@ -45,7 +46,7 @@ const styles = StyleSheet.create({ padding: 24, }, item: { - backgroundColor: '#f9c2ff', + backgroundColor: COLORS.PURPLE, padding: 20, marginVertical: 8, marginHorizontal: 16, diff --git a/apps/common-app/src/new_api/swipeable/AppleStyleSwipeableRow.tsx b/apps/common-app/src/new_api/components/swipeable/AppleStyleSwipeableRow.tsx similarity index 100% rename from apps/common-app/src/new_api/swipeable/AppleStyleSwipeableRow.tsx rename to apps/common-app/src/new_api/components/swipeable/AppleStyleSwipeableRow.tsx diff --git a/apps/common-app/src/new_api/swipeable/GmailStyleSwipeableRow.tsx b/apps/common-app/src/new_api/components/swipeable/GmailStyleSwipeableRow.tsx similarity index 100% rename from apps/common-app/src/new_api/swipeable/GmailStyleSwipeableRow.tsx rename to apps/common-app/src/new_api/components/swipeable/GmailStyleSwipeableRow.tsx diff --git a/apps/common-app/src/new_api/swipeable/index.tsx b/apps/common-app/src/new_api/components/swipeable/index.tsx similarity index 100% rename from apps/common-app/src/new_api/swipeable/index.tsx rename to apps/common-app/src/new_api/components/swipeable/index.tsx diff --git a/apps/common-app/src/components/switchAndInput/index.tsx b/apps/common-app/src/new_api/components/switchAndInput/index.tsx similarity index 100% rename from apps/common-app/src/components/switchAndInput/index.tsx rename to apps/common-app/src/new_api/components/switchAndInput/index.tsx diff --git a/apps/common-app/src/new_api/hover_mouse/context_menu/index.tsx b/apps/common-app/src/new_api/hover_mouse/context_menu/index.tsx new file mode 100644 index 0000000000..e744afa564 --- /dev/null +++ b/apps/common-app/src/new_api/hover_mouse/context_menu/index.tsx @@ -0,0 +1,63 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + MouseButton, + usePanGesture, +} from 'react-native-gesture-handler'; + +export default function ContextMenuExample() { + const p1 = usePanGesture({ mouseButton: MouseButton.RIGHT }); + const p2 = usePanGesture({}); + const p3 = usePanGesture({}); + + return ( + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'space-around', + alignItems: 'center', + }, + + grandParent: { + width: 300, + height: 300, + backgroundColor: COLORS.NAVY, + }, + + parent: { + width: 200, + height: 200, + backgroundColor: COLORS.PURPLE, + }, + + child: { + width: 100, + height: 100, + backgroundColor: COLORS.KINDA_BLUE, + }, + + box: { + display: 'flex', + justifyContent: 'space-around', + alignItems: 'center', + borderRadius: 20, + }, +}); diff --git a/apps/common-app/src/new_api/hover_mouse/hover/index.tsx b/apps/common-app/src/new_api/hover_mouse/hover/index.tsx new file mode 100644 index 0000000000..ba3e4be77a --- /dev/null +++ b/apps/common-app/src/new_api/hover_mouse/hover/index.tsx @@ -0,0 +1,138 @@ +import { COLORS, commonStyles, Feedback } from '../../../common'; +import React, { RefObject, useRef } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { + GestureDetector, + HoverEffect, + useHoverGesture, +} from 'react-native-gesture-handler'; +import Animated, { + useAnimatedStyle, + useSharedValue, +} from 'react-native-reanimated'; + +function useColoredHover( + color: string, + feedbackRef: RefObject<{ showMessage: (msg: string) => void } | null> +) { + const hovered = useSharedValue(false); + + const style = useAnimatedStyle(() => ({ + opacity: hovered.value ? 0.5 : 1, + })); + + const gesture = useHoverGesture({ + onBegin: () => { + hovered.value = true; + feedbackRef.current?.showMessage('Hover begin ' + color); + }, + onActivate: () => { + console.log('hover start', color); + }, + onDeactivate: (_, success) => { + console.log('hover end', color, 'failed', !success); + }, + onFinalize: () => { + hovered.value = false; + console.log('hover finalize', color); + }, + effect: HoverEffect.LIFT, + disableReanimated: true, + }); + + return [gesture, style] as const; +} + +export default function Example() { + const feedbackRefUpper = useRef<{ showMessage: (msg: string) => void }>(null); + const feedbackRefLower = useRef<{ showMessage: (msg: string) => void }>(null); + + const [hover1, style1] = useColoredHover('red', feedbackRefUpper); + const [hover2, style2] = useColoredHover('green', feedbackRefUpper); + const [hover3, style3] = useColoredHover('red', feedbackRefLower); + const [hover4, style4] = useColoredHover('green', feedbackRefLower); + const [hover5, style5] = useColoredHover('blue', feedbackRefLower); + + return ( + + + Parent & child + + + + + + + + + + + + + Absolute positioning + + + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + spacer: { + height: 50, + }, + parentBox: { + width: 200, + height: 200, + backgroundColor: COLORS.RED, + elevation: 8, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 20, + }, + childBox: { + width: 100, + height: 100, + backgroundColor: COLORS.GREEN, + elevation: 8, + borderRadius: 20, + }, + absoluteContainer: { + width: 200, + height: 200, + alignItems: 'center', + justifyContent: 'center', + }, + absoluteRed: { + width: 200, + height: 200, + backgroundColor: COLORS.RED, + borderRadius: 20, + }, + absoluteNavy: { + width: 200, + height: 200, + backgroundColor: COLORS.NAVY, + position: 'absolute', + borderRadius: 20, + }, + absoluteGreen: { + width: 100, + height: 100, + backgroundColor: COLORS.GREEN, + position: 'absolute', + borderRadius: 20, + }, +}); diff --git a/apps/common-app/src/new_api/hover_mouse/hoverable_icons/index.tsx b/apps/common-app/src/new_api/hover_mouse/hoverable_icons/index.tsx new file mode 100644 index 0000000000..dd0de61310 --- /dev/null +++ b/apps/common-app/src/new_api/hover_mouse/hoverable_icons/index.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { + GestureDetector, + HoverEffect, + useHoverGesture, +} from 'react-native-gesture-handler'; +import Animated, { + useAnimatedStyle, + useFrameCallback, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; +import { Platform, StyleSheet } from 'react-native'; +import { commonStyles } from '../../../common'; + +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const SVG = require('../../../common_assets/hoverable_icons/svg.png'); +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const FREEZE = require('../../../common_assets/hoverable_icons/freeze.png'); +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const REA = require('../../../common_assets/hoverable_icons/rea.png'); +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const GH = require('../../../common_assets/hoverable_icons/gh.png'); +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const SCREENS = require('../../../common_assets/hoverable_icons/screens.png'); + +const images = [GH, REA, SCREENS, SVG, FREEZE]; +const SIZE = 100; + +function BoxReanimated(props: { source: any }) { + const scale = useSharedValue(1); + const offsetX = useSharedValue(0); + const targetOffsetX = useSharedValue(0); + const offsetY = useSharedValue(0); + const targetOffsetY = useSharedValue(0); + + useFrameCallback((frame) => { + offsetX.value += + ((targetOffsetX.value - offsetX.value) * + 0.15 * + (frame.timeSincePreviousFrame ?? 1)) / + 16; + offsetY.value += + ((targetOffsetY.value - offsetY.value) * + 0.15 * + (frame.timeSincePreviousFrame ?? 1)) / + 16; + }); + + const style = useAnimatedStyle(() => ({ + transform: [ + { scale: scale.value }, + { translateX: offsetX.value }, + { translateY: offsetY.value }, + ], + })); + + const hover = useHoverGesture({ + onBegin: () => { + scale.value = withTiming(1.15, { duration: 100 }); + }, + onUpdate: (e) => { + const oX = e.x - SIZE / 2; + const oY = e.y - SIZE / 2; + + targetOffsetX.value = Math.pow(Math.abs(oX), 0.3) * Math.sign(oX); + targetOffsetY.value = Math.pow(Math.abs(oY), 0.3) * Math.sign(oY); + }, + onFinalize: () => { + scale.value = withTiming(1, { duration: 100 }); + targetOffsetX.value = 0; + targetOffsetY.value = 0; + }, + }); + + return ( + + + + + + ); +} + +function BoxNative(props: { source: any }) { + const hover = useHoverGesture({ + effect: HoverEffect.LIFT, + }); + + return ( + + + + ); +} + +export default function Example() { + const BoxComponent = Platform.OS === 'ios' ? BoxNative : BoxReanimated; + + return ( + + + {images.map((source, index) => ( + + ))} + + + ); +} + +const styles = StyleSheet.create({ + image: { + overflow: 'visible', + width: SIZE, + height: SIZE, + marginHorizontal: 8, + }, + wrapper: { + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'row', + flexWrap: 'wrap', + }, +}); diff --git a/apps/common-app/src/new_api/hover_mouse/mouse_buttons/index.tsx b/apps/common-app/src/new_api/hover_mouse/mouse_buttons/index.tsx new file mode 100644 index 0000000000..9afa0de991 --- /dev/null +++ b/apps/common-app/src/new_api/hover_mouse/mouse_buttons/index.tsx @@ -0,0 +1,167 @@ +import React, { useRef } from 'react'; +import { + GestureDetector, + MouseButton, + Directions, + ScrollView, + useTapGesture, + usePanGesture, + useLongPressGesture, + useFlingGesture, +} from 'react-native-gesture-handler'; +import { StyleSheet, View, Text } from 'react-native'; +import { COLORS, Feedback } from '../../../common'; + +export default function Buttons() { + const feedbackRef = useRef<{ showMessage: (msg: string) => void }>(null); + + return ( + + + + + + + + + + + + + + + ); +} + +type TestsProps = { + name: string; + color: string; + mouseButton: MouseButton; + feedbackRef: React.RefObject<{ showMessage: (msg: string) => void } | null>; +}; + +function Tests({ name, color, mouseButton, feedbackRef }: TestsProps) { + const tap = useTapGesture({ + mouseButton, + onDeactivate: () => { + feedbackRef.current?.showMessage(`Tap with ${name}`); + }, + disableReanimated: true, + }); + + const pan = usePanGesture({ + mouseButton, + onUpdate: () => { + feedbackRef.current?.showMessage(`Panning with ${name}`); + }, + }); + + const longPress = useLongPressGesture({ + mouseButton, + onActivate: () => { + feedbackRef.current?.showMessage(`LongPress with ${name}`); + }, + }); + + const fling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton, + onActivate: () => { + feedbackRef.current?.showMessage(`Fling with ${name}`); + }, + }); + + const gestures = [tap, longPress, pan, fling]; + + const gestureLabels = ['Tap', 'LongPress', 'Pan', 'Fling']; + + return ( + + {name} + + {gestures.map((gesture, index) => ( + + + + {gestureLabels[index]} + + + + ))} + + + ); +} + +const styles = StyleSheet.create({ + scrollContent: { + flexGrow: 1, + }, + row: { + flexDirection: 'row', + justifyContent: 'space-around', + padding: 20, + }, + testColumn: { + alignItems: 'center', + marginHorizontal: 10, + }, + title: { + fontSize: 16, + fontWeight: 'bold', + marginBottom: 10, + textAlign: 'center', + }, + gesturesColumn: { + flexDirection: 'column', + alignItems: 'center', + }, + gestureItem: { + marginVertical: 10, + alignItems: 'center', + }, + gestureLabel: { + fontSize: 12, + fontWeight: '600', + color: 'white', + textAlign: 'center', + }, + box: { + width: 75, + height: 75, + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + }, + feedbackContainer: { + alignItems: 'center', + padding: 20, + }, +}); diff --git a/apps/common-app/src/new_api/hover_mouse/stylus_data/index.tsx b/apps/common-app/src/new_api/hover_mouse/stylus_data/index.tsx new file mode 100644 index 0000000000..e413473315 --- /dev/null +++ b/apps/common-app/src/new_api/hover_mouse/stylus_data/index.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { StyleSheet, View, Image } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +// eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires +const GH = require('../../../common_assets/hoverable_icons/gh.png'); + +export default function StylusData() { + const scaleFactor = useSharedValue(0); + const rotationXFactor = useSharedValue(0); + const rotationYFactor = useSharedValue(0); + + const pan = usePanGesture({ + onBegin: (e) => { + if (!e.stylusData) { + return; + } + + scaleFactor.value = e.stylusData.pressure; + rotationYFactor.value = e.stylusData.tiltX; + rotationXFactor.value = e.stylusData.tiltY; + }, + onUpdate: (e) => { + if (!e.stylusData) { + return; + } + + scaleFactor.value = e.stylusData.pressure; + rotationYFactor.value = e.stylusData.tiltX; + rotationXFactor.value = e.stylusData.tiltY; + }, + onFinalize: (e) => { + if (!e.stylusData) { + return; + } + + scaleFactor.value = withTiming(0, { duration: 250 }); + rotationXFactor.value = withTiming(0, { duration: 250 }); + rotationYFactor.value = withTiming(0, { duration: 250 }); + }, + minDistance: 0, + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { scale: 1 + scaleFactor.value }, + { rotateY: `${rotationYFactor.value}deg` }, + { rotateX: `${rotationXFactor.value}deg` }, + ], + }; + }); + + return ( + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + + ball: { + width: 200, + height: 200, + borderWidth: 2, + borderRadius: 100, + backgroundColor: '#c8e3f7', + + display: 'flex', + justifyContent: 'space-around', + alignItems: 'center', + }, +}); diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx new file mode 100644 index 0000000000..96673b5446 --- /dev/null +++ b/apps/common-app/src/new_api/index.tsx @@ -0,0 +1,96 @@ +import AnimatedExample from './showcase/animated'; +import BottomSheetExample from './showcase/bottom_sheet'; +import NestedTextExample from './showcase/nested_text/nested_text'; +import OverlapExample from './showcase/overlap'; +import SharedValueExample from './showcase/shared_value'; +import SvgExample from './showcase/svg'; + +import CameraExample from './complicated/camera'; +import ChatHeadsExample from './complicated/chat_heads'; +import LockExample from './complicated/lock'; +import VelocityExample from './complicated/velocity_test'; + +import ContextMenuExample from './hover_mouse/context_menu'; +import HoverIconsExample from './hover_mouse/hover'; +import HoverableIconsExample from './hover_mouse/hoverable_icons'; +import MouseButtonsExample from './hover_mouse/mouse_buttons'; +import StylusDataExample from './hover_mouse/stylus_data'; + +import FlingExample from './simple/fling'; +import HoverExample from './simple/hover'; +import LongPressExample from './simple/longPress'; +import PanExample from './simple/pan'; +import PinchExample from './simple/pinch'; +import RotationExample from './simple/rotation'; +import TapExample from './simple/tap'; + +import ButtonsExample from './components/buttons'; +import ReanimatedDrawerLayout from './components/drawer'; +import FlatListExample from './components/flatlist'; +import ScrollViewExample from './components/scrollview'; +import Swipeable from './components/swipeable/index'; +import SwitchTextInputExample from './components/switchAndInput'; + +import { ExamplesSection } from '../common'; +import EmptyExample from '../empty'; + +export const NEW_EXAMPLES: ExamplesSection[] = [ + { + sectionTitle: 'Empty', + data: [{ name: 'Empty Example', component: EmptyExample }], + }, + { + sectionTitle: 'Simple Gestures', + data: [ + { name: 'Fling', component: FlingExample }, + { name: 'Tap', component: TapExample }, + { name: 'LongPress', component: LongPressExample }, + { name: 'Hover', component: HoverExample }, + { name: 'Pinch', component: PinchExample }, + { name: 'Rotation', component: RotationExample }, + { name: 'Pan', component: PanExample }, + ], + }, + { + sectionTitle: 'Showcase', + data: [ + { name: 'Svg', component: SvgExample }, + { name: 'Nested Text', component: NestedTextExample }, + { name: 'Shared Value', component: SharedValueExample }, + { name: 'Bottom Sheet', component: BottomSheetExample }, + { name: 'Overlap', component: OverlapExample }, + { name: 'Animated', component: AnimatedExample }, + ], + }, + { + sectionTitle: 'Hover and mouse', + data: [ + { name: 'Stylus Data', component: StylusDataExample }, + { name: 'Context Menu', component: ContextMenuExample }, + { name: 'Hover Icons', component: HoverIconsExample }, + { name: 'Hoverable Icons', component: HoverableIconsExample }, + { name: 'Mouse Buttons', component: MouseButtonsExample }, + ], + }, + { + sectionTitle: 'Complicated', + data: [ + { name: 'Lock', component: LockExample }, + { name: 'Velocity Test', component: VelocityExample }, + { name: 'Chat Heads', component: ChatHeadsExample }, + { name: 'Camera', component: CameraExample }, + ], + }, + + { + sectionTitle: 'Components', + data: [ + { name: 'FlatList example', component: FlatListExample }, + { name: 'ScrollView example', component: ScrollViewExample }, + { name: 'Buttons example', component: ButtonsExample }, + { name: 'Switch & TextInput', component: SwitchTextInputExample }, + { name: 'Reanimated Swipeable', component: Swipeable }, + { name: 'Reanimated Drawer Layout', component: ReanimatedDrawerLayout }, + ], + }, +]; diff --git a/apps/common-app/src/new_api/showcase/animated/index.tsx b/apps/common-app/src/new_api/showcase/animated/index.tsx new file mode 100644 index 0000000000..6102968a20 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/animated/index.tsx @@ -0,0 +1,161 @@ +import { COLORS, commonStyles } from '../../../common'; +import React, { useRef } from 'react'; +import { View, Text, StyleSheet, Animated, Dimensions } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; + +const { width } = Dimensions.get('window'); + +export default function MinimalCard() { + const translateX = useRef(new Animated.Value(0)).current; + const translateY = useRef(new Animated.Value(0)).current; + const scale = useRef(new Animated.Value(1)).current; + const rotation = useRef(new Animated.Value(0)).current; + const colorProgress = useRef(new Animated.Value(0)).current; + const normalisedRotation = useRef(new Animated.Value(1)).current; + const opacity = useRef(new Animated.Value(1)).current; + + let offsetX = 0; + let offsetY = 0; + + const panGesture = usePanGesture({ + onActivate: () => { + Animated.spring(scale, { + toValue: 1.05, + useNativeDriver: true, + }).start(); + }, + onUpdate: (e) => { + translateX.setValue(e.translationX + offsetX); + translateY.setValue(e.translationY + offsetY); + const rotationValue = ((e.translationX + offsetX) / width) * 20; + rotation.setValue(rotationValue); + normalisedRotation.setValue( + rotationValue < 0 ? rotationValue - 1 : rotationValue + 1 + ); + }, + onDeactivate: (e) => { + Animated.spring(scale, { + toValue: 1, + useNativeDriver: true, + }).start(); + + if (Math.abs(e.translationX + offsetX) > width * 0.3) { + const direction = e.translationX + offsetX > 0 ? 1 : -1; + + Animated.parallel([ + Animated.timing(translateX, { + toValue: direction * width * 1.5, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }), + ]).start(() => { + translateX.setValue(0); + translateY.setValue(0); + rotation.setValue(0); + normalisedRotation.setValue(1); + + Animated.parallel([ + Animated.spring(scale, { + toValue: 1, + friction: 8, + tension: 40, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + ]).start(); + + offsetX = 0; + offsetY = 0; + }); + } else { + Animated.spring(translateX, { + toValue: 0, + useNativeDriver: true, + }).start(); + Animated.spring(translateY, { + toValue: 0, + useNativeDriver: true, + }).start(); + Animated.spring(rotation, { + toValue: 0, + useNativeDriver: true, + }).start(); + } + + offsetX = 0; + offsetY = 0; + }, + onFinalize: (e) => { + const normalisedRotationValue = e.absoluteX < 0 ? -1 : 1; + Animated.timing(normalisedRotation, { + toValue: normalisedRotationValue, + duration: 500, + useNativeDriver: true, + }).start(); + Animated.timing(colorProgress, { + toValue: 0, + duration: 500, + useNativeDriver: true, + }).start(); + }, + disableReanimated: true, + }); + + const rotateZ = rotation.interpolate({ + inputRange: [-20, 20], + outputRange: ['-20deg', '20deg'], + }); + + const finalBackgroundColor = normalisedRotation.interpolate({ + inputRange: [-10, 0, 10], + outputRange: [COLORS.RED, COLORS.NAVY, COLORS.GREEN], + extrapolate: 'clamp', + }); + + return ( + + + + Drag me + + + + ); +} + +const styles = StyleSheet.create({ + card: { + width: 280, + height: 380, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 24, + color: '#fff', + fontWeight: '500', + }, +}); diff --git a/apps/common-app/src/new_api/showcase/bottom_sheet/index.tsx b/apps/common-app/src/new_api/showcase/bottom_sheet/index.tsx new file mode 100644 index 0000000000..83a02847b1 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/bottom_sheet/index.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { + Dimensions, + NativeScrollEvent, + NativeSyntheticEvent, + StyleSheet, + View, +} from 'react-native'; +import { + GestureDetector, + PanGestureEvent, + useSimultaneousGestures, + usePanGesture, + useTapGesture, + useNativeGesture, +} from 'react-native-gesture-handler'; +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, +} from 'react-native-reanimated'; +import { COLORS, commonStyles, LoremIpsum } from '../../../common'; + +const HEADER_HEIGTH = 50; +const windowHeight = Dimensions.get('window').height; +const SNAP_POINTS_FROM_TOP = [50, windowHeight * 0.4, windowHeight * 0.8]; + +const FULLY_OPEN_SNAP_POINT = SNAP_POINTS_FROM_TOP[0]; +const CLOSED_SNAP_POINT = SNAP_POINTS_FROM_TOP[SNAP_POINTS_FROM_TOP.length - 1]; + +function Example() { + const [snapPoint, setSnapPoint] = useState(CLOSED_SNAP_POINT); + const translationY = useSharedValue(0); + const scrollOffset = useSharedValue(0); + const bottomSheetTranslateY = useSharedValue(CLOSED_SNAP_POINT); + + const onHandlerDeactivate = (e: PanGestureEvent) => { + const dragToss = 0.01; + const endOffsetY = + bottomSheetTranslateY.value + translationY.value + e.velocityY * dragToss; + + // calculate nearest snap point + let destSnapPoint = FULLY_OPEN_SNAP_POINT; + + if ( + snapPoint === FULLY_OPEN_SNAP_POINT && + endOffsetY < FULLY_OPEN_SNAP_POINT + ) { + return; + } + for (const snapPoint of SNAP_POINTS_FROM_TOP) { + const distFromSnap = Math.abs(snapPoint - endOffsetY); + if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) { + destSnapPoint = snapPoint; + } + } + + // update current translation to be able to animate withSpring to snapPoint + bottomSheetTranslateY.value = + bottomSheetTranslateY.value + translationY.value; + translationY.value = 0; + + bottomSheetTranslateY.value = withSpring(destSnapPoint, { + mass: 0.5, + }); + runOnJS(setSnapPoint)(destSnapPoint); + }; + const panGesture = usePanGesture({ + onUpdate: (e) => { + // when bottom sheet is not fully opened scroll offset should not influence + // its position (prevents random snapping when opening bottom sheet when + // the content is already scrolled) + if (snapPoint === FULLY_OPEN_SNAP_POINT) { + translationY.value = e.translationY - scrollOffset.value; + } else { + translationY.value = e.translationY; + } + }, + onDeactivate: onHandlerDeactivate, + }); + + const blockScrollUntilAtTheTop = useTapGesture({ + maxDeltaY: snapPoint - FULLY_OPEN_SNAP_POINT, + maxDuration: 100000, + simultaneousWith: panGesture, + }); + + const headerGesture = usePanGesture({ + onUpdate: (e) => { + translationY.value = e.translationY; + }, + onDeactivate: onHandlerDeactivate, + }); + + const scrollViewGesture = useNativeGesture({ + requireToFail: blockScrollUntilAtTheTop, + }); + + const bottomSheetAnimatedStyle = useAnimatedStyle(() => { + const translateY = bottomSheetTranslateY.value + translationY.value; + + const minTranslateY = Math.max(FULLY_OPEN_SNAP_POINT, translateY); + const clampedTranslateY = Math.min(CLOSED_SNAP_POINT, minTranslateY); + return { + transform: [{ translateY: clampedTranslateY }], + }; + }); + + const simultanousGesture = useSimultaneousGestures( + panGesture, + scrollViewGesture + ); + + return ( + + + + + + + + + + ) => { + scrollOffset.value = e.nativeEvent.contentOffset.y; + }}> + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + header: { + height: HEADER_HEIGTH, + backgroundColor: COLORS.NAVY, + }, + bottomSheet: { + ...StyleSheet.absoluteFillObject, + backgroundColor: COLORS.KINDA_BLUE, + }, +}); + +export default Example; diff --git a/apps/common-app/src/new_api/showcase/nested_text/nested_text.tsx b/apps/common-app/src/new_api/showcase/nested_text/nested_text.tsx new file mode 100644 index 0000000000..861d1e237b --- /dev/null +++ b/apps/common-app/src/new_api/showcase/nested_text/nested_text.tsx @@ -0,0 +1,115 @@ +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + Gesture, + GestureDetector, + InterceptingGestureDetector, + VirtualGestureDetector, + useTapGesture, +} from 'react-native-gesture-handler'; + +import { COLORS, commonStyles, Feedback } from '../../../common'; + +function NativeDetectorExample() { + const feedbackRef = React.useRef<{ showMessage: (msg: string) => void }>( + null + ); + + const tapAll = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped on all text'); + }, + disableReanimated: true, + }); + + const tapFirstPart = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped on "try tapping on this part"'); + }, + disableReanimated: true, + }); + + const tapSecondPart = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped on "or on this part"'); + }, + disableReanimated: true, + }); + + return ( + + + Native Detector example - this one should work + + + + Some text example running with RNGH + + + try tapping on this part + + + + + or on this part + + + this part is not special :( + + + + + ); +} + +function LegacyDetectorExample() { + const feedbackRef = React.useRef<{ showMessage: (msg: string) => void }>( + null + ); + + const tapAll = Gesture.Tap().onStart(() => { + feedbackRef.current?.showMessage('Tapped on all text'); + }); + + const tapFirstPart = Gesture.Tap().onStart(() => { + feedbackRef.current?.showMessage('Tapped on "try tapping on this part"'); + }); + + const tapSecondPart = Gesture.Tap().onStart(() => { + feedbackRef.current?.showMessage('Tapped on "or this part"'); + }); + + return ( + + + Legacy Detector example - this one should only work on web + + + + Some text example running with RNGH + + + try tapping on this part + + + + + or on this part + + + this part is not special :( + + + + + ); +} + +export default function NativeTextExample() { + return ( + + + + + ); +} diff --git a/apps/common-app/src/new_api/showcase/overlap/index.tsx b/apps/common-app/src/new_api/showcase/overlap/index.tsx new file mode 100644 index 0000000000..fa2cd8c32d --- /dev/null +++ b/apps/common-app/src/new_api/showcase/overlap/index.tsx @@ -0,0 +1,152 @@ +import { COLORS, commonStyles, Feedback } from '../../../common'; +import React, { useRef } from 'react'; +import { StyleSheet, View, Text } from 'react-native'; +import { + InterceptingGestureDetector, + useTapGesture, + VirtualGestureDetector, +} from 'react-native-gesture-handler'; + +function Box(props: { + color: string; + overlap?: boolean; + children?: React.ReactNode; + elevated: boolean; +}) { + return ( + + {props.children} + + ); +} + +function OverlapSiblings() { + const feedbackRef = useRef<{ showMessage: (msg: string) => void }>(null); + const [elevated, setElevated] = React.useState(''); + + const tapPurple = useTapGesture({ + onDeactivate: (_e, success) => { + if (success) { + setElevated('purple'); + feedbackRef.current?.showMessage('Tapped purple'); + } + }, + disableReanimated: true, + }); + + const tapBlue = useTapGesture({ + onDeactivate: (_e, success) => { + if (success) { + setElevated('blue'); + feedbackRef.current?.showMessage('Tapped blue'); + } + }, + disableReanimated: true, + }); + + return ( + + + Overlap Siblings + + + + + + + + + + + + + + ); +} + +function OverlapParents() { + const feedbackRef = useRef<{ showMessage: (msg: string) => void }>(null); + const [elevated, setElevated] = React.useState(''); + + const tapRed = useTapGesture({ + onDeactivate: (_e, success) => { + if (success) { + feedbackRef.current?.showMessage('Tapped purple'); + setElevated('purple'); + } + }, + disableReanimated: true, + }); + + const tapGreen = useTapGesture({ + onDeactivate: (_e, success) => { + if (success) { + feedbackRef.current?.showMessage('Tapped blue'); + setElevated('blue'); + } + }, + disableReanimated: true, + }); + + return ( + + + Overlap Child + + + + + + + + + + + + + + + + ); +} + +export default function Example() { + return ( + + + + + ); +} + +const styles = StyleSheet.create({ + row: { + padding: 30, + alignItems: 'center', + height: 225, + marginBottom: 60, + }, + overlap: { + position: 'absolute', + left: 75, + top: 75, + }, + elevated: { + zIndex: 10, + elevation: 16, + shadowColor: 'black', + shadowOffset: { width: 0, height: 3 }, + shadowOpacity: 0.5, + shadowRadius: 8, + }, +}); diff --git a/apps/common-app/src/new_api/showcase/shared_value/index.tsx b/apps/common-app/src/new_api/showcase/shared_value/index.tsx new file mode 100644 index 0000000000..9450f06695 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/shared_value/index.tsx @@ -0,0 +1,89 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { Text, View } from 'react-native'; +import { + GestureDetector, + useLongPressGesture, + usePanGesture, + useSimultaneousGestures, +} from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + interpolateColor, + withTiming, +} from 'react-native-reanimated'; + +export default function PanExample() { + const translateX = useSharedValue(0); + const translateY = useSharedValue(0); + const colorProgress = useSharedValue(0); + const offsetX = useSharedValue(0); + const offsetY = useSharedValue(0); + const maxLongPressDistance = useSharedValue(20); + const panGesture = usePanGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { duration: 150 }); + }, + onUpdate: (event) => { + translateX.value = offsetX.value + event.translationX; + translateY.value = offsetY.value + event.translationY; + maxLongPressDistance.value = Math.abs(event.translationY) * 2 + 20; + }, + onFinalize: () => { + offsetX.value = translateX.value; + offsetY.value = translateY.value; + }, + }); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: () => { + colorProgress.value = withTiming(2, { + duration: 100, + }); + }, + onFinalize: () => { + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + minDuration: 1000, + maxDistance: maxLongPressDistance, + }); + + const gestures = useSimultaneousGestures(longPressGesture, panGesture); + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1, 2], + [COLORS.NAVY, COLORS.PURPLE, COLORS.KINDA_BLUE] + ); + return { + transform: [ + { translateX: translateX.value }, + { translateY: translateY.value }, + ], + backgroundColor, + }; + }); + + return ( + + + + + + + + The ball has simultanous pan and longPress gestures. Upon update pan + changes minDistance of longPress, such that longPress will fail if is + moved horizontally. + + + ); +} diff --git a/apps/common-app/src/new_api/showcase/svg/index.tsx b/apps/common-app/src/new_api/showcase/svg/index.tsx new file mode 100644 index 0000000000..d069777eb0 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/svg/index.tsx @@ -0,0 +1,68 @@ +import { COLORS, commonStyles, Feedback } from '../../../common'; +import React, { useRef } from 'react'; +import { View } from 'react-native'; +import { + InterceptingGestureDetector, + useTapGesture, + VirtualGestureDetector, +} from 'react-native-gesture-handler'; +import Svg, { Circle, Rect } from 'react-native-svg'; + +export default function LogicDetectorExample() { + const feedbackRef = useRef<{ showMessage: (msg: string) => void }>(null); + + const circleElementTap = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped circle!'); + }, + disableReanimated: true, + }); + + const rectElementTap = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped parallelogram!'); + }, + disableReanimated: true, + }); + + const containerTap = useTapGesture({ + onActivate: () => { + feedbackRef.current?.showMessage('Tapped container!'); + }, + disableReanimated: true, + }); + + // onPress must be set to enable gesture recognition on svg + // eslint-disable-next-line @typescript-eslint/no-empty-function + const noop = () => {}; + + return ( + + + + + + + + + + + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/fling/index.tsx b/apps/common-app/src/new_api/simple/fling/index.tsx new file mode 100644 index 0000000000..0859708f7b --- /dev/null +++ b/apps/common-app/src/new_api/simple/fling/index.tsx @@ -0,0 +1,62 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { + Directions, + GestureDetector, + useFlingGesture, +} from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + Easing, + interpolateColor, +} from 'react-native-reanimated'; + +export default function FlingExample() { + const position = useSharedValue(0); + const beginPosition = useSharedValue(0); + const colorProgress = useSharedValue(0); + + const flingGesture = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + onBegin: (e) => { + beginPosition.value = e.x; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: (e) => { + const direction = Math.sign(e.x - beginPosition.value); + position.value = withTiming(position.value + direction * 50, { + duration: 300, + easing: Easing.bounce, + }); + }, + onFinalize: () => { + colorProgress.value = withTiming(0, { + duration: 400, + }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [{ translateX: position.value }], + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.PURPLE] + ), + }; + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/hover/index.tsx b/apps/common-app/src/new_api/simple/hover/index.tsx new file mode 100644 index 0000000000..60e1402e50 --- /dev/null +++ b/apps/common-app/src/new_api/simple/hover/index.tsx @@ -0,0 +1,45 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { GestureDetector, useHoverGesture } from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + interpolateColor, +} from 'react-native-reanimated'; + +export default function TapExample() { + const colorProgress = useSharedValue(0); + + const animatedStyle = useAnimatedStyle(() => { + return { + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + const tapGesture = useHoverGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onFinalize: () => { + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/longPress/index.tsx b/apps/common-app/src/new_api/simple/longPress/index.tsx new file mode 100644 index 0000000000..90902d4df7 --- /dev/null +++ b/apps/common-app/src/new_api/simple/longPress/index.tsx @@ -0,0 +1,63 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { + GestureDetector, + useLongPressGesture, +} from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + interpolateColor, +} from 'react-native-reanimated'; + +export default function LongPressExample() { + const colorProgress = useSharedValue(0); + + const finalise_color = useSharedValue(COLORS.PURPLE); + + const animatedStyle = useAnimatedStyle(() => { + return { + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1, 2], + [COLORS.NAVY, finalise_color.value, COLORS.KINDA_BLUE] + ), + }; + }); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: () => { + colorProgress.value = withTiming(2, { + duration: 100, + }); + }, + onFinalize: (_, success) => { + finalise_color.value = success ? COLORS.GREEN : COLORS.RED; + colorProgress.value = 1; + colorProgress.value = withTiming( + 0, + { + duration: 300, + }, + () => { + finalise_color.value = COLORS.PURPLE; + } + ); + }, + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/pan/index.tsx b/apps/common-app/src/new_api/simple/pan/index.tsx new file mode 100644 index 0000000000..95eba24004 --- /dev/null +++ b/apps/common-app/src/new_api/simple/pan/index.tsx @@ -0,0 +1,55 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + interpolateColor, + withTiming, +} from 'react-native-reanimated'; + +export default function PanExample() { + const translateX = useSharedValue(0); + const translateY = useSharedValue(0); + const colorProgress = useSharedValue(0); + const offsetX = useSharedValue(0); + const offsetY = useSharedValue(0); + + const panGesture = usePanGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { duration: 150 }); + }, + onUpdate: (event) => { + translateX.value = offsetX.value + event.translationX; + translateY.value = offsetY.value + event.translationY; + }, + onFinalize: () => { + offsetX.value = translateX.value; + offsetY.value = translateY.value; + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { translateX: translateX.value }, + { translateY: translateY.value }, + ], + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/pinch/index.tsx b/apps/common-app/src/new_api/simple/pinch/index.tsx new file mode 100644 index 0000000000..ef0699bc51 --- /dev/null +++ b/apps/common-app/src/new_api/simple/pinch/index.tsx @@ -0,0 +1,46 @@ +import { commonStyles, COLORS } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { GestureDetector, usePinchGesture } from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + interpolateColor, + withTiming, +} from 'react-native-reanimated'; + +export default function PinchExample() { + const scale = useSharedValue(1); + const colorProgress = useSharedValue(0); + const pinchGesture = usePinchGesture({ + onUpdate: (event) => { + scale.value = event.scale; + + const p = Math.min(Math.max((event.scale - 1) / 0.5, 0), 1); + colorProgress.value = p; + }, + onDeactivate: () => { + scale.value = withTiming(1, { duration: 150 }); + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [{ scale: scale.value }], + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/rotation/index.tsx b/apps/common-app/src/new_api/simple/rotation/index.tsx new file mode 100644 index 0000000000..cbdafbab73 --- /dev/null +++ b/apps/common-app/src/new_api/simple/rotation/index.tsx @@ -0,0 +1,49 @@ +import { COLORS, commonStyles } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { + GestureDetector, + useRotationGesture, +} from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + interpolateColor, + withTiming, +} from 'react-native-reanimated'; + +export default function RotationExample() { + const rotation = useSharedValue(0); + const colorProgress = useSharedValue(0); + + const rotationGesture = useRotationGesture({ + onUpdate: (event) => { + rotation.value = event.rotation; + const p = Math.min(Math.max(Math.abs(event.rotation) / Math.PI, 0), 1); + colorProgress.value = p; + }, + onDeactivate: () => { + rotation.value = withTiming(0, { duration: 150 }); + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [{ rotateZ: `${rotation.value}rad` }], + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + return ( + + + + + + ); +} diff --git a/apps/common-app/src/new_api/simple/tap/index.tsx b/apps/common-app/src/new_api/simple/tap/index.tsx new file mode 100644 index 0000000000..4bc1ef04cf --- /dev/null +++ b/apps/common-app/src/new_api/simple/tap/index.tsx @@ -0,0 +1,45 @@ +import { commonStyles, COLORS } from '../../../common'; +import React from 'react'; +import { View } from 'react-native'; +import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + interpolateColor, +} from 'react-native-reanimated'; + +export default function TapExample() { + const colorProgress = useSharedValue(0); + + const animatedStyle = useAnimatedStyle(() => { + return { + backgroundColor: interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ), + }; + }); + + const tapGesture = useTapGesture({ + onBegin: () => { + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onFinalize: () => { + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + }); + + return ( + + + + + + ); +} diff --git a/apps/expo-example/package.json b/apps/expo-example/package.json index 9497958460..865a8dce5c 100644 --- a/apps/expo-example/package.json +++ b/apps/expo-example/package.json @@ -27,12 +27,12 @@ "react-dom": "19.1.0", "react-native": "0.81.4", "react-native-gesture-handler": "workspace:*", - "react-native-reanimated": "4.0.2", + "react-native-reanimated": "^4.2.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", "react-native-web": "^0.21.0", - "react-native-worklets": "0.4.0" + "react-native-worklets": "^0.7.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/yarn.lock b/yarn.lock index ee7043f857..7fbc2da0f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8753,12 +8753,12 @@ __metadata: react-dom: "npm:19.1.0" react-native: "npm:0.81.4" react-native-gesture-handler: "workspace:*" - react-native-reanimated: "npm:4.0.2" + react-native-reanimated: "npm:^4.2.1" react-native-safe-area-context: "npm:~5.6.0" react-native-screens: "npm:~4.16.0" react-native-svg: "npm:15.12.1" react-native-web: "npm:^0.21.0" - react-native-worklets: "npm:0.4.0" + react-native-worklets: "npm:^0.7.1" typescript: "npm:~5.9.2" languageName: unknown linkType: soft @@ -14705,21 +14705,6 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:4.0.2": - version: 4.0.2 - resolution: "react-native-reanimated@npm:4.0.2" - dependencies: - react-native-is-edge-to-edge: "npm:^1.2.1" - semver: "npm:7.7.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - react: "*" - react-native: "*" - react-native-worklets: ">=0.4.0" - checksum: 10c0/422aa8308c586dd37d53760860aa963ef5f569d9fce3c03a3ce3d5592e5c93d6bd399567f616b2db2c3156712d9bc3c99d4380f454b8ed9dceedb525dd171ee9 - languageName: node - linkType: hard - "react-native-reanimated@npm:^3.18.0": version: 3.19.4 resolution: "react-native-reanimated@npm:3.19.4" @@ -14758,6 +14743,20 @@ __metadata: languageName: node linkType: hard +"react-native-reanimated@npm:^4.2.1": + version: 4.2.1 + resolution: "react-native-reanimated@npm:4.2.1" + dependencies: + react-native-is-edge-to-edge: "npm:1.2.1" + semver: "npm:7.7.3" + peerDependencies: + react: "*" + react-native: "*" + react-native-worklets: ">=0.7.0" + checksum: 10c0/1458fb634d374125931c5815b2b0941e12b1a880ba12b4ff6bba12b6c101e0ce1f5c32a2226034ae762558e38077ae112d8498eb2c8af6bd967524c7e7f87c6b + languageName: node + linkType: hard + "react-native-safe-area-context@npm:5.4.0": version: 5.4.0 resolution: "react-native-safe-area-context@npm:5.4.0" @@ -14852,28 +14851,6 @@ __metadata: languageName: node linkType: hard -"react-native-worklets@npm:0.4.0": - version: 0.4.0 - resolution: "react-native-worklets@npm:0.4.0" - dependencies: - "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" - "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-classes": "npm:^7.0.0-0" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" - "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" - "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" - "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" - "@babel/preset-typescript": "npm:^7.16.7" - convert-source-map: "npm:^2.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - react: "*" - react-native: "*" - checksum: 10c0/768ef67c1701f2354cda48f44ea337fd57cfa463acdeb65dc7808461aaa92a56c0f33d5ac6880d6d22cc5765b6751caa45307402b20f6575d3183b7029e3567a - languageName: node - linkType: hard - "react-native-worklets@npm:^0.7.1": version: 0.7.1 resolution: "react-native-worklets@npm:0.7.1" @@ -15598,15 +15575,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.7.2": - version: 7.7.2 - resolution: "semver@npm:7.7.2" - bin: - semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea - languageName: node - linkType: hard - "semver@npm:7.7.3, semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.7.3 resolution: "semver@npm:7.7.3"