Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transparent context menu on tap-and-hold with React Native?

Tags:

react-native

I would like to create something like a context menu for an entry within a FlatList: If the user taps and holds, a central dot appears and a couple of icons or SVG graphics around that dot. When the user moves the finger towards one of the icons (and releases the tap), the respective action is triggered, see the screenshot which illustrates that:

 iss

Is there an out-of-the-box component for that? Setting up a modal looks overkill, but are there other alternatives?

like image 363
B--rian Avatar asked Nov 30 '25 03:11

B--rian


1 Answers

I wrote this specially for you: https://snack.expo.io/@alexandrelage/4774e0

It's a boilerplate. There is work to do. I just don't believe the user should need to drag their finger through the menu items in order to select one option. It sounds anti-accessibility design. Instead, you'd better expect the user to select the menu item normally with good old onPress prop.

1. The render() method looks like this:

  render() {
    return (
      <SafeAreaView style={styles.container}>
        <FlatList
          data={data}
          renderItem={this.renderItem}
          scrollEnabled={!this.state.selectedItem}
        />
        {this.state.selectedItem && this.menu()}
      </SafeAreaView>
    );
  }
  1. There is a FlatList component which disables scrolling when there is some selectedItem defined on state.

  2. There is a menu component which is only rendered when there is some selectedItem defined on state. This menu will have the menu items with their actions.

2. The renderItem method looks like this:

  renderItem = ({ item }) => (
    <TouchableOpacity
      key={item}
      style={styles.item}
      onLongPress={event => this.handleLongPress(item, event)}>
      <Text>Long press list item</Text>
    </TouchableOpacity>
  );

It calls handleLongPress method when the user presses the item on FlatList. The handleLongPress sets the selectedItem and the pageX and pageY on state.

  handleLongPress = (item, event) => {
    this.animateMenuScale();
    const locationY = event.nativeEvent.pageY;
    const locationX = event.nativeEvent.pageX;
    this.setState({ selectedItem: { item, locationY, locationX } });
  };

This setState will trigger rendering the menu component as follows.

3. The menu component looks like this:

  menu = () => {
    const { selectedItem } = this.state;
    return (
      <View style={styles.menuBackground}>
        <Animated.View
          style={[
            styles.menu,
            {
              top: selectedItem.locationY-MENU_RADIUS_END/2,
              left: selectedItem.locationX-MENU_RADIUS_END/2,
              width: this.menuScale,
              height: this.menuScale,
              borderRadius: this.menuScale
            },
          ]}
        />
      </View>
    );
  };
  1. This component is Animated when rendered. It uses this.menuScale as value for its width and height. this.menuScale starts animating (growing) when the uses presses the item (handleLongPress).
  2. This component's position is set to absolute, so it is easy to use absolute values for top and left props.

4. Finally setTimeout to clear the selectedItem state:

This line on shouldComponentUpdate sets a timeout if the selectedItem state exists. This timeout waits for 5 seconds then clears the selectedItem state.

  shouldComponentUpdate(nextProps, nextState) {
    nextState.selectedItem &&
      setTimeout(() => this.setState({ selectedItem: null }), 5000); //Clear after few seconds
    return true;
  }

I hope it helps.

TODO:

  • Add haptics feedback (vibrator).
  • Add menu items.
like image 186
ofundefined Avatar answered Dec 05 '25 14:12

ofundefined



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!