Skip to content

Commit 1b5f605

Browse files
committed
feat(components): 🚀 add Haptic feedback to tappable components
1 parent 916000e commit 1b5f605

File tree

4 files changed

+66
-6
lines changed

4 files changed

+66
-6
lines changed

src/components/button/Button.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React, { forwardRef } from "react";
1+
import React, { forwardRef, useCallback } from "react";
22
import {
3+
GestureResponderEvent,
34
Platform,
45
PressableProps,
56
PressableStateCallbackType,
@@ -14,6 +15,7 @@ import {
1415
cx,
1516
generateBoxShadow,
1617
styleAdapter,
18+
useHaptic,
1719
useOnFocus,
1820
useOnHover,
1921
useScaleAnimation,
@@ -91,6 +93,12 @@ export interface ButtonProps extends PressableProps {
9193
* VoiceOver will read this string when a user selects the associated element.
9294
*/
9395
accesibilityLabel: string;
96+
/**
97+
* When set to true, The Tap creates a Touch Feedback
98+
* Check more -> https://docs.expo.dev/versions/latest/sdk/haptics/
99+
* @default true
100+
*/
101+
hapticEnabled: boolean;
94102
}
95103

96104
const RNButton: React.FC<Partial<ButtonProps>> = forwardRef<
@@ -103,27 +111,36 @@ const RNButton: React.FC<Partial<ButtonProps>> = forwardRef<
103111
variant = "solid",
104112
themeColor = "base",
105113
loading = false,
114+
hapticEnabled = true,
106115
prefix,
107116
suffix,
108117
iconOnly,
109118
spinner,
110119
textStyle,
111120
style,
112121
accesibilityLabel,
122+
onPress,
113123
...props
114124
},
115125
ref,
116126
) => {
117127
const { ts, gc } = useTailwind();
118128
const buttonTheme = useTheme("button");
119129
const { handlers, animatedStyle } = useScaleAnimation();
130+
const { hapticMedium } = useHaptic();
120131
const { onHoverIn, onHoverOut, hovered } = useOnHover();
121132
const { onFocus, onBlur, focused } = useOnFocus();
122133

123134
const iconAspectRatio = 1;
124135

125136
const isButtonDisabled = props.disabled || loading;
126137

138+
const handlePress = useCallback((event: GestureResponderEvent) => {
139+
onPress && onPress(event);
140+
hapticEnabled && hapticMedium();
141+
// eslint-disable-next-line react-hooks/exhaustive-deps
142+
}, []);
143+
127144
/**
128145
* Button Prefix Component
129146
*/
@@ -329,6 +346,7 @@ const RNButton: React.FC<Partial<ButtonProps>> = forwardRef<
329346
ref={ref}
330347
disabled={isButtonDisabled}
331348
{...handlers}
349+
onPress={handlePress}
332350
>
333351
{children}
334352
</Touchable>

src/components/checkbox/Checkbox.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
cx,
2323
generateBoxShadow,
2424
styleAdapter,
25+
useHaptic,
2526
useOnFocus,
2627
useOnHover,
2728
useScaleAnimation,
@@ -91,6 +92,12 @@ export interface CheckboxProps extends TouchableProps {
9192
* Checkbox State Value if inside CheckboxGroup
9293
*/
9394
value: string;
95+
/**
96+
* When set to true, The Tap creates a Touch Feedback
97+
* Check more -> https://docs.expo.dev/versions/latest/sdk/haptics/
98+
* @default true
99+
*/
100+
hapticEnabled: boolean;
94101
}
95102

96103
const RNCheckbox: React.FC<Partial<CheckboxProps>> = forwardRef<
@@ -117,12 +124,18 @@ const RNCheckbox: React.FC<Partial<CheckboxProps>> = forwardRef<
117124
setSelected,
118125
isInvalid,
119126
accessibilityLabel = "Check me",
127+
hapticEnabled = true,
120128
isIndeterminate,
121129
isDisabled,
122130
style,
123131
index,
124132
} = props;
125133

134+
const { onHoverIn, onHoverOut, hovered } = useOnHover();
135+
const { onFocus, onBlur, focused } = useOnFocus();
136+
const { handlers, animatedStyle } = useScaleAnimation();
137+
const { hapticSelection } = useHaptic();
138+
126139
const hasOnlyLabel = label && !description;
127140

128141
const size = checkboxGroupState
@@ -165,6 +178,7 @@ const RNCheckbox: React.FC<Partial<CheckboxProps>> = forwardRef<
165178
}, [checkboxToggleState.isSelected, isIndeterminate]);
166179

167180
const handleChange = useCallback(() => {
181+
hapticEnabled && hapticSelection();
168182
if (checkboxGroupState) {
169183
if (props.value) {
170184
if (checkboxToggleState.isSelected) {
@@ -176,12 +190,9 @@ const RNCheckbox: React.FC<Partial<CheckboxProps>> = forwardRef<
176190
} else {
177191
checkboxToggleState.toggle();
178192
}
193+
// eslint-disable-next-line react-hooks/exhaustive-deps
179194
}, [checkboxGroupState, checkboxToggleState, props.value]);
180195

181-
const { onHoverIn, onHoverOut, hovered } = useOnHover();
182-
const { onFocus, onBlur, focused } = useOnFocus();
183-
const { handlers, animatedStyle } = useScaleAnimation();
184-
185196
const children = ({
186197
pressed = false,
187198
isHovered = false,

src/components/radio/Radio.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
cx,
1515
generateBoxShadow,
1616
styleAdapter,
17+
useHaptic,
1718
useOnFocus,
1819
useOnHover,
1920
useScaleAnimation,
@@ -49,6 +50,12 @@ export interface RadioProps extends TouchableProps {
4950
* @default "Tap me"
5051
*/
5152
accesibilityLabel: string;
53+
/**
54+
* When set to true, The Tap creates a Touch Feedback
55+
* Check more -> https://docs.expo.dev/versions/latest/sdk/haptics/
56+
* @default true
57+
*/
58+
hapticEnabled: boolean;
5259
}
5360

5461
const RNRadio: React.FC<Partial<RadioProps>> = forwardRef<
@@ -61,6 +68,7 @@ const RNRadio: React.FC<Partial<RadioProps>> = forwardRef<
6168
isInvalid = false,
6269
isDisabled: isDisabledFromProps,
6370
accesibilityLabel = "Tap me",
71+
hapticEnabled = true,
6472
style,
6573
index,
6674
focusable,
@@ -79,8 +87,10 @@ const RNRadio: React.FC<Partial<RadioProps>> = forwardRef<
7987

8088
const { onHoverIn, onHoverOut, hovered } = useOnHover();
8189
const { onFocus, onBlur, focused } = useOnFocus();
90+
const { hapticSelection } = useHaptic();
8291
const { handlers, animatedStyle } = useScaleAnimation();
8392
const state = useRadioGroupContext();
93+
8494
const {
8595
themeColor: themeColorFromGroupContext,
8696
size: sizeFromGroupContext,
@@ -101,9 +111,11 @@ const RNRadio: React.FC<Partial<RadioProps>> = forwardRef<
101111
}, [index, props?.value, selectedValue, setFocusableIndex]);
102112

103113
const handleChange = useCallback(() => {
114+
hapticEnabled && hapticSelection();
104115
// @ts-ignore
105116
setSelectedValue(props?.value);
106117
setFocusableIndex(index as number);
118+
// eslint-disable-next-line react-hooks/exhaustive-deps
107119
}, [index, props?.value, setFocusableIndex, setSelectedValue]);
108120

109121
const size = sizeFromGroupContext;

src/components/tag/Tag.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React, { forwardRef } from "react";
1+
import React, { forwardRef, useCallback } from "react";
22
import {
3+
GestureResponderEvent,
34
Platform,
45
PressableProps,
56
PressableStateCallbackType,
@@ -15,6 +16,7 @@ import {
1516
generateBoxShadow,
1617
RenderPropType,
1718
styleAdapter,
19+
useHaptic,
1820
useOnFocus,
1921
useOnHover,
2022
useScaleAnimation,
@@ -69,6 +71,12 @@ export interface TagProps extends PressableProps {
6971
* VoiceOver will read this string when a user selects the associated element.
7072
*/
7173
accesibilityLabel: string;
74+
/**
75+
* When set to true, The Tap creates a Touch Feedback
76+
* Check more -> https://docs.expo.dev/versions/latest/sdk/haptics/
77+
* @default true
78+
*/
79+
hapticEnabled: boolean;
7280
}
7381

7482
const RNTag: React.FC<Partial<TagProps>> = forwardRef<
@@ -81,6 +89,8 @@ const RNTag: React.FC<Partial<TagProps>> = forwardRef<
8189
const { onHoverIn, onHoverOut, hovered } = useOnHover();
8290
const { onFocus, onBlur, focused } = useOnFocus();
8391
const { handlers, animatedStyle } = useScaleAnimation();
92+
const { hapticSelection } = useHaptic();
93+
8494
const {
8595
size = "md",
8696
variant = "solid",
@@ -91,9 +101,17 @@ const RNTag: React.FC<Partial<TagProps>> = forwardRef<
91101
style,
92102
textStyle,
93103
accesibilityLabel,
104+
hapticEnabled = true,
105+
onPress,
94106
...otherProps
95107
} = props;
96108

109+
const handlePress = useCallback((event: GestureResponderEvent) => {
110+
onPress && onPress(event);
111+
hapticEnabled && hapticSelection();
112+
// eslint-disable-next-line react-hooks/exhaustive-deps
113+
}, []);
114+
97115
/* Prefix Slot */
98116
const _prefix =
99117
prefix &&
@@ -226,6 +244,7 @@ const RNTag: React.FC<Partial<TagProps>> = forwardRef<
226244
accessibilityRole="button"
227245
accessibilityLabel={accesibilityLabel}
228246
{...handlers}
247+
onPress={handlePress}
229248
>
230249
<>
231250
{_prefix}

0 commit comments

Comments
 (0)