diff --git a/apps/common-app/App.tsx b/apps/common-app/App.tsx index 4edb8aad23..dc71db1237 100644 --- a/apps/common-app/App.tsx +++ b/apps/common-app/App.tsx @@ -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] = React.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]) { + 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] = React.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,6 +244,10 @@ const styles = StyleSheet.create({ text: { color: 'black', }, + aligned: { + textAlign: 'center', + alignSelf: 'center', + }, list: {}, separator: { height: 2, @@ -435,18 +262,15 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'space-between', }, - buttonContent: { + settingsButton: { flex: 1, + paddingHorizontal: 20, flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - autoOpenSetting: { - margin: 16, - borderRadius: 16, + margin: 12, + borderRadius: 10, backgroundColor: '#eef0ff', - paddingHorizontal: 16, - justifyContent: 'space-between', + padding: 10, + justifyContent: 'center', elevation: 8, ...(Platform.OS !== 'macos' ? { @@ -457,8 +281,19 @@ 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: 8, + }, }); diff --git a/apps/common-app/src/common.tsx b/apps/common-app/src/common.tsx index 0a7436e840..23b37b8e14 100644 --- a/apps/common-app/src/common.tsx +++ b/apps/common-app/src/common.tsx @@ -1,10 +1,60 @@ -import React from 'react'; -import { Text, StyleSheet, ViewStyle, StyleProp } from 'react-native'; +import React, { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; +import { + Text, + StyleSheet, + ViewStyle, + StyleProp, + View, + Platform, +} from 'react-native'; +import Animated, { + Easing, + runOnJS, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +export interface Example { + name: string; + component: React.ComponentType; + unsupportedPlatforms?: Set; +} + +export interface ExamplesSection { + sectionTitle: string; + data: Example[]; +} 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 +75,101 @@ export class LoremIpsum extends React.Component { } } +interface InfoSectionProps { + description: string; +} + +export function InfoSection({ description }: InfoSectionProps) { + return ( + + {description} + + ); +} + +type FeedbackProps = { + duration?: number; +}; + +export type FeedbackHandle = { + showMessage: (message: string) => void; +}; + +export const Feedback = forwardRef( + ({ duration = 1000 }, ref) => { + 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) => { + 'worklet'; + runOnJS(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..7aec0f0ee7 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; 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 89% 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..f9d4b73e43 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; 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/index.tsx b/apps/common-app/src/new_api/complicated/camera/index.tsx new file mode 100644 index 0000000000..4c1fc3e5e9 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/camera/index.tsx @@ -0,0 +1,287 @@ +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 Animated, { + SharedValue, + runOnJS, + useAnimatedProps, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; +import { Circle, Svg } from 'react-native-svg'; +import AnimatedCameraView from '../../../common_assets/AnimatedCameraView/AnimatedCameraView'; +import { COLORS } from '../../../common'; + +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; +const RECORD_INDICATOR_STROKE = 10; + +const AnimatedCircle = Animated.createAnimatedComponent(Circle); + +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) => { + 'worklet'; + selectedFilter.value -= e.changeX / FILTER_SIZE; + }, + onDeactivate: () => { + 'worklet'; + 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: () => { + 'worklet'; + runOnJS(takePhoto)(); + captureProgress.value = withTiming(0, { duration: 1000 }); + }, + }); + + const takeVideoGesture = useLongPressGesture({ + shouldCancelWhenOutside: false, + maxDistance: 10000, + onActivate: () => { + 'worklet'; + runOnJS(startRecording)(); + captureProgress.value = withTiming(1, { duration: VIDEO_DURATION }); + }, + onDeactivate: () => { + 'worklet'; + runOnJS(stopRecording)(); + captureProgress.value = 0; + }, + }); + + const panZoomGesture = usePanGesture({ + shouldCancelWhenOutside: false, + requireToFail: filterChangeGesture, + onUpdate: (e) => { + 'worklet'; + zoom.value = Math.max(1, Math.min(2, zoom.value - e.changeY / 500)); + }, + }); + + const pinchZoomGesture = usePinchGesture({ + onUpdate: (e) => { + 'worklet'; + zoom.value = Math.max( + 1, + Math.min(2, zoom.value * ((e.scaleChange - 1) * 0.2 + 1)) + ); + console.log(zoom.value); + }, + }); + + const changeCameraGesture = useTapGesture({ + numberOfTaps: 2, + onDeactivate: () => { + 'worklet'; + setFacing((f) => (f === 'back' ? 'front' : 'back')); + }, + disableReanimated: true, + }); + + const raceGesture = useCompetingGestures( + pinchZoomGesture, + changeCameraGesture + ); + const simultanousGesture = useSimultaneousGestures( + panZoomGesture, + takeVideoGesture + ); + const exclusiveGesture = useExclusiveGestures( + simultanousGesture, + takePhotoGesture + ); + return ( + + + + + + + + + + + + + + + ); +} + +interface FilterCarouselProps { + filters: string[]; + selected: SharedValue; +} + +function FilterCarousel(props: FilterCarouselProps) { + const style = useAnimatedStyle(() => { + return { + flexDirection: 'row', + position: 'absolute', + left: '50%', + gap: FILTER_SIZE * 0.4, + transform: [ + { + translateX: + -FILTER_SIZE / 2 - FILTER_SIZE * 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 ( + + ); +} + +interface CaptureButtonProps { + progress: SharedValue; +} + +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({ + container: { + flex: 1, + }, + carouselContainer: { + position: 'absolute', + left: 0, + bottom: 32, + height: CAROUSEL_SIZE, + width: '100%', + justifyContent: 'center', + }, + 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/chat_heads/index.tsx b/apps/common-app/src/new_api/complicated/chat_heads/index.tsx new file mode 100644 index 0000000000..2ade06d1c5 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/chat_heads/index.tsx @@ -0,0 +1,213 @@ +import React, { useState } from 'react'; +import { StyleSheet, ImageStyle, LayoutChangeEvent } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; +import Animated, { + 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: Animated.SharedValue; + y: Animated.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) => { + 'worklet'; + panOffset.x.value = mainChatHeadPosition.x.value + e.translationX; + panOffset.y.value = mainChatHeadPosition.y.value + e.translationY; + }, + onDeactivate: (e) => { + 'worklet'; + 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..809c7ef547 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/lock/index.tsx @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + runOnJS, +} from 'react-native-reanimated'; +import { + GestureDetector, + useCompetingGestures, + useLongPressGesture, + usePinchGesture, + useRotationGesture, + useSimultaneousGestures, + useTapGesture, +} from 'react-native-gesture-handler'; +import { COLORS } 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; + + // Tap to lock + const tap = useTapGesture({ + onDeactivate: () => { + 'worklet'; + if (savedRotation.value === 0 && scale.value === maxScale) { + runOnJS(setLocked)(false); + } + }, + }); + + // Long press to cancel tap + const longPress = useLongPressGesture({}); + + const confirm = useCompetingGestures(longPress, tap); + + const rotationGesture = useRotationGesture({ + onUpdate: (e) => { + 'worklet'; + rotation.value = savedRotation.value + e.rotation; + + if (!locked) { + runOnJS(setLocked)(true); + } + }, + onDeactivate: () => { + 'worklet'; + + 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) => { + 'worklet'; + const value = savedScale.value * e.scale; + if (value < minScale || value > maxScale) { + return; + } + scale.value = value; + + if (!locked) { + runOnJS(setLocked)(true); + } + }, + onDeactivate: () => { + 'worklet'; + + 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!'} + + Tou unlock rotate 90 degrees clockwise, and scale to fill the square. + Then tap to confirm, longpress to cancel the tap + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + lockIcon: { + fontSize: 40, + color: '#fff', + fontWeight: 'bold', + }, + instructions: { + color: COLORS.GRAY, + marginTop: 8, + textAlign: 'center', + paddingHorizontal: 16, + }, + + 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..30355bc6e3 --- /dev/null +++ b/apps/common-app/src/new_api/complicated/velocity_test/index.tsx @@ -0,0 +1,107 @@ +import { StyleSheet, 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 } 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: () => { + 'worklet'; + isPressed.value = true; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onUpdate: (event) => { + 'worklet'; + offsetX.value += event.changeX; + offsetY.value += event.changeY; + }, + onFinalize: (event) => { + 'worklet'; + 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(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + + return { + transform: [ + { translateX: offsetX.value }, + { translateY: offsetY.value }, + { scale: withTiming(isPressed.value ? 1.2 : 1, { duration: 100 }) }, + ], + backgroundColor, + }; + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + height: '100%', + }, + box: { + width: BOX_SIZE, + height: BOX_SIZE, + borderRadius: BOX_SIZE / 2, + // @ts-expect-error `grab` is correct value for `cursor` property + cursor: 'grab', + }, +}); 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/index.tsx b/apps/common-app/src/new_api/index.tsx new file mode 100644 index 0000000000..d514f83abb --- /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 './platform_specific/context_menu'; +import HoverIconsExample from './platform_specific/hover'; +import HoverableIconsExample from './platform_specific/hoverable_icons'; +import MouseButtonsExample from './platform_specific/mouse_buttons'; +import StylusDataExample from './platform_specific/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: 'Platform Specific Features', + 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/platform_specific/context_menu/index.tsx b/apps/common-app/src/new_api/platform_specific/context_menu/index.tsx new file mode 100644 index 0000000000..e744afa564 --- /dev/null +++ b/apps/common-app/src/new_api/platform_specific/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/platform_specific/hover/index.tsx b/apps/common-app/src/new_api/platform_specific/hover/index.tsx new file mode 100644 index 0000000000..d2650ae623 --- /dev/null +++ b/apps/common-app/src/new_api/platform_specific/hover/index.tsx @@ -0,0 +1,156 @@ +import { COLORS, 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: () => { + 'worklet'; + hovered.value = true; + feedbackRef.current?.showMessage('Hover begin ' + color); + }, + onActivate: () => { + 'worklet'; + console.log('hover start', color); + }, + onDeactivate: (_, success) => { + 'worklet'; + console.log('hover end', color, 'failed', !success); + }, + onFinalize: () => { + 'worklet'; + hovered.value = false; + console.log('hover finalize', color); + }, + effect: HoverEffect.LIFT, + }); + + 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({ + container: { + flex: 1, + padding: 16, + }, + subcontainer: { + flex: 1, + gap: 8, + alignItems: 'center', + justifyContent: 'center', + borderBottomWidth: 1, + }, + title: { + fontSize: 22, + marginVertical: 8, + }, + 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/platform_specific/hoverable_icons/index.tsx b/apps/common-app/src/new_api/platform_specific/hoverable_icons/index.tsx new file mode 100644 index 0000000000..e23ea02253 --- /dev/null +++ b/apps/common-app/src/new_api/platform_specific/hoverable_icons/index.tsx @@ -0,0 +1,125 @@ +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'; + +// 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: () => { + 'worklet'; + scale.value = withTiming(1.15, { duration: 100 }); + }, + onUpdate: (e) => { + 'worklet'; + 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: () => { + 'worklet'; + 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({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + image: { + overflow: 'visible', + width: SIZE, + height: SIZE, + marginHorizontal: 8, + }, +}); diff --git a/apps/common-app/src/new_api/platform_specific/mouse_buttons/index.tsx b/apps/common-app/src/new_api/platform_specific/mouse_buttons/index.tsx new file mode 100644 index 0000000000..55cec62db0 --- /dev/null +++ b/apps/common-app/src/new_api/platform_specific/mouse_buttons/index.tsx @@ -0,0 +1,314 @@ +import React, { useRef } from 'react'; +import { + GestureDetector, + MouseButton, + Directions, + ScrollView, + useTapGesture, + usePanGesture, + useLongPressGesture, + useFlingGesture, + SingleGesture, +} from 'react-native-gesture-handler'; +import { StyleSheet, View, Text } from 'react-native'; +import { COLORS, Feedback } from '../../../common'; + +const COLORS_ARR = [ + COLORS.PURPLE, + COLORS.GREEN, + COLORS.NAVY, + COLORS.RED, + COLORS.KINDA_RED, +]; +export default function Buttons() { + const feedbackRef = useRef<{ showMessage: (msg: string) => void }>(null); + type TestProps = { + name: string; + gestures: SingleGesture[]; + }; + + function Test({ name, gestures }: TestProps) { + return ( + + {name} + + {gestures.map((handler, index) => { + return ( + + + + + + ); + })} + + + ); + } + + function TapTests() { + const leftTap = useTapGesture({ + mouseButton: MouseButton.LEFT, + onDeactivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tap with left'); + }, + }); + + const middleTap = useTapGesture({ + mouseButton: MouseButton.MIDDLE, + onDeactivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tap with middle'); + }, + }); + + const rightTap = useTapGesture({ + mouseButton: MouseButton.RIGHT, + onDeactivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tap with right'); + }, + }); + + const leftRightTap = useTapGesture({ + mouseButton: MouseButton.LEFT | MouseButton.RIGHT, + onDeactivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tap with left | right'); + }, + }); + + const allTap = useTapGesture({ + mouseButton: MouseButton.ALL, + onDeactivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tap with any button'); + }, + }); + + const gestures = [leftTap, middleTap, rightTap, leftRightTap, allTap]; + + return ; + } + + function PanTests() { + const leftPan = usePanGesture({ + mouseButton: MouseButton.LEFT, + onUpdate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Panning with left'); + }, + }); + + const middlePan = usePanGesture({ + mouseButton: MouseButton.MIDDLE, + onUpdate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Panning with middle'); + }, + }); + + const rightPan = usePanGesture({ + mouseButton: MouseButton.RIGHT, + onUpdate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Panning with right'); + }, + }); + + const leftRightPan = usePanGesture({ + mouseButton: MouseButton.LEFT | MouseButton.RIGHT, + onUpdate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Panning with left | right'); + }, + }); + + const allPan = usePanGesture({ + mouseButton: MouseButton.ALL, + onUpdate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Panning with any button'); + }, + }); + + const gestures = [leftPan, middlePan, rightPan, leftRightPan, allPan]; + + return ; + } + + function LongPressTests() { + const leftLongPress = useLongPressGesture({ + mouseButton: MouseButton.LEFT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('LongPress with left'); + }, + }); + + const middleLongPress = useLongPressGesture({ + mouseButton: MouseButton.MIDDLE, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('LongPress with middle'); + }, + }); + + const rightLongPress = useLongPressGesture({ + mouseButton: MouseButton.RIGHT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('LongPress with right'); + }, + }); + + const leftRightLongPress = useLongPressGesture({ + mouseButton: MouseButton.LEFT | MouseButton.RIGHT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('LongPress with left | right'); + }, + }); + + const allLongPress = useLongPressGesture({ + mouseButton: MouseButton.ALL, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('LongPress with any button'); + }, + }); + + const gestures = [ + leftLongPress, + middleLongPress, + rightLongPress, + leftRightLongPress, + allLongPress, + ]; + + return ; + } + + function FlingTests() { + const leftFling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton: MouseButton.LEFT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Fling with left'); + }, + }); + + const middleFling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton: MouseButton.MIDDLE, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Fling with middle'); + }, + }); + + const rightFling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton: MouseButton.RIGHT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Fling with right'); + }, + }); + + const leftRightFling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton: MouseButton.LEFT | MouseButton.RIGHT, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Fling with left | right'); + }, + }); + + const allFling = useFlingGesture({ + direction: Directions.LEFT | Directions.RIGHT, + mouseButton: MouseButton.ALL, + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Fling with any button'); + }, + }); + + const gestures = [ + leftFling, + middleFling, + rightFling, + leftRightFling, + allFling, + ]; + + return ; + } + + const Titles = () => { + return ( + + + Left + + + + Middle + + + Right + + + Left or right + + + Any button + + + ); + }; + + return ( + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + center: { + alignItems: 'center', + justifyContent: 'space-around', + }, + box: { + width: 75, + height: 75, + }, + row: { + padding: 10, + width: '100%', + flexDirection: 'row', + }, + title: { + fontSize: 16, + fontWeight: 'bold', + marginHorizontal: 5, + }, + rowItem: { + flex: 1, + height: '100%', + alignItems: 'center', + borderRightWidth: 2, + }, +}); diff --git a/apps/common-app/src/new_api/platform_specific/stylus_data/index.tsx b/apps/common-app/src/new_api/platform_specific/stylus_data/index.tsx new file mode 100644 index 0000000000..41410218cf --- /dev/null +++ b/apps/common-app/src/new_api/platform_specific/stylus_data/index.tsx @@ -0,0 +1,96 @@ +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) => { + 'worklet'; + if (!e.stylusData) { + return; + } + + scaleFactor.value = e.stylusData.pressure; + rotationYFactor.value = e.stylusData.tiltX; + rotationXFactor.value = e.stylusData.tiltY; + }, + onUpdate: (e) => { + 'worklet'; + if (!e.stylusData) { + return; + } + + scaleFactor.value = e.stylusData.pressure; + rotationYFactor.value = e.stylusData.tiltX; + rotationXFactor.value = e.stylusData.tiltY; + }, + onFinalize: (e) => { + 'worklet'; + 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/showcase/animated/index.tsx b/apps/common-app/src/new_api/showcase/animated/index.tsx new file mode 100644 index 0000000000..4272ef98e5 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/animated/index.tsx @@ -0,0 +1,167 @@ +import { COLORS } 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; + let offsetX = 0; + let offsetY = 0; + + const panGesture = usePanGesture({ + onBegin: () => { + normalisedRotation.setValue(1); + Animated.timing(colorProgress, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }).start(); + }, + onActivate: () => { + 'worklet'; + + Animated.spring(scale, { + toValue: 1.05, + useNativeDriver: true, + }).start(); + }, + onUpdate: (e) => { + 'worklet'; + + 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) => { + 'worklet'; + + 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.timing(translateX, { + toValue: direction * width, + duration: 200, + useNativeDriver: true, + }).start(() => { + translateX.setValue(0); + translateY.setValue(0); + rotation.setValue(0); + 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(); + + const normalisedRotationValue = e.absoluteX < 0 ? -1 : 1; + + Animated.timing(normalisedRotation, { + toValue: normalisedRotationValue, + duration: 500, + useNativeDriver: true, + }).start(); + } + offsetX = 0; + offsetY = 0; + }, + onFinalize: () => { + Animated.timing(colorProgress, { + toValue: 0, + duration: 500, + useNativeDriver: true, + }).start(); + }, + disableReanimated: true, + }); + + const rotateZ = rotation.interpolate({ + inputRange: [-20, 20], + outputRange: ['-20deg', '20deg'], + }); + const finalBackgroundColor = Animated.multiply( + colorProgress, + normalisedRotation + ).interpolate({ + inputRange: [-10, -1, 0, 1, 10], + outputRange: [ + COLORS.RED, + COLORS.PURPLE, + COLORS.NAVY, + COLORS.PURPLE, + COLORS.GREEN, + ], + extrapolate: 'clamp', + }); + + return ( + + + + Drag me + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + 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..85cfb9319a --- /dev/null +++ b/apps/common-app/src/new_api/showcase/bottom_sheet/index.tsx @@ -0,0 +1,163 @@ +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, 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 onHandlerEndOnJS = (point: number) => { + setSnapPoint(point); + }; + const onHandlerDeactivate = (e: PanGestureEvent) => { + 'worklet'; + 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(onHandlerEndOnJS)(destSnapPoint); + }; + const panGesture = usePanGesture({ + onUpdate: (e) => { + 'worklet'; + // 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) => { + 'worklet'; + 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({ + container: { + flex: 1, + }, + 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..71aced0d7a --- /dev/null +++ b/apps/common-app/src/new_api/showcase/nested_text/nested_text.tsx @@ -0,0 +1,141 @@ +import * as React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { + Gesture, + GestureDetector, + InterceptingGestureDetector, + VirtualGestureDetector, + useTapGesture, +} from 'react-native-gesture-handler'; + +import { COLORS, Feedback } from '../../../common'; + +function NativeDetectorExample() { + const feedbackRef = React.useRef<{ showMessage: (msg: string) => void }>( + null + ); + + const tapAll = useTapGesture({ + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped on all text'); + }, + }); + + const tapFirstPart = useTapGesture({ + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped on "try tapping on this part"'); + }, + }); + + const tapSecondPart = useTapGesture({ + onActivate: () => { + 'worklet'; + console.log('Tapped on second part!'); + feedbackRef.current?.showMessage('Tapped on "or on this part"'); + }, + }); + + 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(() => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped on all text'); + }); + + const tapFirstPart = Gesture.Tap().onStart(() => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped on "try tapping on this part"'); + }); + + const tapSecondPart = Gesture.Tap().onStart(() => { + 'worklet'; + 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 ( + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + subcontainer: { + flex: 1, + gap: 8, + alignItems: 'center', + justifyContent: 'center', + borderBottomWidth: 1, + }, + header: { + fontSize: 18, + textAlign: 'center', + paddingHorizontal: 24, + }, +}); 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..8a6e1b2dbc --- /dev/null +++ b/apps/common-app/src/new_api/showcase/overlap/index.tsx @@ -0,0 +1,173 @@ +import { COLORS, 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) => { + 'worklet'; + if (success) { + setElevated('purple'); + feedbackRef.current?.showMessage('Tapped purple'); + } + }, + disableReanimated: true, + }); + + const tapBlue = useTapGesture({ + onDeactivate: (_e, success) => { + 'worklet'; + 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) => { + 'worklet'; + if (success) { + feedbackRef.current?.showMessage('Tapped purple'); + setElevated('purple'); + } + }, + disableReanimated: true, + }); + + const tapGreen = useTapGesture({ + onDeactivate: (_e, success) => { + 'worklet'; + if (success) { + feedbackRef.current?.showMessage('Tapped blue'); + setElevated('blue'); + } + }, + disableReanimated: true, + }); + + return ( + + + Overlap Child + + + + + + + + + + + + + + + + ); +} + +export default function Example() { + return ( + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + subcontainer: { + flex: 1, + alignItems: 'center', + borderBottomWidth: 1, + }, + row: { + padding: 30, + alignItems: 'center', + height: 225, + marginBottom: 60, + }, + box: { + borderRadius: 20, + width: 150, + height: 150, + }, + overlap: { + position: 'absolute', + left: 75, + top: 75, + }, + text: { + fontSize: 24, + margin: 4, + }, + 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..e686a27334 --- /dev/null +++ b/apps/common-app/src/new_api/showcase/shared_value/index.tsx @@ -0,0 +1,114 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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: () => { + 'worklet'; + colorProgress.value = withTiming(1, { duration: 150 }); + }, + onUpdate: (event) => { + 'worklet'; + translateX.value = offsetX.value + event.translationX; + translateY.value = offsetY.value + event.translationY; + maxLongPressDistance.value = Math.abs(event.translationY) * 2 + 20; + }, + onFinalize: () => { + 'worklet'; + offsetX.value = translateX.value; + offsetY.value = translateY.value; + }, + }); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + 'worklet'; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: () => { + 'worklet'; + colorProgress.value = withTiming(2, { + duration: 100, + }); + }, + onFinalize: () => { + 'worklet'; + 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. + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 60, + marginBottom: 100, + }, + instructions: { + marginTop: 8, + textAlign: 'center', + paddingHorizontal: 16, + }, +}); 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..3f99c3a81f --- /dev/null +++ b/apps/common-app/src/new_api/showcase/svg/index.tsx @@ -0,0 +1,77 @@ +import { COLORS, Feedback } from '../../../common'; +import React, { useRef } from 'react'; +import { View, StyleSheet } 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: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped circle!'); + }, + }); + + const rectElementTap = useTapGesture({ + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped parallelogram!'); + }, + }); + + const containerTap = useTapGesture({ + onActivate: () => { + 'worklet'; + feedbackRef.current?.showMessage('Tapped container!'); + }, + }); + + // onPress must be set to enable gesture recognition on svg + // eslint-disable-next-line @typescript-eslint/no-empty-function + const noop = () => {}; + + return ( + + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 48, + }, +}); 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..351498c1f2 --- /dev/null +++ b/apps/common-app/src/new_api/simple/fling/index.tsx @@ -0,0 +1,80 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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) => { + 'worklet'; + beginPosition.value = e.x; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: (e) => { + 'worklet'; + const direction = Math.sign(e.x - beginPosition.value); + position.value = withTiming(position.value + direction * 50, { + duration: 300, + easing: Easing.bounce, + }); + }, + onFinalize: () => { + 'worklet'; + colorProgress.value = withTiming(0, { + duration: 400, + }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.PURPLE] + ); + return { + transform: [{ translateX: position.value }], + backgroundColor, + }; + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 30, + }, +}); 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..d25b21d186 --- /dev/null +++ b/apps/common-app/src/new_api/simple/hover/index.tsx @@ -0,0 +1,62 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + return { + backgroundColor, + }; + }); + + const tapGesture = useHoverGesture({ + onBegin: () => { + 'worklet'; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onFinalize: () => { + 'worklet'; + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 30, + }, +}); 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..5660eb562d --- /dev/null +++ b/apps/common-app/src/new_api/simple/longPress/index.tsx @@ -0,0 +1,72 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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 animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1, 2], + [COLORS.NAVY, COLORS.PURPLE, COLORS.KINDA_BLUE] + ); + + return { + backgroundColor, + }; + }); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + 'worklet'; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onActivate: () => { + 'worklet'; + colorProgress.value = withTiming(2, { + duration: 100, + }); + }, + onFinalize: () => { + 'worklet'; + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 30, + }, +}); 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..4146179838 --- /dev/null +++ b/apps/common-app/src/new_api/simple/pan/index.tsx @@ -0,0 +1,73 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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: () => { + 'worklet'; + colorProgress.value = withTiming(1, { duration: 150 }); + }, + onUpdate: (event) => { + 'worklet'; + translateX.value = offsetX.value + event.translationX; + translateY.value = offsetY.value + event.translationY; + }, + onFinalize: () => { + 'worklet'; + offsetX.value = translateX.value; + offsetY.value = translateY.value; + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + return { + transform: [ + { translateX: translateX.value }, + { translateY: translateY.value }, + ], + backgroundColor, + }; + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 100, + }, +}); 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..734a6a9ddb --- /dev/null +++ b/apps/common-app/src/new_api/simple/pinch/index.tsx @@ -0,0 +1,64 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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) => { + 'worklet'; + scale.value = event.scale; + + const p = Math.min(Math.max((event.scale - 1) / 0.5, 0), 1); + colorProgress.value = p; + }, + onDeactivate: () => { + 'worklet'; + scale.value = withTiming(1, { duration: 150 }); + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + + return { + transform: [{ scale: scale.value }], + backgroundColor, + }; + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 100, + }, +}); 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..cf114c62a5 --- /dev/null +++ b/apps/common-app/src/new_api/simple/rotation/index.tsx @@ -0,0 +1,67 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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) => { + 'worklet'; + rotation.value = event.rotation; + const p = Math.min(Math.max(Math.abs(event.rotation) / Math.PI, 0), 1); + colorProgress.value = p; + }, + onDeactivate: () => { + 'worklet'; + rotation.value = withTiming(0, { duration: 150 }); + colorProgress.value = withTiming(0, { duration: 150 }); + }, + }); + + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + + return { + transform: [{ rotateZ: `${rotation.value}rad` }], + backgroundColor, + }; + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 100, + }, +}); 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..6ebed67252 --- /dev/null +++ b/apps/common-app/src/new_api/simple/tap/index.tsx @@ -0,0 +1,62 @@ +import { COLORS } from '../../../common'; +import React from 'react'; +import { StyleSheet, 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(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [COLORS.NAVY, COLORS.KINDA_BLUE] + ); + return { + backgroundColor, + }; + }); + + const tapGesture = useTapGesture({ + onBegin: () => { + 'worklet'; + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, + onFinalize: () => { + 'worklet'; + colorProgress.value = withTiming(0, { + duration: 100, + }); + }, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + centerView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + box: { + height: 120, + width: 120, + borderRadius: 20, + marginBottom: 30, + }, +});