cooperka / react-native-snackbar

:candy: Material Design "Snackbar" component for Android and iOS.
Other
823 stars 152 forks source link

Displace action button/content instead of overlaying #11

Open JulianKingman opened 7 years ago

JulianKingman commented 7 years ago

Material design recommends that the snackbar not overlay an action button, is it possible to do that with this package?

here: https://material.io/guidelines/components/snackbars-toasts.html#snackbars-toasts-usage

cooperka commented 7 years ago

Hi @JulianKingman, thanks for the link. I agree we should follow this recommendation as closely as possible. Unfortunately there's no real way for us to know if the app has an action button or not, so we can't make it happen automatically, but I wonder if we could support some sort of redux event to broadcast how tall the snackbar is at any given point (or at least broadcast when it toggles visible/hidden).

For now, there is no easy solution for you, but I do want to support this soon. I'm considering re-writing the entire module to be pure JS instead of native, and that would make this type of thing easier to support.

I'll update you here when there's a change. Curious to hear your thoughts about this, too!

JulianKingman commented 7 years ago

What if you require manually adding the snackbar component, that way it could actually take up space on the layout, and be positioned in the right place for it? For example:

render() {
  <View>
    <Toolbar />
    <Content>
      <ActionButton />
    </Content>
    <Snackbar />
  </View>
}
cooperka commented 7 years ago

@JulianKingman that's a good idea, but currently the Snackbar UI is implemented natively, so I don't think that would be possible without rewriting the whole thing in JS. That's something I want to do, but it's a big change and probably won't be done for a while!

We could add a dummy component that simply takes up space underneath the native Snackbar, but that's hacky and may not work very well if it just pushes up all content. However, it might work if you include it in a view with just your FAB... something like this:

<View>
  <FloatingActionButton />
  <SnackbarDummy />
</View>

Android uses a CoordinatorLayout to handle this when the FAB is also native, but in your case you have a native Snackbar and a JS FAB so it's more tricky ;)

Are you willing to make a PR for this?

iRoachie commented 7 years ago

If you're using react-native-material-ui's ActionButton, I just sent in a pr that implements this. https://github.com/xotahal/react-native-material-ui/pull/144

cooperka commented 7 years ago

@iRoachie that's a neat workaround, but I don't think that's the right way to go about it. What about exporting a SnackbarAvoidingView component that does the exact same thing, but from this library? Then it's generalizable and can be used by anyone for anything, not just that specific library.

Slightly modified from the original suggestion above, it could be used like:

<SnackbarAvoidingView>
  <ActionButton />
</SnackbarAvoidingView>

or as a HOC:

render() {
  const SnackbarAvoidingActionButton = withSnackbarAvoidingView(ActionButton);
  return <SnackbarAvoidingActionButton />;
}
iRoachie commented 7 years ago

@cooperka We'd still need a way, to notify the view when the snackbar is showing as well as the duration, and height, etc. so I'm not seeing any difference. Am I missing something?

cooperka commented 7 years ago

The difference is that SnackbarAvoidingView is the one that animates its height, rather than ActionButton. So instead of needing to send a PR to every library that has a component that might want to animate away from a snackbar, we can just export a wrapper from this library and anyone can use it.

iRoachie commented 7 years ago

Ah gotcha 👍

iRoachie commented 7 years ago

@cooperka Fresh off the press. https://github.com/iRoachie/react-native-snackbar-avoiding-view

cooperka commented 7 years ago

Hey, nice work!

Would you mind also submitting a PR to add that as a dependency to this library, and simply export it (so people don't need to install both modules separately)? Seems like it would be common enough to want both. It's cool that your module can be used as a standalone too.

cooperka commented 7 years ago

I wonder if we could also hook it up to (optionally) automatically call avoidSnackbar() during Snackbar.show(). That would be awesome.

iRoachie commented 7 years ago

@cooperka that sounds good actually, just wondering. Is an ActionButton the only use case that the snackbar avoids?

cooperka commented 7 years ago

Anything with the same z-index as the snackbar should move to avoid it, per material design specs (because in real life, things can't occupy the same space). The only other thing defined to have the same z-index (6) is the FAB: https://material.io/guidelines/material-design/elevation-shadows.html#elevation-shadows-elevation-android

So yes, that's pretty much the only thing you need to worry about.

iRoachie commented 7 years ago

@cooperka Soo my method behind doing that is to pass a ref to the SnackbarAvoidView into the options for Snackbar.show, getting a red warning that I have no idea how to fix.

Invalid data message - all must be length 11

Snackbar.show({
      title: 'That\' right FAB, move!',
      duration: Snackbar.LENGTH_LONG,
      ref: this.refs.avoidingView
})
cooperka commented 7 years ago

@iRoachie try cleaning all caches and starting fresh. I got that message before when running mismatched versions of React Native (one in JS and a different one in native code).

cooperka commented 7 years ago

@iRoachie did you end up getting it to work?

iRoachie commented 7 years ago

@cooperka Sorry about that, went on the backlog for a bit. Will knock up something between now and weekend

aMarCruz commented 6 years ago

@cooperka , if you expose onShow and onHide events this issue have an easy solution. Anyway, your lib is a great work, thanks.

cooperka commented 6 years ago

Hmm, that's an interesting idea @aMarCruz. It seems like there might be some delay using events though, which could look janky (if your FAB rises up 100 ms after the snackbar appears).

It would be a decent temporary solution and I'm happy to accept a PR for it!

aMarCruz commented 6 years ago

@cooperka , Unfortunately I don't have enough experience on Android and know nothing about iOS, but will try to make something about this.

cooperka commented 6 years ago

The event could be fired from JS, no native code needed. Have a look at src/index.js show().

aMarCruz commented 6 years ago

mmm... I'm thinking about the native "onDismiss" method of Snackbar.Callback to catch dismiss events, it works without CoordinatorLayout, by anyway I'll make a try. Thanks.

dantman commented 6 years ago

Animated.event with useNativeDriver: true on events like onScroll can handle event animations entirely natively. I wonder if it's possible for libraries to define their own events that do the same.

Then we could fire some sort of onSnackbarOffsetChange on every frame while the snackbar is sliding in or out, and as long as transform and useNativeDriver are used on whatever else is being animated the animation will be kept in sync.

cooperka commented 6 years ago

@dantman nice suggestion, that method would be ideal if anyone is up for implementing it.

dantman commented 6 years ago

The Snackbar's class on Android doesn't seem to have animation information, so perhaps we'll have to trick CoordinatorLayout to giving us the information we need. Possibly using CoordinatorLayout.Behavior#onApplyWindowInsets if that works.