root-two / react-native-drawer

React Native Drawer
MIT License
2.54k stars 390 forks source link

Feature Request: tap to close #20

Closed bleonard closed 9 years ago

bleonard commented 9 years ago

The spec on the drawer I'm supposed to build is:

1) button to open the drawer 2) no sliding to open the drawer - only the button 3) opens most of the way but "main" content can still be seen on the right 4) tap on what is seen on the right to close it 5) no sliding to close

I am able to accomplish most of these with the current code:

1) call drawer.open() when button is hit 2) set panOpenMask={0.0} to prevent sliding 3) set openDrawerOffset={0.15} to show 15% of it 4) does not seem possible 5) set panCloseMask={0.0} to prevent sliding

2 and 5 are a little hacky so I'd love it if there was a boolean slides={false}. This might also allow you to completely stop all the pan listening and recording. I'm not sure the performance impact on that.

However, 4 does not seem to work. There has to be some panning. So the feature request is to be able to tap the "main" content to be able to close it. Ideally it would also prevent it from interacting with the main content.

Thanks.

rt2zz commented 9 years ago

I would be ok with adding acceptTap and allowPan props, or something along those lines. I am a little concerned about prop bloat though, especially as we look to add in more advanced features like multiple drawers.

Another option would be a toggleMode(s) prop that is an array of acceptable toggle methods ['pan', 'press', 'doublePress'].

Need to think about this more, but I will probably add the first two props to solve your use case for now, and take a hard look at refactoring the props in 2.0.

bleonard commented 9 years ago

Sweet. Thanks. Let me know if you want me to test it out.

rt2zz commented 9 years ago

try out v1.1.4

new props are acceptTap and acceptPan (for consistency with acceptDoubleTap). You probably want to set them to true and false respectively, as well as set panCloseMask to equal your open offset.

bleonard commented 9 years ago

I used v1.2.1 - should that have worked? It did seem to accept tap. Thanks for responding so quickly!

Here is my current setup:

      <Drawer
        ref="drawer"
        acceptTap={true}  // tap to open
        acceptPan={false} // have to hit button
        openDrawerOffset={showWhenOpenPercent} // percent of screen to leave open (can also be pixels)
        panCloseMask={showWhenOpenPercent}     // allow slide it closed for now TODO: tap anywhere on it
        panOpenMask={0.0} // zero so no taps will work TODO: allow boolean in real project
        content={<Sidebar selectedRoute={parsed.route} />}
        >

I found that acceptTap worked as expected to close the drawer.

I'm not sure acceptPan worked, though. I figured that would allow me to not have to set panOpenMask to zero, so I unset that. However, that configuration opened the drawer with any click in the left-ish side of the screen. Putting back to zero gives me the behavior I want, but I thought you'd want to know.

One thing I noticed is that the tap to close the drawer continues to propogate to the children. For example, if the app is showing the drawer and there is a button on the "main" page, tapping anywhere on the page should close the drawer. That works as expected. However, if that tap happens to be on the button, it still executes that. On our current iOS implementation, we found that to be a problem as people hit stuff accidentally a lot. Is there a stopProgagation() call to make somewhere?

rt2zz commented 9 years ago

Yes, allowing false for panOpenMask is probably a good idea. That said setting it to 0 (as in your example) was my intended solution. acceptTap refers to both tap to open and tap to close.

Propagation starts form the child and bubbles up. Another person had a similar request with respect to blocking pan on children. I will take a look...

rt2zz commented 9 years ago

Ok, I have a solution, although it is not super pretty:

Basically the strategy is to mask your content with a pan responder when the drawer is open. I got it working using roughly the following code:

<Drawer
        ref="drawer"
        onOpen={() => {
          this.setState({disableGestures : true})
        }}
        onClose={() => {
          this.setState({disableGestures : false})
        }}
        acceptTap={true}  // tap to open
        acceptPan={false} // have to hit button
        openDrawerOffset={showWhenOpenPercent}
        panCloseMask={showWhenOpenPercent} 
        panOpenMask={0.0} // zero so no taps will work TODO: allow boolean in real project
        content={<Sidebar selectedRoute={parsed.route} />}
        >
    <Main disableGestures={this.state.disableGestures} />
</Drawer>

//...
var Main = React.createClass({
  render(){
    return (
      <View>
        <Content />
        {this.props.disableGestures ? <CaptureGestures /> : null}
      </View>
  }
})

CaptureGestures.js

var React = require('react-native')
var {NativeModules, StyleSheet, PanResponder, View} = React
var deviceScreen = NativeModules.UIManager.Dimensions.window

module.exports = React.createClass({

  componentWillMount: function() {
    this._panGesture = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => { return false },
      onStartShouldSetPanResponderCapture: (evt, gestureState) => { return false },
      onMoveShouldSetPanResponder: (evt, gestureState) => { return false },
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => { return false },
    });
  },

  render () {
    var style = {
      position: 'absolute',
      top: 0,
      left: 0,
      width: this.props.width || deviceScreen.width,
      height: deviceScreen.height,
      backgroundColor: 'transparent'
    }
    return (
      <View style={style} {...this._panGesture.panHandlers} />
    );
  }
});

It will probably take some massaging to work with your exact use case. I played around with adding another prop to handle this case, but I do not yet have a generic solution I am happy enough with.

Let me know if that works for you.

rt2zz commented 9 years ago

Note: in investigating I found a bug in the shouldComponentUpdate code, so you will need to update to the latest

bleonard commented 9 years ago

It works! Thanks for your help.