Home

Awesome

react-native-tableview-simple

Donate Build Status npm version npm Package Quality Known Vulnerabilities GitHub license

<!-- prettier-ignore -->

:point_right: This component is used in my production app Game ideas. Make sure to check it out! :point_left:

This cross-platform component is inspired by the iOS-TableView. Made with pure CSS, the intention is to provide a flexible and lightweight alternative to a bridged component. Don't be scared of React-Native upgrades anymore!

A possible use case might be an about- or a settings-screen with a few rows. For displaying long datalists it is recommended to use the FlatList Component together with Cell and Separator Components. (see example or live demo with expo)

:rocket: If you like my component and want to buy me a coffee press the Sponsor Button and find out about GitHub Sponsors – Thanks! :point_left:

Have a look at the examples below! :-)

Installation

  1. Install as dependency:
// yarn
yarn add react-native-tableview-simple
// or npm
npm i react-native-tableview-simple --S
  1. Add needed components:
import { Cell, Section, TableView } from 'react-native-tableview-simple';

Extensible

react-native-tableview-simple provides you with some predefined CSS-styles, inspired by the native TableView. You can always mix the Cell-instances inside a Section, with other (React-Native)-Views.

Override defaults of Cell-Component

Don't repeat yourself. If you override the default props over and over again, just pass them as an object.

const cellPropsCustom = {
  cellStyle: 'Basic',
  title: 'Basic Custom',
  backgroundColor: 'black',
};


<Cell onPress={console.log} {...cellPropsCustom} />
<Cell onPress={console.log} {...cellPropsCustom} />

Separator BackgroundColor is derived from Cell BackgroundColor

The Separator-Component is a line from the left end to the right end. According to the original iOS TableView there should be an insent on the left end. This is done by separating the Separator-Component in two parts: SeparatorContainer (full width) and SeparatorInner (width - inset). (See: Separator.tsx) The SeparatorContainer has the same color that the Cell-Component above. The SeparatorInner has the default Separator Color. Pressing a Cell Component will change the color of SeparatorInner to transparent.

Why is that so complicated?

Because just hiding the separator would make the height of the component jump.

Props

TableView

The TableView component controls the theme.

PropDefaultTypeDescription
children-React.ReactNodeChildren. Should be of type Section
appearanceautostringauto: System Appearance; light: Light Appearance; dark: Dark Appearance; [string]: Appearance defined through customAppearances
customAppearances-THEME_APPEARANCE
style-ViewStyleApplied to the table wrapper

Section

The Section component is needed to render the Cells together with Separators. It's possible to use the Flatlist component instead (Example).

PropDefaultTypeDescription
allowFontScalingtrueboolRespect Text Size accessibility setting on iOS
footerComponent-React.ComponentInject any component to replace original footer (optional)
headerComponent-React.ComponentInject any component to replace original header (optional)
footer-stringFooter value
footerTextColor#6d6d72stringText color of footer
footerTextStyle{}Text.propTypes.styleThese styles will be applied to the footer Text-Component.
header-stringHeader value
headerTextColor#6d6d72stringText color of header
headerTextStyle{}Text.propTypes.styleThese styles will be applied to the header Text-Component.
hideSeparatorfalseboolHide separators
hideSurroundingSeparatorsfalseboolHide surrounding separators, best combined with roundedCorners
roundedCornersfalseboolEnable rounded corners, best combined with hideSurroundingSeparator
sectionPaddingBottom15numberPadding bottom of section
sectionPaddingTop15numberPadding top of section
sectionTintColor#EFEFF4stringBackground color of section
separatorInsetLeft15numberLeft inset of separator
separatorInsetRight0numberRight inset of separator
separatorTintColor#C8C7CCstringColor of separator
withSafeAreaViewtrue / false (on iOS <= 10)boolRender section header and footer with SafeAreaView

Cell

The style of the Cell component is inspired by the native UITableView. Because the Cell component is created with CSS only, its highly flexible. The content of the cell is separated in three views, which can all be modified via props: cellImageView | cellContentView | cellAccessoryView.

To get an idea what you can modify via props, have a look at the examples below.

PropDefaultTypeDescription
accessory-stringPredefined accessory: DisclosureIndicator, Detail, DetailDisclosure, Checkmark
accessoryColor#007AFFstringColor of accessory
accessoryColorDisclosureIndicator#C7C7CCstringColor of accessory DisclosureIndicator
allowFontScalingtrueboolRespect Text Size accessibility setting on iOS
backgroundColor#FFFstringBackground color of cell
cellStyleBasicstringPredefined styles: Basic, RightDetail, LeftDetail, Subtitle
cellAccessoryView-React.ComponentReplace accessory view component (e.g.: add Switch or ActivityIndicator)
cellContentView-React.ComponentReplace content view component
cellImageView-React.ComponentReplace image view component
children-React.ComponentAdditional content to be displayed below the cell. (e.g: Add Picker or DateTimePicker)
contentContainerStyle{}View.propTypes.styleThese styles will be applied to the content container which wraps all of the child views. Overrides cellStyle (e.g.: Override paddingLeft and paddingRight or set fixed height)
detail-string or numberDetail value
detailTextProps{}Text.propTypesThese props will be applied to the (left- / right-) detail Text-Component.
detailTextStyle{}Text.propTypes.styleThese styles will be applied to the (left- / right-) detail Text-Component.
disableImageResizefalseboolDisable resizing of image
hideSeparatorfalseboolHide the following Separator-Component
highlightActiveOpacity0.8numberOpacity of cell when touch is active
highlightUnderlayColorblackstringColor of underlay that will show through when touch is active
isDisabledfalseboolCell is disabled. onPress will not get triggered
image-React.Component (Image)Image component displayed on the left. Will resized automatically
leftDetailColor#007AFFstringText color of left detail
rightDetailColor#8E8E93stringText color of right detail
subtitleColor#000stringText color of subtitle
subtitleTextStyle{}Text.propTypes.styleThese styles will be applied to the subtitle Text-Component.
testIDundefinedstringAdd testID to root component
title-string or number or React.ComponentTitle value
titleTextColor#000stringText color of title
titleTextProps{}Text.propTypesThese props will be applied to the title Text-Component.
titleTextStyle{}Text.propTypes.styleThese styles will be applied to the title Text-Component (e.g.: update fontSize or fontFamily)
titleTextStyleDisabled{}Text.propTypes.styleThese styles will be applied to the title Text-Component, when the cell is disabled
onPress-func or falseIf set, cell will be automaticaly initialized with TouchableHighlight
onPressDetailAccessory-func or falseListen to onPress event of detail accessory
withSafeAreaViewtrue / false (on iOS <= 10)boolRender cell with SafeAreaView

Wrap Cell

Sometimes custom Cell components are needed. By creating a new component, which is based on Cell, its only necessary to set the props once. However, this comes with certain downsides. In order to keep the API as easy to use as possible, I implemented some automations for the Sections component. For example, the Cell.backgroundColor prop will also decide on the backgroundColor of the Separator component.

Given the following pattern:

import {
  Cell,
  Section,
  TableView,
} from 'react-native-tableview-simple';

const CellVariant = (props) => (
  <Cell
    {...props}
    cellContentView={
      <View
        style={{ alignItems: 'center', flexDirection: 'row', flex: 1, paddingVertical: 10 }}
      >
        <Text
          allowFontScaling
          numberOfLines={1}
          style={{ flex: 1, fontSize: 20 }}
        >
          {props.title}
        </Text>
      </View>
    }
  />
);

// ...

<TableView>
  <Section>
    <CellVariant title="Element 1" />
    <CellVariant title="Element 2" />
    <CellVariant title="Element 3" />
    <CellVariant title="Element 4" />
  </Section>
</TableView>

This pattern introduces an additional layer between Section and Cell: Section -> CellVariant -> Cell. The Section component is expecting a Cell component as a child and therefor tries to access the props as defined for the Cell component. If following the mentioned pattern, this would fail, because CellVariant.props only contains the prop title. Instead, I recommend to insert your new default props as description in this section: Override defaults of Cell-Component.

If this is not enough for you, and you still need to have a custom cell component, consider merging both approaches:

import {
  Cell,
  Section,
  TableView,
} from 'react-native-tableview-simple';


const cellPropsVariant = {
  hideSeparator: true,
  backgroundColor: 'black',
};

const CellVariant = (props) => (
  <Cell
    {...props}
    cellContentView={
      <View
        style={{ alignItems: 'center', flexDirection: 'row', flex: 1, paddingVertical: 10 }}
      >
        <Text
          allowFontScaling
          numberOfLines={1}
          style={{ flex: 1, fontSize: 20 }}
        >
          {props.title}
        </Text>
      </View>
    }
  />
);

// ...

<TableView>
  <Section>
    <CellVariant title="Element 1" {...cellPropsVariant} />
    <CellVariant title="Element 2" {...cellPropsVariant} />
    <CellVariant title="Element 3" {...cellPropsVariant} />
    <CellVariant title="Element 4" {...cellPropsVariant} />
  </Section>
</TableView>

Separator

In general the Separator component is used internally by the Section component. But additionally this component can be used together with FlatList. See the example below.

PropDefaultTypeDescription
backgroundColor#EFEFF4stringBackground color
insetLeft15numberLeft inset of separator
insetRight0numberRight inset of separator
isHiddenfalseboolHide separator but keeping its height
tintColor#C8C7CCstringColor of separator
withSafeAreaViewtrue / false (on iOS <= 10)boolRender separator with SafeAreaView

Examples

The following examples can be found in the folder example. To run the example project, follow these steps:

  1. git clone https://github.com/Purii/react-native-tableview-simple
  2. cd example
  3. yarn or npm i
  4. run /example/ios/example.xcodeproj via Xcode

Quick look

// ActivityIndicator as accessory
  <Cell
    title="Switch"
    cellAccessoryView={<Switch />}
    contentContainerStyle={{ paddingVertical: 4 }} // Adjust height
  />

// Switch as accessory
  <Cell
    title="ActivityIndicator"
    cellAccessoryView={<ActivityIndicator />}
  />

// TextInput
  <Cell
    cellContentView={<TextInput style={{fontSize: 16, flex: 1}} placeholder="TextInput"/>}
  />

// Image
  <Cell
    title="Image"
    image={
      <Image
        style={{ borderRadius: 5 }}
        source={{
          uri: 'https://facebook.github.io/react/img/logo_og.png',
        }}
      />
    }
  />

Use case: About-screen

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import { AppRegistry, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Cell, Section, TableView } from 'react-native-tableview-simple';

export default class App extends Component<{}> {
  render() {
    return (
      <ScrollView contentContainerStyle={styles.stage}>
        <View
          style={{
            backgroundColor: '#37474F',
            height: 500,
            alignItems: 'center',
            justifyContent: 'center',
          }}>
          <View
            style={{
              backgroundColor: '#ffc107',
              width: 80,
              height: 80,
              borderRadius: 10,
            }}
          />
        </View>
        <TableView>
          <Section footer="All rights reserved.">
            <Cell
              title="Help / FAQ"
              titleTextColor="#007AFF"
              onPress={() => console.log('open Help/FAQ')}
            />
            <Cell
              title="Contact Us"
              titleTextColor="#007AFF"
              onPress={() => console.log('open Contact Us')}
            />
          </Section>
        </TableView>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  stage: {
    backgroundColor: '#EFEFF4',
    paddingBottom: 20,
    flex: 1,
  },
});

react-native-tableview-simple vs. Native iOS

The left and middle screens are build using react-native-tableview-simple. The right one is native.

react-native-tableview-simple (Dark Appearance)react-native-tableview-simpleNative iOS
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  ActivityIndicator,
  AppRegistry,
  Dimensions,
  Image,
  ScrollView,
  StyleSheet,
  Switch,
  Text,
  TextInput,
  View,
} from 'react-native';
import { Cell, Section, TableView } from 'react-native-tableview-simple';

export default class App extends Component<{}> {
  render() {
    return (
      <ScrollView contentContainerStyle={styles.stage}>
        <TableView>
          <Section header="STANDARD" footer="A Footer">
            <Cell cellStyle="Basic" title="Basic" />
            <Cell cellStyle="RightDetail" title="RightDetail" detail="Detail" />
            <Cell cellStyle="LeftDetail" title="LeftDetail" detail="Detail" />
            <Cell
              cellStyle="Subtitle"
              title="Subtitle"
              detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
            />
            <Cell
              cellStyle="Basic"
              title="Pressable w/ accessory"
              accessory="DisclosureIndicator"
              onPress={() => console.log('Heyho!')}
            />
          </Section>
          <Section header="DISABLED">
            <Cell cellStyle="Basic" isDisabled title="Basic" />
            <Cell
              cellStyle="RightDetail"
              isDisabled
              title="RightDetail"
              detail="Detail"
            />
            <Cell
              cellStyle="LeftDetail"
              isDisabled
              title="LeftDetail"
              detail="Detail"
            />
            <Cell
              cellStyle="Subtitle"
              isDisabled
              title="Subtitle"
              detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
            />
            <Cell
              cellStyle="Basic"
              isDisabled
              title="Pressable w/ accessory"
              accessory="DisclosureIndicator"
              onPress={() => console.log('Heyho!')}
            />
          </Section>
          <Section header="ACCESSORY">
            <Cell
              cellStyle="Basic"
              accessory="DisclosureIndicator"
              title="Basic"
            />
            <Cell
              cellStyle="RightDetail"
              accessory="DetailDisclosure"
              title="RightDetail"
              detail="Detail"
            />
            <Cell
              cellStyle="LeftDetail"
              accessory="Detail"
              title="LeftDetail"
              detail="Detail"
            />
            <Cell
              cellStyle="Subtitle"
              accessory="Checkmark"
              title="Subtitle"
              detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
            />
            <Cell
              cellStyle="Basic"
              accessory="Detail"
              title="Pressable w/ accessory"
              onPress={() => console.log('Heyho!')}
            />
          </Section>
          <Section header="Image" footer="A Footer">
            <Cell
              cellStyle="Basic"
              title="Basic"
              image={
                <Image
                  style={{ borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
            <Cell
              cellStyle="RightDetail"
              title="RightDetail"
              detail="Detail"
              image={
                <Image
                  style={{ borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
            <Cell
              cellStyle="LeftDetail"
              title="LeftDetail"
              detail="Detail"
              image={
                <Image
                  style={{ borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
            <Cell
              cellStyle="Subtitle"
              title="Subtitle"
              detail="No linebreakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
              image={
                <Image
                  style={{ borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
            <Cell
              cellStyle="Basic"
              title="Pressable w/ accessory"
              accessory="DisclosureIndicator"
              onPress={() => console.log('Heyho!')}
              image={
                <Image
                  style={{ borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
            <Cell
              cellStyle="Basic"
              title="Disable image resize"
              disableImageResize
              image={
                <Image
                  style={{ height: 50, width: 50, borderRadius: 5 }}
                  source={{
                    uri: 'https://facebook.github.io/react/img/logo_og.png',
                  }}
                />
              }
            />
          </Section>
          <Section header="MISC">
            <Cell
              cellStyle="RightDetail"
              title="RightDetail"
              detail="Detail"
              rightDetailColor="#6cc644"
            />
            <Cell
              cellStyle="LeftDetail"
              title="LeftDetail"
              detail="Detail"
              leftDetailColor="#6cc644"
            />
            <Cell
              cellStyle="Basic"
              title="Switch"
              cellAccessoryView={<Switch />}
              contentContainerStyle={{ paddingVertical: 4 }}
            />
            <Cell
              cellStyle="Basic"
              title="ActivityIndicator"
              cellAccessoryView={<ActivityIndicator />}
            />
            <Cell
              cellContentView={
                <TextInput
                  style={{ fontSize: 16, flex: 1 }}
                  placeholder="TextInput"
                />
              }
            />
          </Section>
          <Section header="CUSTOMCELLS">
            <Cell
              onPress={() => console.log('Heyho!')}
              contentContainerStyle={{ alignItems: 'flex-start', height: 60 }}
              cellContentView={
                <Text style={{ flex: 1, fontSize: 16 }}>
                  Custom height with Cell-Component
                </Text>
              }
            />
          </Section>
          <Section headerComponent={<CustomSectionHeader />}>
            <Cell cellStyle="Basic" title="Section uses prop headerComponent" />
          </Section>
        </TableView>
        <View
          style={{
            minHeight: Dimensions.get('window').height,
          }}>
          <View
            style={{
              backgroundColor: '#37474F',
              height: 500,
              alignItems: 'center',
              justifyContent: 'center',
            }}>
            <View
              style={{
                backgroundColor: '#ffc107',
                width: 80,
                height: 80,
                borderRadius: 10,
              }}
            />
          </View>
          <TableView>
            <Section footer="All rights reserved.">
              <Cell
                title="Help / FAQ"
                titleTextColor="#007AFF"
                onPress={() => console.log('open Help/FAQ')}
              />
              <Cell
                title="Contact Us"
                titleTextColor="#007AFF"
                onPress={() => console.log('open Contact Us')}
              />
            </Section>
          </TableView>
        </View>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  stage: {
    backgroundColor: '#EFEFF4',
    paddingTop: 20,
    paddingBottom: 20,
  },
});

Render with FlatList

Be aware of the prop keyboardShouldPersistTaps if using ScrollView or similar components. (See #85)

import React from 'react';
import { FlatList } from 'react-native';

import { Cell, Separator, TableView } from 'react-native-tableview-simple';

const data = [
  { id: 1, title: '1' },
  { id: 2, title: '2' },
  { id: 3, title: '3' },
  { id: 4, title: '4' },
];

export default ExampleWithFlatList = () => (
  <TableView style={{ flex: 1 }}>
    <FlatList
      data={data}
      keyExtractor={(item, index) => item.id}
      renderItem={({ item, separators }) => (
        <Cell
          title={item.title}
          onPress={console.log}
          onHighlightRow={separators.highlight}
          onUnHighlightRow={separators.unhighlight}
        />
      )}
      ItemSeparatorComponent={({ highlighted }) => (
        <Separator isHidden={highlighted} />
      )}
    />
  </TableView>
);

Try it out

Try it in Expo: https://snack.expo.io/@purii/react-native-tableview-simple