@cawfree/react-native-elastic-footer

1.0.5 • Public • Published

react-native-elastic-footer

A React Native ScrollView footer with a little give.

Compatible with React Native Web.

🚀 Getting Started

Using npm:

npm install --save @cawfree/react-native-elastic-footer

Using yarn:

yarn add @cawfree/react-native-elastic-footer

✍️ Example

In the example below, we show how to use the <ElasticFooter/> to drive a dynamic update on the enclosing <ScrollView/>.

import React from 'react';
import {
  Animated,
  Image,
  ScrollView,
  Platform,
  View,
  StyleSheet,
} from 'react-native';

import ElasticFooter from '@cawfree/react-native-elastic-footer';

const styles = StyleSheet.create(
  {
    container: {
      position: 'absolute',
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
      backgroundColor: 'black',
    },
    scrollView: {
      flexDirection: 'row',
    },
    image: {
      width: 300,
      height: 350,
    },
    footer: {
      position: 'absolute',
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
      overflow: 'hidden',
      alignItems: 'center',
      justifyContent: 'center',
    },
    icon: {
      width: 70,
      height: 70,
    },
  },
);

class App extends React.Component {
  constructor(nextProps) {
    super(nextProps);
    this.__onHandleMixins = this.__onHandleMixins.bind(this);
    this.__onRefresh = this.__onRefresh.bind(this);
    this.__onCancel = this.__onCancel.bind(this);
    this.state = {
      onScroll: null,
      refreshing: false,
      items: [
        { key: 'https://magic.wizards.com/sites/mtg/files/images/card/en_5eDl5u25Hzm.png' },
        { key: 'https://magic.wizards.com/sites/mtg/files/images/card/en_WUdaayMnfL.png' },
        { key: 'https://magic.wizards.com/sites/mtg/files/images/card/QRay1SEQ9e.png' },
        { key: 'https://magic.wizards.com/sites/mtg/files/images/card/Tz0PAj1LTD_0.png' },
      ],
      animRotate: new Animated.Value(0),
    };
  }
  componentWillUpdate(nextProps, nextState) {
    const {
      refreshing,
      animRotate,
    } = nextState;
    if (refreshing && !this.state.refreshing) {
      animRotate.setValue(0);
      Animated.timing(
        animRotate,
        {
          toValue: 1,
          duration: 1000,
          useNativeDriver: true,
        },
      )
        .start(() => console.log('fin'));
    }
  }
  __onHandleMixins(onScroll) {
    this.setState(
      {
        onScroll,
      },
    );
  }
  __onCancel(contentHeight) {
    const { scrollView } = this.refs;
    scrollView
      .scrollTo(
        {
          x: 0,
          y: contentHeight - 20,
          animated: true,
        },
      );
    this.setState(
      {
        refreshing: false,
      },
    );
  }
  __onRefresh(contentHeight) {
    // XXX: You can just leave the ElasticFooter open...
    return new Promise(resolve => this.setState({ refreshing: true }, resolve))
      // XXX: Or you can fetch data and programmatically close it once you're done.
      .then(() => new Promise(resolve => setTimeout(resolve, 1000)))
      .then(() => new Promise(resolve => this.setState({
        items: [
          { key: 'https://media.wizards.com/2018/images/magic/GRN/callout/en_Z6I4dSYzkH.png' },
          ...this.state.items,
        ],
        refreshing: false,
      }, resolve)))
      .then(() => {
        const { scrollView } = this.refs;
        scrollView.scrollTo(
          {
            x: 0,
            y: 0,
            animated: true,
          },
        );
      });
  }
  render() {
    const {
      onScroll,
      refreshing,
      items,
      animRotate,
    } = this.state;
    return (
      <View
        style={styles.container}
      >
        <ScrollView
          ref="scrollView"
          onScroll={onScroll}
          style={styles.scrollView}
          scrollEventThrottle={0.1}
        >
          {items.map(({ key }) => (
            <Image
              key={key}
              style={styles.image}
              source={{ uri: key }}
            />
          ))}
          <ElasticFooter
            maxHeight={120}
            onHandleMixins={this.__onHandleMixins}
            onRefresh={this.__onRefresh}
            onCancel={this.__onCancel}
            refreshing={refreshing}
          >
            {({ animValue, refreshing }) => (
              <Animated.View
                style={[
                  styles.footer,
                  {
                    backgroundColor: '#000000',
                  },
                ]}
              >
                <Animated.Image
                  style={[
                    styles.icon,
                    {
                      opacity: Animated.add(0.5, animValue),
                      transform: [
                        {
                          rotate: animRotate.interpolate(
                            {
                              inputRange: [0, 1],
                              outputRange: ['0deg', '360deg'],
                            },
                          ),
                        },
                      ],
                    },
                  ]}
                  source={{ uri: 'https://flaticons.net/gd/makefg.php?i=icons/Miscellaneous/Sword-03.png&r=255&g=255&b=255' }}
                />
              </Animated.View>
            )}
          </ElasticFooter>
        </ScrollView>
      </View>
    );
  }
}

let hotWrapper = () => () => App;
if (Platform.OS === 'web') {
  const { hot } = require('react-hot-loader');
  hotWrapper = hot;
}
export default hotWrapper(module)(App);

📜 Prop Types

Name Type Required Default Value Description
onHandleMixins func false onScroll => null Supplies a callback method which needs to be dispatched to the enclosing 's onScroll prop.
maxHeight number false 100 Defines the size of the ElasticFooter when fully opened.
refreshing bool false false Defines whether the ElasticFooter is expanded.
onRefresh func false () => null Called when the user has dragged the ElasticFooter to the maxHeight, signifying that they wish to refresh.
onCancel func false () => null Called when the user has began to expand the ElasticFooter but bailed out before reaching the maxHeight.
duration number false 300 The duration for open/close animations.
debounce number false 120 The amount of time to wait before processing scroll events. Warning: You must choose a debounce value which is sufficient for your animation duration.
children function false ({ animValue, refreshing }) => ... A function which accepts the animated value animValue, which varies from 0 -> 1, and is an indicator of the percentage of expansion.

✌️ License

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i @cawfree/react-native-elastic-footer

Weekly Downloads

1

Version

1.0.5

License

MIT

Unpacked Size

6.63 MB

Total Files

110

Last publish

Collaborators

  • cawfree