Harish Gyanani
Harish Gyanani

Reputation: 1395

Need to show Expandable list view inside navigation drawer

I am an Android Application Developer. I have started working on React-Native. I am unable to find a way to show expandable list inside navigation drawer. Suggest a library if this functionality can be done in that.

navigationOptions does not have a way to provide a list (refer code below).

I want to show expandable view like item 4
I want to show expandable view like item 4
My Code is :-

import {DrawerNavigator} from 'react-navigation';
import React, {Component} from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  Image,
  View,
  TouchableHighlight
} from 'react-native';

import Screen1 from './screen/Screen1'
import Screen2 from './screen/Screen2'
const util = require('util');

class MyHomeScreen extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      headertitle: 'ffffff'
    };
  }

  componentWillReceiveProps(nextProps) {
    navigationOptions = {
      title: this.nextProps.headertitle
    };
  }

  static navigationOptions = {
    drawerLabel: 'Home',
    drawerIcon: ({tintColor}) => (
      <Image
      source={require('./images/document.png')}
      style={[
      styles.icon, {
        tintColor: tintColor
      }
    ]}/>),
    title: 'NIIT'
  };

  render() {

    return (<Screen1/>);
  }
}

class MyNotificationsScreen extends React.Component {
  static navigationOptions = {
    drawerLabel: 'Notifications',
    drawerIcon: ({tintColor}) => (<Image source={require('./images/smartphone.png')} style={[styles.icon]}/>),
    title: 'Gnome'
  };

  render() {
    return (<Screen2/>);
  }
}

const styles = StyleSheet.create({
  icon: {
    width: 24,
    height: 24
  }
});

const DrawerScreen = DrawerNavigator({
  Screen1: {
    screen: MyHomeScreen
  },
  Screen2: {
    screen: MyNotificationsScreen
  }
}, {headerMode: 'none'})

export default DrawerScreen;

Upvotes: 1

Views: 10563

Answers (3)

Pickled Brain
Pickled Brain

Reputation: 333

I think this is a single class, simple implementation of what the OP is asking for. It uses react-navigation v5. It's a standalone component that is configured via the ExpandableDrawerProps (title is the name of parent drawer, i.e. what contains the subdrawers and has no navigation, and choices is a map of label name to navigation screen component names.) It is written in TypeScript (both of these are .tsx files), so if you're not using TypeScript, just strip out the typing.

import {
  DrawerContentComponentProps,
  DrawerContentScrollView,
  DrawerItem,
} from '@react-navigation/drawer';
import React from 'react';
import { Text, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import styles from './styles';

export type ExpandableDrawerProps = DrawerContentComponentProps & {
  title: string;
  choices: Map<string, string>;
};

export default class ExpandableDrawer extends React.Component<
  ExpandableDrawerProps,
  {
    isExpanded: boolean;
  }
> {
  constructor(props: ExpandableDrawerProps, state: { isExpanded: boolean }) {
    super(props);
    this.state = state;
  }

  onPress = (): void => {
    this.setState(() => {
      return {
        isExpanded: !this.state.isExpanded,
      };
    });
  };

  render = (): JSX.Element => {
    return (
      <View style={styles.container}>
        <TouchableOpacity
          activeOpacity={0.8}
          onPress={this.onPress}
          style={styles.heading}
        >
          <Text style={styles.expander}>{this.props.title}</Text>
        </TouchableOpacity>

        {this.state.isExpanded ? (
          <DrawerContentScrollView>
            <View style={styles.expandedItem}>
              {[...this.props.choices.keys()].map(
                (label: string): JSX.Element | null => {
                  const screen = this.props.choices.get(label);
                  if (screen != undefined) {
                    return (
                      <DrawerItem
                        key={label}
                        label={label}
                        onPress={(): void => {
                          this.props.navigation.navigate(screen);
                        }}
                      />
                    );
                  } else {
                    return null;
                  }
                }
              )}
            </View>
          </DrawerContentScrollView>
        ) : null}
      </View>
    );
  };
}

You can drop that code in a file, make a simple styles file or remove them from that code, and then you're able to use <ExpandableDrawerMenu {...expandable} /> in your normal drawer navigation.

Here's how I used it in a normal navigation drawer.

const DrawerContent = (props: DrawerContentComponentProps): JSX.Element => {
  const c = new Map<string, string>();
  c.set('SubItem 1', 'SubItem1');
  c.set('SubItem 2', 'SubItem2');
  const expandable: ExpandableDrawerProps = {
    title: 'Expandable Drawer',
    choices: c,
    navigation: props.navigation,
    state: props.state,
    descriptors: props.descriptors,
    progress: props.progress,
  };
  return (
    <DrawerContentScrollView {...props}>
      <View style={styles.drawerContent}>
        <Drawer.Section style={styles.drawerSection}>
          <DrawerItem
            label="Item 1"
            onPress={(): void => {
              props.navigation.navigate('Item1');
            }}
          />

          <ExpandableDrawerMenu {...expandable} />

          <DrawerItem>
            label="Item 2"
            onPress={(): void => {
              props.navigation.navigate('Item2');
            }}
          />
          <DrawerItem
            label="Item 3"
            onPress={(): void => {
              props.navigation.navigate('Item3');
            }}
          />
        </Drawer.Section>
      </View>
    </DrawerContentScrollView>
  );
};

export default class Navigator extends Component {
  render = (): JSX.Element => {
    const Drawer = createDrawerNavigator();

    return (
      <NavigationContainer>
        <Drawer.Navigator
          drawerContent={(props: DrawerContentComponentProps): JSX.Element =>
            DrawerContent(props)
          }
          initialRouteName="Item1"
        >
          <Drawer.Screen name="Item1" component={Item1Screen} />
          <Drawer.Screen name="SubItem1" component={SubItem1Screen} />
          <Drawer.Screen name="SubItem2" component={SubItem2Screen} />
          <Drawer.Screen name="Item2" component={Item2Screen} />
          <Drawer.Screen name="Item3" component={Item3Screen} />
        </Drawer.Navigator>
      </NavigationContainer>
    );
  };
}

Upvotes: 4

Harish Gyanani
Harish Gyanani

Reputation: 1395

I have developed a solution for this problem. My code uses
"@react-navigation/drawer": "^5.1.1" and
"@react-navigation/native": "^5.0.9".

Gihub link - https://github.com/gyanani-harish/ReactNative-ExpandableDrawerMenu

Upvotes: 0

Kraylog
Kraylog

Reputation: 7563

react-navigation does not, at this time, support a collapsible menu in the drawer navigator.

You can, however, implement your own, by supplying your own contentComponent to the navigator:

const DrawerScreen = DrawerNavigator({
  Screen1: {
    screen: MyHomeScreen
  },
  Screen2: {
    screen: MyNotificationsScreen
  }
}, {
  headerMode: 'none',
  contentComponent: MyDrawer
})

const MyDrawer = (props) => ...

See the documentation for more information.

You can use something like react-native-collapsible to achieve the effect of the collapsible menu itself.

Upvotes: 1

Related Questions