feat(components): PressableSurface with refined press feedback
This commit is contained in:
1 parent
9b02c10926
commit
fc42e1da43
2 files changed
+113
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
|
||||
jest.mock('react-native-reanimated', () => {
|
||||
const { View } = require('react-native');
|
||||
return {
|
||||
__esModule: true,
|
||||
default: { View: View, createAnimatedComponent: (c: unknown) => c },
|
||||
Easing: { out: () => (() => 0), inOut: () => (() => 0), quad: () => 0 },
|
||||
useSharedValue: (initial: number) => ({ value: initial }),
|
||||
useAnimatedStyle: (fn: () => unknown) => fn(),
|
||||
withTiming: (to: number) => to,
|
||||
withRepeat: (to: number) => to,
|
||||
cancelAnimation: () => undefined,
|
||||
};
|
||||
});
|
||||
|
||||
import { PressableSurface } from '../components/PressableSurface';
|
||||
|
||||
describe('PressableSurface', () => {
|
||||
it('renders children inside', () => {
|
||||
let tree: any;
|
||||
TestRenderer.act(() => {
|
||||
tree = TestRenderer.create(
|
||||
React.createElement(PressableSurface, { onPress: () => undefined, testID: 'surface' },
|
||||
React.createElement('Text', null, 'Hi'),
|
||||
),
|
||||
);
|
||||
});
|
||||
const json = tree.toJSON();
|
||||
expect(JSON.stringify(json)).toContain('Hi');
|
||||
});
|
||||
|
||||
it('has accessibilityRole=button when accessibilityLabel is set', () => {
|
||||
let tree: any;
|
||||
TestRenderer.act(() => {
|
||||
tree = TestRenderer.create(
|
||||
React.createElement(PressableSurface, { onPress: () => undefined, accessibilityLabel: 'Tap', testID: 'surface' },
|
||||
React.createElement('Text', null, 'Hi'),
|
||||
),
|
||||
);
|
||||
});
|
||||
const json = JSON.stringify(tree.toJSON());
|
||||
expect(json).toContain('"accessibilityLabel":"Tap"');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useCallback, type ReactNode } from 'react';
|
||||
import { Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
type Props = {
|
||||
readonly children: ReactNode;
|
||||
readonly onPress?: () => void;
|
||||
readonly disabled?: boolean;
|
||||
readonly style?: StyleProp<ViewStyle>;
|
||||
readonly accessibilityLabel?: string;
|
||||
readonly testID?: string;
|
||||
};
|
||||
|
||||
export function PressableSurface({
|
||||
children,
|
||||
onPress,
|
||||
disabled,
|
||||
style,
|
||||
accessibilityLabel,
|
||||
testID,
|
||||
}: Props) {
|
||||
const scale = useSharedValue(1);
|
||||
const opacity = useSharedValue(1);
|
||||
|
||||
const onPressIn = useCallback(() => {
|
||||
scale.value = withTiming(0.96, { duration: 80, easing: Easing.out(Easing.quad) });
|
||||
opacity.value = withTiming(0.82, { duration: 80, easing: Easing.out(Easing.quad) });
|
||||
}, [scale, opacity]);
|
||||
|
||||
const onPressOut = useCallback(() => {
|
||||
scale.value = withTiming(1, { duration: 160, easing: Easing.out(Easing.quad) });
|
||||
opacity.value = withTiming(1, { duration: 160, easing: Easing.out(Easing.quad) });
|
||||
}, [scale, opacity]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
opacity: opacity.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View style={animatedStyle}>
|
||||
<Pressable
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole={accessibilityLabel ? 'button' : undefined}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
style={[styles.base, style]}
|
||||
testID={testID}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
alignSelf: 'stretch',
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user