Skip to content

Instantly share code, notes, and snippets.

@ianmartorell
Forked from necolas/Hoverable.js
Last active August 25, 2021 16:32
Show Gist options
  • Save ianmartorell/32bb7df95e5eff0a5ee2b2f55095e6a6 to your computer and use it in GitHub Desktop.
Save ianmartorell/32bb7df95e5eff0a5ee2b2f55095e6a6 to your computer and use it in GitHub Desktop.
Hover styles in React Native for Web
import React, { useCallback, ReactNode } from 'react';
import { isHoverEnabled } from './HoverState';
export interface HoverableProps {
onHoverIn?: () => void;
onHoverOut?: () => void;
children: ((isHovered: boolean) => ReactNode) | ReactNode;
}
export default function Hoverable({ onHoverIn, onHoverOut, children }: HoverableProps) {
const [isHovered, setHovered] = React.useState(false);
const [showHover, setShowHover] = React.useState(true);
const handleMouseEnter = useCallback(() => {
if (isHoverEnabled() && !isHovered) {
if (onHoverIn) onHoverIn();
setHovered(true);
}
}, [isHovered, onHoverIn]);
const handleMouseLeave = useCallback(() => {
if (isHovered) {
if (onHoverOut) onHoverOut();
setHovered(false);
}
}, [isHovered, onHoverOut]);
const handleGrant = useCallback(() => {
setShowHover(false);
}, []);
const handleRelease = useCallback(() => {
setShowHover(true);
}, []);
const child =
typeof children === 'function' ? children(showHover && isHovered) : children;
return React.cloneElement(React.Children.only(child), {
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
// prevent hover showing while responder
onResponderGrant: handleGrant,
onResponderRelease: handleRelease,
// if child is Touchable
onPressIn: handleGrant,
onPressOut: handleRelease,
});
}
/* eslint-disable no-inner-declarations */
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
let isEnabled = false;
if (canUseDOM) {
/**
* Web browsers emulate mouse events (and hover states) after touch events.
* This code infers when the currently-in-use modality supports hover
* (including for multi-modality devices) and considers "hover" to be enabled
* if a mouse movement occurs more than 1 second after the last touch event.
* This threshold is long enough to account for longer delays between the
* browser firing touch and mouse events on low-powered devices.
*/
const HOVER_THRESHOLD_MS = 1000;
let lastTouchTimestamp = 0;
function enableHover() {
if (isEnabled || Date.now() - lastTouchTimestamp < HOVER_THRESHOLD_MS) {
return;
}
isEnabled = true;
}
function disableHover() {
lastTouchTimestamp = Date.now();
if (isEnabled) {
isEnabled = false;
}
}
document.addEventListener('touchstart', disableHover, true);
document.addEventListener('touchmove', disableHover, true);
document.addEventListener('mousemove', enableHover, true);
}
export function isHoverEnabled(): boolean {
return isEnabled;
}
export const Usage = () => (
<Hoverable>
{isHovered => (
<Touchable accessibilityRole="link" onPress={() => {}}>
<Text style={[styles.link, isHovered && styles.linkActive]}>
Click me
</Text>
</Touchable>
)}
</Hoverable>
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment