Skip to content

Instantly share code, notes, and snippets.

@necolas
Last active September 23, 2020 09:16
Show Gist options
  • Save necolas/b2d35105365c31321e7e3a0b815328b4 to your computer and use it in GitHub Desktop.
Save necolas/b2d35105365c31321e7e3a0b815328b4 to your computer and use it in GitHub Desktop.
React Pressable / OnLayout
/**
* OnLayout is built upon: View (and ResizeObserver), StyleSheet
*/
const elementBreakpoints = {
small: { minWidth: 200 },
medium: { minWidth: 300 }
large: { minWidth: 500 }
};
// OnLayout forwards information about the container's dimensions (NOT the viewport).
// It allows complete control over what children and styles are rendered for a given state.
// It addresses the lack of "container queries" on the web, and the need to perform
// layout-dependent adjustments to component trees not just styles.
const Example = () => (
<OnLayout
breakpoints={elementBreakpoints}
style={(state) => ([
state.small && styles.small,
state.medium && !state.large && styles.mediumNotLarge,
state.large && styles.large
])}
/>
{(state) => (
<Text>Hello, {state.large ? "big world" : "small world"}</Text>
)}
</OnLayout>
);
/**
* Pressable is built upon: View, StyleSheet, and the Responder Event system.
*/
type PressableState = $ReadOnly<{|
hovered: boolean,
focused: boolean,
pressed: boolean,
longPressed: boolean
|}>
// Pressable forwards information about the modality interaction state.
// It allows for complete control over what children and styles are rendered for a given state.
// It addresses various UX quirks on mobile web (e.g., hover is NEVER true during touch interactions).
const SimpleExample = () => (
<Pressable
style={(state) => ([
state.hovered && styles.hovered,
state.pressed && styles.pressed
])}
>
<Text>Press me</Text>
</Pressable>
);
// More complex uses cases – logging interactions, adding delays to hover/press effects, etc. – are possible.
// JS events are in sync with the state that is forwarded to "children" and "style".
const ComplexExample = () => (
<Pressable
// accessibilityLabel="Create new post (long press for shortcuts to open the camera roll and video recorder)",
// accessibilityRole="button",
// accessibilityStates=["selected"],
// hitSlop,
// onLayout,
onBlur={e => {…})
onFocus={e => {…})
// Delays allow you to control when an interaction state is activated.
delayHoverIn={100}
delayHoverOut={200}
delayPressIn={150}
delayPressOut={0}
delayLongPress={0}
// Mouse
onHoverIn={e => {…})
onHoverOut={e => {…})
// Touch, Mouse, Keyboard
onPress={e => {…})
onPressIn={e => {…})
onPressMove={e => {…}}
onPressOut={e => {…})
onLongPress={e => {…}}
onLongPressShouldCancelPress={() => true}
pressRetentionOffset={pressRetentionOffset: EdgeInsetsProp}
// Style as a function of interaction state.
style={getStyle}
>
// Children as a function of interaction state.
{getChildren}
</Pressable>
);
const getChildren = (state: PressableState) => {
const { hovered, pressed, longPressed } = state;
return (
<>
// Fine control over render tree
{(hovered && !pressed) ? <Tooltip /> : null}
<Text style={pressed && style.pressedText}>Press me</Text>
{longPressed ? <RadialMenu /> : null}
</>
);
}
const getStyle = (state: PressableState) => {
const { hovered, focused, pressed, longPressed } = state;
return [
styles.root,
// Fine control over how styles compose (or don't)
// Unlike CSS ":hover", hovered is never "true" during a touch.
hovered && !focused && styles.hovered,
focused && styles.focused,
pressed && styles.pressed,
longPressed && styles.longPressed
];
};
const styles = StyleSheet.create({
root: { ... }
hovered: { ... },
focused: { ... },
pressed: { ... },
longPressed: { ... },
pressedText: { ... }
});
// Questions?
// Use case for 'onHoverMove'?
// Use case for 'onMoveShouldCancelHover'?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment