Skip to content

Commit

Permalink
feat: add hover support to web (#3909)
Browse files Browse the repository at this point in the history
  • Loading branch information
teneeto authored Oct 16, 2023
1 parent 23e7fd8 commit 416be23
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 15 deletions.
35 changes: 35 additions & 0 deletions src/components/TouchableRipple/Pressable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type * as React from 'react';
import type {
PressableProps as PressableNativeProps,
StyleProp,
View,
ViewStyle,
} from 'react-native';
import { Pressable as PressableNative } from 'react-native';

// This component is added to support type-safe hover and focus states on web
// https://necolas.github.io/react-native-web/docs/pressable/

export type PressableStateCallbackType = {
hovered: boolean;
pressed: boolean;
focused: boolean;
};

export type PressableProps = Omit<
PressableNativeProps,
'children' | 'style'
> & {
children:
| React.ReactNode
| ((state: PressableStateCallbackType) => React.ReactNode)
| undefined;
style?:
| StyleProp<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)
| undefined;
};

export const Pressable: React.ForwardRefExoticComponent<
PressableProps & React.RefAttributes<View>
> = PressableNative as any;
5 changes: 3 additions & 2 deletions src/components/TouchableRipple/TouchableRipple.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
Platform,
ViewStyle,
StyleSheet,
Pressable,
GestureResponderEvent,
View,
ColorValue,
} from 'react-native';

import type { PressableProps } from './Pressable';
import { Pressable } from './Pressable';
import { getTouchableRippleColors } from './utils';
import { Settings, SettingsContext } from '../../core/settings';
import { useInternalTheme } from '../../core/theming';
Expand All @@ -20,7 +21,7 @@ import hasTouchHandler from '../../utils/hasTouchHandler';
const ANDROID_VERSION_LOLLIPOP = 21;
const ANDROID_VERSION_PIE = 28;

export type Props = React.ComponentProps<typeof Pressable> & {
export type Props = PressableProps & {
borderless?: boolean;
background?: PressableAndroidRippleConfig;
centered?: boolean;
Expand Down
48 changes: 35 additions & 13 deletions src/components/TouchableRipple/TouchableRipple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import {
ColorValue,
GestureResponderEvent,
Platform,
Pressable,
StyleProp,
StyleSheet,
ViewStyle,
} from 'react-native';

import color from 'color';

import type { PressableProps, PressableStateCallbackType } from './Pressable';
import { Pressable } from './Pressable';
import { getTouchableRippleColors } from './utils';
import { Settings, SettingsContext } from '../../core/settings';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
import hasTouchHandler from '../../utils/hasTouchHandler';

export type Props = React.ComponentPropsWithRef<typeof Pressable> & {
export type Props = PressableProps & {
/**
* Whether to render the ripple outside the view bounds.
*/
Expand Down Expand Up @@ -60,8 +63,13 @@ export type Props = React.ComponentPropsWithRef<typeof Pressable> & {
/**
* Content of the `TouchableRipple`.
*/
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
children:
| ((state: PressableStateCallbackType) => React.ReactNode)
| React.ReactNode;
style?:
| StyleProp<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)
| undefined;
/**
* @optional
*/
Expand Down Expand Up @@ -105,6 +113,11 @@ const TouchableRipple = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
const { calculatedRippleColor } = getTouchableRippleColors({
theme,
rippleColor,
});
const hoverColor = color(calculatedRippleColor).fade(0.5).rgb().string();
const { rippleEffectEnabled } = React.useContext<Settings>(SettingsContext);

const { onPress, onLongPress, onPressIn, onPressOut } = rest;
Expand All @@ -116,11 +129,6 @@ const TouchableRipple = ({
if (rippleEffectEnabled) {
const { centered } = rest;

const { calculatedRippleColor } = getTouchableRippleColors({
theme,
rippleColor,
});

const button = e.currentTarget;
const style = window.getComputedStyle(button);
const dimensions = button.getBoundingClientRect();
Expand Down Expand Up @@ -209,7 +217,7 @@ const TouchableRipple = ({
});
}
},
[onPressIn, rest, rippleColor, theme, rippleEffectEnabled]
[onPressIn, rest, rippleEffectEnabled, calculatedRippleColor]
);

const handlePressOut = React.useCallback(
Expand Down Expand Up @@ -262,9 +270,20 @@ const TouchableRipple = ({
onPressIn={handlePressIn}
onPressOut={handlePressOut}
disabled={disabled}
style={[styles.touchable, borderless && styles.borderless, style]}
style={(state) => [
styles.touchable,
borderless && styles.borderless,
// focused state is not ready yet: https://github.com/necolas/react-native-web/issues/1849
// state.focused && { backgroundColor: ___ },
state.hovered && { backgroundColor: hoverColor },
typeof style === 'function' ? style(state) : style,
]}
>
{React.Children.only(children)}
{(state) =>
React.Children.only(
typeof children === 'function' ? children(state) : children
)
}
</Pressable>
);
};
Expand All @@ -277,7 +296,10 @@ TouchableRipple.supported = true;
const styles = StyleSheet.create({
touchable: {
position: 'relative',
...(Platform.OS === 'web' && { cursor: 'pointer' }),
...(Platform.OS === 'web' && {
cursor: 'pointer',
transition: '150ms background-color',
}),
},
borderless: {
overflow: 'hidden',
Expand Down

0 comments on commit 416be23

Please sign in to comment.