Skip to content

Commit

Permalink
feat: Add Tap and Double Tap Support
Browse files Browse the repository at this point in the history
  • Loading branch information
ksitko committed Mar 6, 2021
1 parent 3522873 commit fd1ad65
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 24 deletions.
114 changes: 90 additions & 24 deletions src/components/holdItem/HoldItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import React, { memo, useMemo } from 'react';
import { Portal } from '@gorhom/portal';
import { nanoid } from 'nanoid/non-secure';
import {
TapGestureHandler,
LongPressGestureHandler,
TapGestureHandlerGestureEvent,
LongPressGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
Expand All @@ -15,6 +17,7 @@ import Animated, {
useSharedValue,
withDelay,
withTiming,
withSequence,
withSpring,
useAnimatedReaction,
} from 'react-native-reanimated';
Expand All @@ -39,7 +42,7 @@ import styles from './styles';
import { useDeviceOrientation } from '../../hooks';

// Types
import type { HoldItemProps } from './types';
import type { HoldItemProps, GestureHandlerProps } from './types';
import styleGuide from '../../styleGuide';
import { useInternal } from '../../hooks';

Expand All @@ -51,6 +54,7 @@ const HoldItemComponent = ({
containerStyles,
disableMove,
menuAnchorPosition,
activateOn,
children,
}: HoldItemProps) => {
const { state, menuProps } = useInternal();
Expand All @@ -70,7 +74,9 @@ const HoldItemComponent = ({

const deviceOrientation = useDeviceOrientation();
const key = useMemo(() => `hold-item-${nanoid()}`, []);

const isHold = useMemo(() => !activateOn || activateOn === 'hold', [
activateOn,
]);
const menuHeight = useMemo(() => {
const itemsWithSeperator = items.filter(item => item.withSeperator);
return calculateMenuHeight(items.length, itemsWithSeperator.length);
Expand Down Expand Up @@ -147,8 +153,45 @@ const HoldItemComponent = ({
});
};

const longPressGestureEvent = useAnimatedGestureHandler<
LongPressGestureHandlerGestureEvent,
const onCompletion = (isFinised: boolean) => {
'worklet';
const isListValid = items && items.length > 0;
if (isFinised && isListValid) {
state.value = CONTEXT_MENU_STATE.ACTIVE;
isActive.value = true;
scaleBack();
}

// TODO: Warn user if item list is empty or not given
};

const scaleHold = () => {
'worklet';
itemScale.value = withTiming(
HOLD_ITEM_SCALE_DOWN_VALUE,
{ duration: HOLD_ITEM_SCALE_DOWN_DURATION },
onCompletion
);
};

const scaleTap = () => {
'worklet';
itemScale.value = withSequence(
withTiming(HOLD_ITEM_SCALE_DOWN_VALUE, {
duration: HOLD_ITEM_SCALE_DOWN_DURATION,
}),
withTiming(
1,
{
duration: HOLD_ITEM_TRANSFORM_DURATION / 2,
},
onCompletion
)
);
};

const gestureEvent = useAnimatedGestureHandler<
LongPressGestureHandlerGestureEvent | TapGestureHandlerGestureEvent,
Context
>({
onActive: (_, context) => {
Expand All @@ -160,25 +203,18 @@ const HoldItemComponent = ({
}

if (!isActive.value) {
itemScale.value = withTiming(
HOLD_ITEM_SCALE_DOWN_VALUE,
{ duration: HOLD_ITEM_SCALE_DOWN_DURATION },
isFinised => {
const isListValid = items && items.length > 0;
if (isFinised && isListValid) {
state.value = CONTEXT_MENU_STATE.ACTIVE;
isActive.value = true;
scaleBack();
}

// TODO: Warn user if item list is empty or not given
}
);
if (isHold) {
scaleHold();
} else {
scaleTap();
}
}
},
onFinish: (_, context) => {
context.didMeasureLayout = false;
scaleBack();
if (isHold) {
scaleBack();
}
},
});

Expand Down Expand Up @@ -252,16 +288,46 @@ const HoldItemComponent = ({
}
);

const GestureHandler = useMemo(() => {
switch (activateOn) {
case `double-tap`:
return ({ children }: GestureHandlerProps) => (
<TapGestureHandler
numberOfTaps={2}
onHandlerStateChange={gestureEvent}
>
{children}
</TapGestureHandler>
);
case `tap`:
return ({ children }: GestureHandlerProps) => (
<TapGestureHandler
numberOfTaps={1}
onHandlerStateChange={gestureEvent}
>
{children}
</TapGestureHandler>
);
// default is hold
default:
return ({ children }: GestureHandlerProps) => (
<LongPressGestureHandler
minDurationMs={150}
onHandlerStateChange={gestureEvent}
>
{children}
</LongPressGestureHandler>
);
}
}, [activateOn]);

return (
<>
<LongPressGestureHandler
minDurationMs={150}
onHandlerStateChange={longPressGestureEvent}
>
<GestureHandler>
<Animated.View ref={containerRef} style={containerStyle}>
{children}
</Animated.View>
</LongPressGestureHandler>
</GestureHandler>

<Portal key={key} name={key}>
<Animated.View
Expand Down
13 changes: 13 additions & 0 deletions src/components/holdItem/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,17 @@ export type HoldItemProps = {
* bottom={true}
*/
bottom?: boolean;

/**
* Set if you'd like a different tap activation
* @type string
* @default 'hold'
* @examples
* activateOn="hold"
*/
activateOn?: 'tap' | 'double-tap' | 'hold';
};

export type GestureHandlerProps = {
children: React.ReactElement | React.ReactElement[];
};

0 comments on commit fd1ad65

Please sign in to comment.