Repository: mlaursen/react-md
Pull request #96 (2016-06-03 19:14:39)

Feature/navigation drawer rehaul
This pull request adds new functionality to the `NavigationDrawer`. It will now respond better to different screen sizes and the state is locally managed instead of be controlled.

This also completes #48 and #23 

Once 0.3.0 is ready for release, commit 13a228b7196023284d8ad735bfeae6a19bf6c0a7 will need to be reverted again to get the correct css.


Patch:
diff --git a/docs/UpgradeGuide.md b/docs/UpgradeGuide.md
new file mode 100644
index 0000000000..ff973ea75a
--- /dev/null
+++ b/docs/UpgradeGuide.md
@@ -0,0 +1,51 @@
+# react-md Upgrade Guide
+
+## Upgrading from 0.2.x to 0.3.x
+
+### NavigationDrawer
+The `NavigationDrawer` component was redone to respond to different device sizes better.
+
+Some of the required changes:
+
+- `isOpen` - Has been removed.  See [onDrawerChange](#ondrawerchange) for new functionality.
+- `style` - Has been renamed to `drawerStyle` to better match existing naming conventions.
+- `className` - Has been renamed to `drawerClassName` to better match existing naming conventions.
+- `containerStyle` - Has been renamed to `style` to better match existing naming conventions.
+- `containerClassName` - Has been renamed to `className` to better match existing naming conventions.
+- `title` - Has been renamed to `drawerTitle` to be more specific.
+- `openDrawer` - Has been removed. See [onDrawerChange](#ondrawerchange) for new functionality.
+- `closeDrawer` - Has been removed. See [onDrawerChange](#ondrawerchange) for new functionality.
+- `drawerType` - Has been removed. See [Responsive Drawer Changes](#respnsive-drawer-changes) for new functionality.
+- `navHeader` - Has been removed.
+- `navHeaderChildren` - Has been renamed to `drawerChildren` for clarity.
+- `autoclose` - New boolean prop that will automatically close the drawer on the temporary drawer types when a nav item is clicked if this is set to `true`.
+
+#### Responsive Drawer Changes
+The `drawerType` prop has been removed so that there is now a specific drawer type
+at different media sizes.
+
+Six props: `mobileDrawerType`, `mobileMinWidth`, `tabletDrawerType`, `tabletMinWidth`, `desktopDrawerType`, and `desktopMinWidth`
+have been created to help with the media flows.
+
+The mobile drawer type can only be temporary or temporary mini, while the tablet and desktop
+drawer types can be any varient.
+
+The `mobileDrawerType` will be chosen if the `mobileMinWidth` matches in the media query
+and the max width is less than `desktopMinWidth - 1`.
+
+The `tabletDrawerType` will be used if the `tabletMinWidth` matches in the media query
+and the max width is less than `desktopMinWidth - 1` and the device orientation is `landscape`.
+If the device orientation, the `mobileDrawerType` will be used instead.
+
+The `desktopDrawerType` will be used if the media query matches the `desktopMinWidth`.
+
+
+#### onDrawerChange
+
+Definition: `onDrawerChange(isDrawerOpen)`
+
+Since the `openDrawer` and `closeDrawer` props were removed, you can get the current
+drawer open state by adding a `onDrawerChange` prop. This function will give you
+a boolean for the next open state of the drawer. It will be triggered when a media
+resize happens that changes the open state, or when the user toggles it for a temporary
+or persistent drawer.
diff --git a/src/js/NavigationDrawers/Drawer.js b/src/js/NavigationDrawers/Drawer.js
new file mode 100644
index 0000000000..99d2b53062
--- /dev/null
+++ b/src/js/NavigationDrawers/Drawer.js
@@ -0,0 +1,195 @@
+import React, { Component, PropTypes } from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import classnames from 'classnames';
+
+import Divider from '../Dividers';
+import { List, ListItem } from '../Lists';
+import Subheader from '../Subheaders';
+import DrawerHeader from './DrawerHeader';
+
+/**
+ * The `Drawer` component is another version of the `Sidebar` component
+ * that is built into the `NavigationDrawer` component.
+ */
+export default class Drawer extends Component {
+  constructor(props) {
+    super(props);
+
+    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+  }
+
+  static propTypes = {
+    /**
+     * An optional style to apply to the drawer.
+     */
+    style: PropTypes.object,
+
+    /**
+     * An optional className to apply to the drawer.
+     */
+    className: PropTypes.string,
+
+    /**
+     * Any additional children to display in the drawer's header.
+     */
+    children: PropTypes.node,
+
+    /**
+     * Any children used to render the close button for a persistent Drawer.
+     */
+    closeIconChildren: PropTypes.node,
+
+    /**
+     * The icon className to render the close button for a persistent Drawer.
+     */
+    closeIconClassName: PropTypes.string,
+
+    /**
+     * An optional title to display in the header.
+     */
+    title: PropTypes.string,
+
+    /**
+     * Boolean if the `closeDrawer` function should be called automatically when
+     * a nav item is clicked.
+     */
+    autoclose: PropTypes.bool.isRequired,
+
+    /**
+     * A function to close the drawer for persistent and temporary drawers.
+     */
+    closeDrawer: PropTypes.func.isRequired,
+
+    /**
+     * Boolean if the drawer is temporary.
+     */
+    temporary: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the drawer is persistent.
+     */
+    persistent: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the drawer is the mini variant.
+     */
+    mini: PropTypes.bool.isRequired,
+
+    /**
+     * The current drawer type for the media size.
+     */
+    drawerType: PropTypes.string.isRequired,
+
+    /**
+     * Boolean if the drawer is currently open.
+     */
+    isOpen: PropTypes.bool.isRequired,
+
+    /**
+     * The nav items to display in the drawer.
+     *
+     * @see NavigationDrawer for more info
+     */
+    navItems: PropTypes.arrayOf(PropTypes.oneOfType([
+      PropTypes.node,
+      PropTypes.shape({
+        /**
+         * Boolean if this item should be rendered as a divider.
+         */
+        divider: PropTypes.bool,
+
+        /**
+         * Boolean if this item should be rendered as a subheader.
+         */
+        subheader: PropTypes.bool,
+
+        /**
+         * An optional component to render the `ListItem` as.
+         */
+        component: PropTypes.oneOfType([
+          PropTypes.string,
+          PropTypes.func,
+        ]),
+
+        /**
+         * The main text to be displayed in a `Subheader` or a `ListItem`.
+         */
+        primaryText: PropTypes.string,
+      }),
+    ])).isRequired,
+  };
+
+  mapItemsToComponents = (item, key) => {
+    if(React.isValidElement(item)) {
+      return item;
+    }
+
+    const { mini, isOpen } = this.props;
+    const { divider, subheader, nestedItems, ...remainingProps } = item;
+    let component;
+    if(divider) {
+      component = Divider;
+    } else if(subheader) {
+      component = Subheader;
+    } else {
+      component = ListItem;
+    }
+
+    const props = Object.assign({}, remainingProps, {
+      key: item.key || key,
+    });
+
+    if(nestedItems && (!mini || isOpen)) {
+      props.nestedItems = nestedItems.map(this.mapItemsToComponents);
+    }
+
+    return React.createElement(component, props);
+  };
+
+  render() {
+    const {
+      style,
+      className,
+      title,
+      closeDrawer,
+      closeIconChildren,
+      closeIconClassName,
+      navItems,
+      persistent,
+      temporary,
+      drawerType,
+      mini,
+      isOpen,
+      children,
+      autoclose,
+    } = this.props;
+
+    const items = navItems.map(this.mapItemsToComponents);
+    let header;
+    if(!mini || (mini && isOpen)) {
+      header = (
+        <DrawerHeader
+          key={header}
+          title={title}
+          closeDrawer={closeDrawer}
+          closeIconChildren={closeIconChildren}
+          closeIconClassName={closeIconClassName}
+          persistent={persistent}
+          children={children}
+        />
+      );
+    }
+
+    return (
+      <nav
+        style={style}
+        className={classnames('md-navigation-drawer', className, drawerType, {
+          'active': mini && isOpen,
+        })}
+      >
+        {header}
+        <List key="nav-items" onClick={autoclose && temporary ? closeDrawer : null}>{items}</List>
+      </nav>
+    );
+  }
+}
diff --git a/src/js/NavigationDrawers/DrawerHeader.js b/src/js/NavigationDrawers/DrawerHeader.js
new file mode 100644
index 0000000000..2cd427d80e
--- /dev/null
+++ b/src/js/NavigationDrawers/DrawerHeader.js
@@ -0,0 +1,95 @@
+import React, { Component, PropTypes } from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import classnames from 'classnames';
+
+import { IconButton } from '../Buttons';
+import Divider from '../Dividers';
+
+/**
+ * The `DrawerHeader` component is used to create a simple header for the sidebar
+ * in the Navigation Drawer. It consists of an optional title, any children, and
+ * a close icon for persistent Navigation Drawers.
+ */
+export default class DrawerHeader extends Component {
+  constructor(props) {
+    super(props);
+
+    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+  }
+
+  static propTypes = {
+    /**
+     * A className to apply.
+     */
+    className: PropTypes.string,
+
+    /**
+     * Any additional children to display after the close icon button (for persistent drawers)
+     * and the optional title.
+     */
+    children: PropTypes.node,
+
+    /**
+     * An optional title.
+     */
+    title: PropTypes.string,
+
+    /**
+     * A function that will close the drawer.
+     */
+    closeDrawer: PropTypes.func.isRequired,
+
+    /**
+     * Any children to render the close icon button.
+     */
+    closeIconChildren: PropTypes.node,
+
+    /**
+     * The icon className to use to render the close icon button.
+     */
+    closeIconClassName: PropTypes.string,
+
+    /**
+     * Boolean if the drawer is persistent.
+     */
+    persistent: PropTypes.bool.isRequired,
+  };
+
+  render() {
+    const {
+      className,
+      title,
+      children,
+      closeDrawer,
+      closeIconChildren,
+      closeIconClassName,
+      persistent,
+    } = this.props;
+
+    let headerTitle, closeBtn;
+    if(title) {
+      headerTitle = <h3 key="title" className="md-title">{title}</h3>;
+    }
+
+    if(persistent) {
+      closeBtn = (
+        <IconButton
+          key="close-btn"
+          className="md-drawer-toggle"
+          onClick={closeDrawer}
+          children={closeIconChildren}
+          className={closeIconClassName}
+        />
+      );
+    }
+
+    return (
+      <header className={classnames('md-drawer-header', className)}>
+        {headerTitle}
+        {children}
+        {closeBtn}
+        <Divider key="divider" />
+      </header>
+    );
+  }
+}
diff --git a/src/js/NavigationDrawers/DrawerToolbar.js b/src/js/NavigationDrawers/DrawerToolbar.js
new file mode 100644
index 0000000000..387bdfb7aa
--- /dev/null
+++ b/src/js/NavigationDrawers/DrawerToolbar.js
@@ -0,0 +1,121 @@
+import React, { Component, PropTypes } from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import classnames from 'classnames';
+
+import { IconButton } from '../Buttons';
+
+/**
+ * The `DrawerToolbar` component is the Toolbar that gets rendered to the right
+ * of the sliding drawer and above the main content.
+ */
+export default class DrawerToolbar extends Component {
+  constructor(props) {
+    super(props);
+
+    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+  }
+
+  static propTypes = {
+    /**
+     * An optional style to apply.
+     */
+    style: PropTypes.object,
+
+    /**
+     * An optional className to apply.
+     */
+    className: PropTypes.string,
+
+    /**
+     * The current drawer type.
+     */
+    drawerType: PropTypes.string.isRequired,
+
+    /**
+     * Any additional children to display after the menu button (if temporary or persistent)
+     * and the optional title.
+     */
+    children: PropTypes.node,
+
+    /**
+     * Boolean if the sliding drawer is currently open.
+     */
+    isOpen: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the sliding drawer is temporary.
+     */
+    temporary: PropTypes.bool.isRequired,
+
+    /**
+     * Boolean if the sliding drawer is persistent.
+     */
+    persistent: PropTypes.bool.isRequired,
+
+    /**
+     * A function that will open the sliding drawer. This will be
+     * added to the menu button on temporary and persistent drawers.
+     */
+    openDrawer: PropTypes.func.isRequired,
+
+    /**
+     * Any children to render the menu icon button.
+     */
+    menuIconChildren: PropTypes.node,
+
+    /**
+     * The icon className to use to render the menu icon button.
+     */
+    menuIconClassName: PropTypes.string,
+
+    /**
+     * An optional title to display
+     */
+    title: PropTypes.string,
+  };
+
+  render() {
+    const {
+      style,
+      className,
+      drawerType,
+      isOpen,
+      openDrawer,
+      persistent,
+      temporary,
+      menuIconChildren,
+      menuIconClassName,
+      title,
+      children,
+    } = this.props;
+
+    let menuBtn, toolbarTitle;
+    if(temporary || (!isOpen && persistent)) {
+      menuBtn = (
+        <IconButton
+          key="menu-btn"
+          onClick={openDrawer}
+          children={menuIconChildren}
+          iconClassName={menuIconClassName}
+        />
+      );
+    }
+
+    if(title) {
+      toolbarTitle = <h3 key="title" className="md-title">{title}</h3>;
+    }
+
+    return (
+      <header
+        style={style}
+        className={classnames('md-navigation-drawer-toolbar', className, drawerType, {
+          'active': isOpen && !temporary,
+        })}
+      >
+        {menuBtn}
+        {toolbarTitle}
+        {children}
+      </header>
+    );
+  }
+}
diff --git a/src/js/NavigationDrawers/NavigationDrawer.js b/src/js/NavigationDrawers/NavigationDrawer.js
index 1f02daee2b..aab97bafb2 100644
--- a/src/js/NavigationDrawers/NavigationDrawer.js
+++ b/src/js/NavigationDrawers/NavigationDrawer.js
@@ -11,6 +11,28 @@ import Overlay from '../Transitions/Overlay';
 import NavigationDrawerHeader from './NavigationDrawerHeader';
 import NavigationDrawerToolbar from './NavigationDrawerToolbar';
 
+const deprecate = (props, propName, component, to) => {
+  if(typeof props[propName] !== 'undefined') {
+    console.error(
+      `The prop '${propName}' has been renamed to '${to}' ` +
+      'for the next release. Check the upgrade docs at ' +
+      'https://github.com/mlaursen/react-md/tree/master/docs/UpgradeGuide.md ' +
+      'for more information.'
+    );
+  }
+};
+
+const removed = (props, propName) => {
+  if(typeof props[propName] !== 'undefined') {
+    console.error(
+      `The prop '${propName}' has been removed for the next release. Check ` +
+      'the upgrade docs at ' +
+      'https://github.com/mlaursen/react-md/tree/master/docs/UpgradeGuide.md ' +
+      'for more information.'
+    );
+  }
+};
+
 /**
  * Navigation drawers are an excellent component to use to set up the initial
  * layout of your application. This component combines a Navigation drawer
@@ -39,22 +61,34 @@ export default class NavigationDrawer extends Component {
     /**
      * An optional style to apply to the navigation drawer itself.
      */
-    style: PropTypes.object,
+    style: (props, propName, component) => {
+      deprecate(props, propName, component, 'drawerStyle');
+      return PropTypes.object(props, propName, component);
+    },
 
     /**
      * An optional className to apply to the navigation drawer iteself.
      */
-    className: PropTypes.string,
+    className: (props, propName, component) => {
+      deprecate(props, propName, component, 'drawerClassName');
+      return PropTypes.string(props, propName, component);
+    },
 
     /**
      * An optional style to apply to the navigation drawer container.
      */
-    containerStyle: PropTypes.object,
+    containerStyle: (props, propName, component) => {
+      deprecate(props, propName, component, 'style');
+      return PropTypes.object(props, propName, component);
+    },
 
     /**
      * An optional className to apply to the navigation drawer container.
      */
-    containerClassName: PropTypes.string,
+    containerClassName: (props, propName, component) => {
+      deprecate(props, propName, component, 'className');
+      return PropTypes.string(props, propName, component);
+    },
 
     /**
      * An optional style to apply to the main content.
@@ -79,12 +113,18 @@ export default class NavigationDrawer extends Component {
     /**
      * Boolean if the navigation drawer is currently open.
      */
-    isOpen: PropTypes.bool.isRequired,
+    isOpen: (props, propName, component) => {
+      removed(props, propName);
+      return PropTypes.bool.isRequired(props, propName, component);
+    },
 
     /**
      * An optional title to display in the navigation drawer header.
      */
-    title: PropTypes.string,
+    title: (props, propName, component) => {
+      deprecate(props, propName, component, 'drawerTitle');
+      return PropTypes.string(props, propName, component);
+    },
 
     /**
      * The main content to display.
@@ -115,13 +155,18 @@ export default class NavigationDrawer extends Component {
      * A function to call that will open the navigation drawer. This will
      * be applied to the main menu button.
      */
-    openDrawer: PropTypes.func,
+    openDrawer: (props, propName, component) => {
+      removed(props, propName);
+      return PropTypes.func(props, propName, component);
+    },
 
     /**
      * A function to call that will close the navigation drawer. If the drawer
      * is persistent or persistent mini, this function is required.
      */
     closeDrawer: (props, propName, component) => {
+      removed(props, propName);
+
       const { PERSISTENT, PERSISTENT_MINI } = NavigationDrawer.DrawerType;
       const { drawerType } = props;
       if(drawerType !== PERSISTENT && drawerType !== PERSISTENT_MINI) {
@@ -171,7 +216,10 @@ export default class NavigationDrawer extends Component {
     /**
      * The drawer type to use.
      */
-    drawerType: PropTypes.oneOf(Object.keys(NavigationDrawer.DrawerType).map(k => NavigationDrawer.DrawerType[k])),
+    drawerType: (props, propName, component) => {
+      removed(props, propName);
+      return PropTypes.oneOf(Object.keys(NavigationDrawer.DrawerType).map(k => NavigationDrawer.DrawerType[k]))(props, propName, component);
+    },
 
     /**
      * Any additional children to display in the main toolbar.
@@ -188,13 +236,19 @@ export default class NavigationDrawer extends Component {
      * before the `navItems`. This will replace the generated header if you gave the
      * `title` prop.
      */
-    navHeader: PropTypes.node,
+    navHeader: (props, propName, component) => {
+      removed(props, propName);
+      return PropTypes.node(props, propName, component);
+    },
 
     /**
      * Any additional children to display in the navigation drawer's header after the
      * `title`.
      */
-    navHeaderChildren: PropTypes.node,
+    navHeaderChildren: (props, propName, component) => {
+      deprecate(props, propName, component, 'drawerChildren');
+      return PropTypes.node(props, propName, component);
+    },
   };
 
   static defaultProps = {
diff --git a/src/js/NavigationDrawers/_NavigationDrawer.js b/src/js/NavigationDrawers/_NavigationDrawer.js
new file mode 100644
index 0000000000..980bb9757f
--- /dev/null
+++ b/src/js/NavigationDrawers/_NavigationDrawer.js
@@ -0,0 +1,516 @@
+import React, { Component, PropTypes } from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import CSSTransitionGroup from 'react-addons-css-transition-group';
+import classnames from 'classnames';
+
+import Drawer from './Drawer';
+import DrawerToolbar from './DrawerToolbar';
+import Overlay from '../Transitions/Overlay';
+
+/**
+ * Navigation Drawers are an excellent component to use to set up the initial
+ * layout of your application. This component combines a Drawer
+ * (a sidebar of nav items), an app bar, and displays any additional content.
+ *
+ * The `NavigationDrawer` component is customizable to have different
+ * display states for mobile, tablet, and desktop displays.
+ */
+export default class NavigationDrawer extends Component {
+  constructor(props) {
+    super(props);
+
+    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+    this.state = {
+      isOpen: typeof props.initiallyOpen !== 'undefined' ? props.initiallyOpen : true,
+      mobile: false,
+      tablet: false,
+      desktop: true,
+      drawerType: props.desktopDrawerType,
+    };
+  }
+
+  static DrawerType = {
+    FULL_HEIGHT: 'full-height',
+    CLIPPED: 'clipped',
+    FLOATING: 'floating',
+    PERSISTENT: 'persistent',
+    PERSISTENT_MINI: 'mini',
+    TEMPORARY: 'temporary',
+    // want styles of temporary and mini. Little hacky.
+    TEMPORARY_MINI: 'temporary mini',
+  };
+
+  static propTypes = {
+    /**
+     * An optional style to apply to the entire container.
+     */
+    style: PropTypes.object,
+
+    /**
+     * An optional className to apply to the entire container.
+     */
+    className: PropTypes.string,
+
+    /**
+     * An optional style to apply to the drawer.
+     */
+    drawerStyle: PropTypes.object,
+
+    /**
+     * An optional className to apply to the drawer.
+     */
+    drawerClassName: PropTypes.string,
+
+    /**
+     * An optional style to apply to the main content.
+     */
+    contentStyle: PropTypes.object,
+
+    /**
+     * An optional className to apply to the main content.
+     */
+    contentClassName: PropTypes.string,
+
+    /**
+     * An optional style to apply to the main toolbar.
+     */
+    toolbarStyle: PropTypes.object,
+
+    /**
+     * An optional className to applt to the main toolbar.
+     */
+    toolbarClassName: PropTypes.string,
+
+    /**
+     * An optional title to display in the navigation drawer header.
+     */
+    drawerTitle: PropTypes.string,
+
+    /**
+     * Any additional children you want to display in the navigation drawer header after
+     * the optional title.
+     */
+    drawerChildren: PropTypes.node,
+
+    /**
+     * An optional title to display in the toolbar.
+     */
+    toolbarTitle: PropTypes.string,
+
+    /**
+     * Any additional children you want to display in the main toolbar after the menu
+     * button (for persistent drawers) and the optional title.
+     */
+    toolbarChildren: PropTypes.node,
+
+    /**
+     * The main content to display.
+     */
+    children: PropTypes.node,
+
+    /**
+     * An optional boolean if the drawer is open for persistent or temporary
+     * drawers.
+     */
+    initiallyOpen: PropTypes.bool,
+
+    /**
+     * The `DrawerType` to use for mobile devices. If the `mobileMinWidth` prop
+     * matches or the `tabletMinWidth` prop matches and the orientation is portrait,
+     * this drawer type will be used.
+     */
+    mobileDrawerType: PropTypes.oneOf([
+      NavigationDrawer.DrawerType.TEMPORARY,
+      NavigationDrawer.DrawerType.TEMPORARY_MINI,
+    ]).isRequired,
+
+    /**
+     * The `DrawerType` to use for landscape oriented tablets. If the
+     * `tabletMinWidth` prop matches and the orientation is landscape, this drawer
+     * type will be used.
+     */
+    tabletDrawerType: PropTypes.oneOf([
+      NavigationDrawer.DrawerType.FULL_HEIGHT,
+      NavigationDrawer.DrawerType.CLIPPED,
+      NavigationDrawer.DrawerType.FLOATING,
+      NavigationDrawer.DrawerType.PERSISTENT,
+      NavigationDrawer.DrawerType.PERSISTENT_MINI,
+      NavigationDrawer.DrawerType.TEMPORARY,
+      NavigationDrawer.DrawerType.TEMPORARY_MINI,
+    ]).isRequired,
+
+    /**
+     * The `DrawerType` to use for desktop displays. If the `desktopMinWidth`
+     * prop matches, this drawer type will be used.
+     */
+    desktopDrawerType: PropTypes.oneOf([
+      NavigationDrawer.DrawerType.FULL_HEIGHT,
+      NavigationDrawer.DrawerType.CLIPPED,
+      NavigationDrawer.DrawerType.FLOATING,
+      NavigationDrawer.DrawerType.PERSISTENT,
+      NavigationDrawer.DrawerType.PERSISTENT_MINI,
+      NavigationDrawer.DrawerType.TEMPORARY,
+      NavigationDrawer.DrawerType.TEMPORARY_MINI,
+    ]).isRequired,
+
+    /**
+     * The min width to use for a mobile media query.
+     */
+    mobileMinWidth: PropTypes.number.isRequired,
+
+    /**
+     * The min width to use for a tablet media query.
+     */
+    tabletMinWidth: PropTypes.number.isRequired,
+
+    /**
+     * The min width to use for a desktop media query.
+     */
+    desktopMinWidth: PropTypes.number.isRequired,
+
+    /**
+     * The icon className to use for the main menu icon.
+     */
+    menuIconClassName: PropTypes.string,
+
+    /**
+     * Any children required to render the main menu icon.
+     */
+    menuIconChildren: PropTypes.node,
+
+    /**
+     * The icon className to use for closing a persistent navigation drawer.
+     */
+    closeIconClassName: PropTypes.string,
+
+    /**
+     * Any children required to render the close icon for a persistent navigation drawer.
+     */
+    closeIconChildren: PropTypes.node,
+
+    /**
+     * Boolean if the sliding drawer should automatically close when a nav item
+     * is clicked for a temporary or temporary mini drawer.
+     */
+    autoclose: PropTypes.bool.isRequired,
+
+    /**
+     * An optional function to call when the sliding drawer's open state changes.
+     * This can happen from rotating a tablet, resizing the browser window,
+     * or toggling the drawer for persistent and temporary versions. The
+     * `onDrawerChange` prop will be given the new open state.
+     *
+     * `onDrawerChange(isOpen)`
+     */
+    onDrawerChange: PropTypes.func,
+
+    /**
+     * A list of items to render in the navigation drawer. If an item
+     * is a prop object, all props will be passed to either a `ListItem`,
+     * `Divider`, or `Subheader` component.
+     *
+     * ##### Additional Info
+     */
+    navItems: PropTypes.arrayOf(PropTypes.oneOfType([
+      /**
+       * Any react component you want to render instead of using props
+       * to generate a component.
+       */
+      PropTypes.element,
+
+      /**
+       * An object of props to use that will generate either a `ListItem`, `Divider`,
+       * or a `Subheader` component. Any props not listed will be passed
+       * to the component.
+       */
+      PropTypes.shape({
+        /**
+         * Boolean if this item should be rendered as a divider.
+         */
+        divider: PropTypes.bool,
+
+        /**
+         * Boolean if this item should be rendered as a subheader.
+         */
+        subheader: PropTypes.bool,
+
+        /**
+         * An optional component to render the `ListItem` as.
+         */
+        component: PropTypes.oneOfType([
+          PropTypes.string,
+          PropTypes.func,
+        ]),
+
+        /**
+         * The main text to be displayed in a `Subheader` or a `ListItem`.
+         */
+        primaryText: PropTypes.string,
+      }),
+    ])).isRequired,
+
+    /**
+     * The transition name to use for the drawer sliding in and out of view.
+     */
+    transitionName: PropTypes.string.isRequired,
+
+    /**
+     * The transition enter duration for the drawer sliding in to view.
+     */
+    transitionEnterTimeout: PropTypes.number.isRequired,
+
+    /**
+     * The transition leave duration for the drawer sliding out of view.
+     */
+    transitionLeaveTimeout: PropTypes.number.isRequired,
+
+    /**
+     * An optional transition to use when the content changes. To disable this transition,
+     * set the enter and leave timeouts to `0`, `null`, or `undefined`. Make sure your
+     * child has a new key as well.
+     */
+    contentTransitionName: PropTypes.string.isRequired,
+
+    /**
+     * The transition enter timeout for when the main content changes. Settings this value
+     * to `0`, `null`, or `undefined` will disable the enter transition.
+     */
+    contentTransitionEnterTimeout: PropTypes.number,
+
+    /**
+     * The transition leave timeout for when the main content changes. Settings this value
+     * to `0`, `null`, or `undefined` will disable the leave transition.
+     */
+    contentTransitionLeaveTimeout: PropTypes.number,
+  };
+
+  static defaultProps = {
+    mobileDrawerType: NavigationDrawer.DrawerType.TEMPORARY,
+    tabletDrawerType: NavigationDrawer.DrawerType.PERSISTENT,
+    desktopDrawerType: NavigationDrawer.DrawerType.FULL_HEIGHT,
+    mobileMinWidth: 320,
+    tabletMinWidth: 768,
+    desktopMinWidth: 1025,
+    menuIconChildren: 'menu',
+    autoclose: true,
+    closeIconChildren: 'keyboard_arrow_left',
+    transitionName: 'drawer',
+    transitionEnterTimeout: 300,
+    transitionLeaveTimeout: 300,
+    contentTransitionName: 'cross-fade',
+    contentTransitionEnterTimeout: 300,
+  };
+
+  componentDidMount() {
+    window.addEventListener('resize', this._updateMedia);
+    this._updateMedia();
+  }
+
+  componentWillReceiveProps(nextProps) {
+    const {
+      mobileMinWidth,
+      mobileDrawerType,
+      tabletMinWidth,
+      tabletDrawerType,
+      desktopMinWidth,
+      desktopDrawerType,
+    } = this.props;
+
+    if(nextProps.mobileMinWidth !== mobileMinWidth || nextProps.mobileDrawerType !== mobileDrawerType
+      || nextProps.tabletMinWidth !== tabletMinWidth || nextProps.tabletDrawerType !== tabletDrawerType
+      || nextProps.desktopMinWidth !== desktopMinWidth || nextProps.desktopDrawerType !== desktopDrawerType) {
+      this._updateDrawerType(nextProps);
+    }
+  }
+
+  componentWillUpdate(nextProps, nextState) {
+    if(this.state.isOpen !== nextState.isOpen && nextProps.onDrawerChange) {
+      nextProps.onDrawerChange(nextState.isOpen);
+    }
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('resize', this._updateMedia);
+  }
+
+  _matches = (min, max, orientation) => {
+    let media = 'only screen';
+    if(orientation) {
+      media += ` and (orientation: ${orientation})`;
+    }
+
+    media += ` and (min-width: ${min}px)`;
+
+    if(max) {
+      media += ` and (max-width: ${max}px)`;
+    }
+
+    return window.matchMedia(media).matches;
+  };
+
+  _updateDrawerType = (props) => {
+    const {
+      mobileMinWidth,
+      mobileDrawerType,
+      tabletMinWidth,
+      tabletDrawerType,
+      desktopMinWidth,
+      desktopDrawerType,
+    } = props;
+
+    const nextState = {
+      mobile: this._matches(mobileMinWidth, desktopMinWidth - 1),
+      tablet: this._matches(tabletMinWidth, desktopMinWidth - 1, 'landscape'),
+      desktop: this._matches(desktopMinWidth),
+    };
+
+    if(nextState.tablet) {
+      nextState.drawerType = tabletDrawerType;
+    } else if(nextState.mobile) {
+      nextState.drawerType = mobileDrawerType;
+    } else {
+      nextState.drawerType = desktopDrawerType;
+    }
+
+    if(this._isFullHeight(nextState.drawerType) !== this._isFullHeight(this.state.drawerType)) {
+      nextState.isOpen = this._isFullHeight(nextState.drawerType);
+    }
+
+    this.setState(nextState);
+  };
+
+  _updateMedia = () => {
+    this._updateDrawerType(this.props);
+  };
+
+  openDrawer = () => {
+    this.setState({ isOpen: true });
+  };
+
+  closeDrawer = () => {
+    this.setState({ isOpen: false });
+  };
+
+  _isFullHeight = (drawerType) => {
+    const { FULL_HEIGHT, CLIPPED, FLOATING } = NavigationDrawer.DrawerType;
+    return [FULL_HEIGHT, CLIPPED, FLOATING].indexOf(drawerType) !== -1;
+  };
+
+  _isPersistent = (drawerType) => {
+    const { PERSISTENT, PERSISTENT_MINI } = NavigationDrawer.DrawerType;
+    return [PERSISTENT, PERSISTENT_MINI].indexOf(drawerType) !== -1;
+  };
+
+  _isTemporary = (drawerType) => {
+    const { TEMPORARY, TEMPORARY_MINI } = NavigationDrawer.DrawerType;
+    return [TEMPORARY, TEMPORARY_MINI].indexOf(drawerType) !== -1;
+  };
+
+  _isMini = (drawerType) => {
+    const { PERSISTENT_MINI, TEMPORARY_MINI } = NavigationDrawer.DrawerType;
+    return [PERSISTENT_MINI, TEMPORARY_MINI].indexOf(drawerType) !== -1;
+  };
+
+  render() {
+    const {
+      style,
+      className,
+      drawerStyle,
+      drawerClassName,
+      contentStyle,
+      contentClassName,
+      toolbarStyle,
+      toolbarClassName,
+      drawerTitle,
+      drawerChildren,
+      menuIconChildren,
+      menuIconClassName,
+      closeIconChildren,
+      closeIconClassName,
+      toolbarTitle,
+      toolbarChildren,
+      autoclose,
+      navItems,
+      children,
+      transitionName,
+      transitionEnterTimeout,
+      transitionLeaveTimeout,
+      contentTransitionName,
+      contentTransitionEnterTimeout,
+      contentTransitionLeaveTimeout,
+    } = this.props;
+
+    const { isOpen, drawerType } = this.state;
+    const persistent = this._isPersistent(drawerType);
+    const temporary = this._isTemporary(drawerType);
+    const mini = this._isMini(drawerType);
+
+    let drawer, overlay;
+    if(isOpen || mini) {
+      drawer = (
+        <Drawer
+          key="drawer"
+          style={drawerStyle}
+          className={drawerClassName}
+          autoclose={autoclose}
+          title={drawerTitle}
+          closeDrawer={this.closeDrawer}
+          closeIconChildren={closeIconChildren}
+          closeIconClassName={closeIconClassName}
+          temporary={temporary}
+          persistent={persistent}
+          navItems={navItems}
+          drawerType={drawerType}
+          children={drawerChildren}
+          mini={mini}
+          isOpen={isOpen}
+        />
+      );
+    }
+
+    if(temporary) {
+      overlay = <Overlay isOpen={isOpen} onClick={this.closeDrawer} />;
+    }
+
+    return (
+      <CSSTransitionGroup
+        style={style}
+        className={classnames('md-navigation-drawer-container', className)}
+        component="div"
+        transitionName={transitionName}
+        transitionEnterTimeout={transitionEnterTimeout}
+        transitionLeaveTimeout={transitionLeaveTimeout}
+      >
+        {drawer}
+        <CSSTransitionGroup
+          style={contentStyle}
+          className={classnames('md-navigation-drawer-content', contentClassName, drawerType, {
+            'active': isOpen && !temporary,
+          })}
+          component="div"
+          transitionName={contentTransitionName}
+          transitionEnter={!!contentTransitionEnterTimeout}
+          transitionEnterTimeout={contentTransitionEnterTimeout}
+          transitionLeave={!!contentTransitionLeaveTimeout}
+          transitionLeaveTimeout={contentTransitionLeaveTimeout}
+        >
+          <DrawerToolbar
+            isOpen={isOpen}
+            drawerType={drawerType}
+            style={toolbarStyle}
+            className={toolbarClassName}
+            temporary={temporary}
+            persistent={persistent}
+            openDrawer={this.openDrawer}
+            menuIconChildren={menuIconChildren}
+            menuIconClassName={menuIconClassName}
+            title={toolbarTitle}
+            children={toolbarChildren}
+          />
+          {children}
+          {overlay}
+        </CSSTransitionGroup>
+      </CSSTransitionGroup>
+    );
+  }
+}


Test patch:
diff --git a/src/js/NavigationDrawers/__tests__/Drawer.js b/src/js/NavigationDrawers/__tests__/Drawer.js
new file mode 100644
index 0000000000..0f57ad9107
--- /dev/null
+++ b/src/js/NavigationDrawers/__tests__/Drawer.js
@@ -0,0 +1,159 @@
+/*eslint-env jest*/
+jest.unmock('../Drawer');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  findRenderedComponentWithType,
+  scryRenderedComponentsWithType,
+} from 'react-addons-test-utils';
+
+import Drawer from '../Drawer';
+import DrawerHeader from '../DrawerHeader';
+import { List } from '../../Lists';
+
+describe('Drawer', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const props = {
+      style,
+      className,
+      navItems: [],
+      isOpen: true,
+      persistent: false,
+      temporary: false,
+      mini: false,
+      drawerType: 'clipped',
+      autoclose: true,
+      closeDrawer: jest.fn(),
+    };
+    const drawer = renderIntoDocument(<Drawer {...props} />);
+
+    const drawerNode = findDOMNode(drawer);
+    expect(drawerNode.style.display).toBe(style.display);
+    expect(drawerNode.className).toContain(className);
+  });
+
+  it('updates the className to include the drawerType and active classNames', () => {
+    let props = {
+      navItems: [],
+      isOpen: true,
+      persistent: false,
+      temporary: false,
+      mini: false,
+      drawerType: 'clipped',
+      autoclose: true,
+      closeDrawer: jest.fn(),
+    };
+    let drawer = renderIntoDocument(<Drawer {...props} />);
+    let drawerNode = findDOMNode(drawer);
+
+    expect(drawerNode.className).toContain(props.drawerType);
+    expect(drawerNode.className).not.toContain('active');
+
+    // unopened mini drawer
+    props = Object.assign({}, props, {
+      drawerType: 'mini',
+      mini: true,
+      isOpen: false,
+    });
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    drawerNode = findDOMNode(drawer);
+
+    expect(drawerNode.className).toContain(props.drawerType);
+    expect(drawerNode.className).not.toContain('active');
+
+    // opened mini drawer
+    props = Object.assign({}, props, {
+      drawerType: 'mini',
+      mini: true,
+      isOpen: true,
+    });
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    drawerNode = findDOMNode(drawer);
+
+    expect(drawerNode.className).toContain(props.drawerType);
+    expect(drawerNode.className).toContain('active');
+  });
+
+  it('renders the DrawerHeader component when not mini or open when mini', () => {
+    let props = {
+      navItems: [],
+      isOpen: true,
+      persistent: false,
+      temporary: false,
+      mini: false,
+      drawerType: 'full-height',
+      autoclose: true,
+      closeDrawer: jest.fn(),
+      title: 'Title',
+      closeIconChildren: 'what',
+      closeIconClassName: 'material-icons',
+      children: 'Hello',
+    };
+    let drawer = renderIntoDocument(<Drawer {...props} />);
+    let headers = scryRenderedComponentsWithType(drawer, DrawerHeader);
+    expect(headers.length).toBe(1);
+    expect(headers[0].props.title).toBe(props.title);
+    expect(headers[0].props.closeDrawer).toBe(props.closeDrawer);
+    expect(headers[0].props.closeIconChildren).toBe(props.closeIconChildren);
+    expect(headers[0].props.closeIconClassName).toBe(props.closeIconClassName);
+    expect(headers[0].props.persistent).toBe(props.persistent);
+    expect(headers[0].props.children).toBe(props.children);
+
+    // Closed mini drawer
+    props = Object.assign({}, props, {
+      drawerType: 'mini',
+      mini: true,
+      isOpen: false,
+    });
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    headers = scryRenderedComponentsWithType(drawer, DrawerHeader);
+    expect(headers.length).toBe(0);
+
+    // Opened mini drawer
+    props = Object.assign({}, props, {
+      isOpen: true,
+    });
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    headers = scryRenderedComponentsWithType(drawer, DrawerHeader);
+    expect(headers.length).toBe(1);
+  });
+
+  it('renders a List component with an onClick to close the drawer when temporary and autoclose is true', () => {
+    let props = {
+      navItems: [],
+      isOpen: true,
+      persistent: false,
+      temporary: true,
+      mini: false,
+      drawerType: 'temporary',
+      autoclose: true,
+      closeDrawer: jest.fn(),
+    };
+
+    let drawer = renderIntoDocument(<Drawer {...props} />);
+    let list = findRenderedComponentWithType(drawer, List);
+    expect(list.props.onClick).toBe(props.closeDrawer);
+
+    props = Object.assign({}, props, { autoclose: false });
+
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    list = findRenderedComponentWithType(drawer, List);
+    expect(list.props.onClick).toBe(null);
+
+    props = Object.assign({}, props, { temporary: false, drawerType: 'full-height' });
+
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    list = findRenderedComponentWithType(drawer, List);
+    expect(list.props.onClick).toBe(null);
+
+    props = Object.assign({}, props, { autoclose: true });
+
+    drawer = renderIntoDocument(<Drawer {...props} />);
+    list = findRenderedComponentWithType(drawer, List);
+    expect(list.props.onClick).toBe(null);
+  });
+});
diff --git a/src/js/NavigationDrawers/__tests__/DrawerHeader.js b/src/js/NavigationDrawers/__tests__/DrawerHeader.js
new file mode 100644
index 0000000000..99cb3db7d9
--- /dev/null
+++ b/src/js/NavigationDrawers/__tests__/DrawerHeader.js
@@ -0,0 +1,52 @@
+/*eslint-env jest*/
+jest.unmock('../DrawerHeader');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+} from 'react-addons-test-utils';
+
+import DrawerHeader from '../DrawerHeader';
+import { IconButton } from '../../Buttons';
+
+describe('DrawerHeader', () => {
+  it('allows for a className to be passed it', () => {
+    const props = {
+      className: 'test',
+      closeDrawer: jest.fn(),
+      persistent: false,
+    };
+
+    const header = renderIntoDocument(<DrawerHeader {...props} />);
+    const headerNode = findDOMNode(header);
+    expect(headerNode.className).toContain(props.className);
+  });
+
+  it('renders a title when the title prop is defined.', () => {
+    const props = {
+      closeDrawer: jest.fn(),
+      title: 'Hello, World!',
+      persistent: false,
+    };
+
+    const header = renderIntoDocument(<DrawerHeader {...props} />);
+    const headerNode = findDOMNode(header);
+    expect(headerNode.textContent).toBe(props.title);
+  });
+
+  it('renders a close button when persistent', () => {
+    const props = {
+      closeDrawer: jest.fn(),
+      closeIconChildren: 'close',
+      persistent: true,
+    };
+
+    const header = renderIntoDocument(<DrawerHeader {...props} />);
+    const btns = scryRenderedComponentsWithType(header, IconButton);
+    expect(btns.length).toBe(1);
+    expect(btns[0].props.children).toBe(props.closeIconChildren);
+    expect(btns[0].props.onClick).toBe(props.closeDrawer);
+  });
+});
diff --git a/src/js/NavigationDrawers/__tests__/DrawerToolbar.js b/src/js/NavigationDrawers/__tests__/DrawerToolbar.js
new file mode 100644
index 0000000000..a3e5e2db3c
--- /dev/null
+++ b/src/js/NavigationDrawers/__tests__/DrawerToolbar.js
@@ -0,0 +1,133 @@
+/*eslint-env jest*/
+jest.unmock('../DrawerToolbar');
+
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  scryRenderedComponentsWithType,
+  findRenderedDOMComponentWithClass,
+} from 'react-addons-test-utils';
+
+import DrawerToolbar from '../DrawerToolbar';
+import { IconButton } from '../../Buttons';
+
+describe('DrawerToolbar', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    let props = {
+      style,
+      className,
+      drawerType: 'clipped',
+      isOpen: true,
+      temporary: false,
+      persistent: false,
+      openDrawer: jest.fn(),
+    };
+    let drawerToolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+
+    let drawerToolbarNode = findDOMNode(drawerToolbar);
+    expect(drawerToolbarNode.style.display).toBe(style.display);
+    expect(drawerToolbarNode.className).toContain(className);
+    expect(drawerToolbarNode.className).toContain(props.drawerType);
+    expect(drawerToolbarNode.className).toContain('active');
+
+    props = Object.assign({}, props, { isOpen: false });
+    drawerToolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+
+    drawerToolbarNode = findDOMNode(drawerToolbar);
+    expect(drawerToolbarNode.style.display).toBe(style.display);
+    expect(drawerToolbarNode.className).toContain(className);
+    expect(drawerToolbarNode.className).toContain(props.drawerType);
+    expect(drawerToolbarNode.className).not.toContain('active');
+
+    props = Object.assign({}, props, { true: false, drawerType: 'temporary', temporary: true });
+    drawerToolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+
+    drawerToolbarNode = findDOMNode(drawerToolbar);
+    expect(drawerToolbarNode.style.display).toBe(style.display);
+    expect(drawerToolbarNode.className).toContain(className);
+    expect(drawerToolbarNode.className).toContain(props.drawerType);
+    expect(drawerToolbarNode.className).not.toContain('active');
+  });
+
+  it('renders a menu icon button if it is temporary or not open and persistent', () => {
+    let props = {
+      drawerType: 'clipped',
+      isOpen: true,
+      temporary: false,
+      persistent: false,
+      openDrawer: jest.fn(),
+    };
+    let toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    let btns = scryRenderedComponentsWithType(toolbar, IconButton);
+
+    expect(btns.length).toBe(0);
+
+    // Temporary drawer
+    props = Object.assign({}, props, {
+      drawerType: 'temporary',
+      temporary: true,
+    });
+    toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    btns = scryRenderedComponentsWithType(toolbar, IconButton);
+
+    expect(btns.length).toBe(1);
+
+    // Closed persistent drawer
+    props = Object.assign({}, props, {
+      drawerType: 'persistent',
+      temporary: false,
+      persistent: true,
+      isOpen: false,
+    });
+    toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    btns = scryRenderedComponentsWithType(toolbar, IconButton);
+
+    expect(btns.length).toBe(1);
+
+    // Open persistent drawer
+    props = Object.assign({}, props, {
+      isOpen: true,
+    });
+    toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    btns = scryRenderedComponentsWithType(toolbar, IconButton);
+
+    expect(btns.length).toBe(0);
+  });
+
+  it('renders a title if given', () => {
+    const props = {
+      drawerType: 'clipped',
+      isOpen: true,
+      temporary: false,
+      persistent: false,
+      openDrawer: jest.fn(),
+      title: 'Eyyyy',
+    };
+
+    const toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    const toolbarNode = findDOMNode(toolbar);
+    expect(toolbarNode.textContent).toBe(props.title);
+  });
+
+  it('renders any additional children after the menu button and title', () => {
+    let props = {
+      drawerType: 'temporary',
+      isOpen: true,
+      temporary: true,
+      persistent: false,
+      openDrawer: jest.fn(),
+      title: 'Eyyyy',
+      children: <div className="test">Wow</div>,
+    };
+
+    let toolbar = renderIntoDocument(<DrawerToolbar {...props} />);
+    let toolbarNode = findDOMNode(toolbar);
+    expect(toolbarNode.childNodes.length).toBe(3);
+    // Not sure how to test that first is icon button
+    expect(toolbarNode.childNodes[1].textContent).toBe(props.title);
+    expect(toolbarNode.childNodes[2]).toBe(findRenderedDOMComponentWithClass(toolbar, 'test'));
+  });
+});
diff --git a/src/js/NavigationDrawers/__tests__/NavigationDrawer.js b/src/js/NavigationDrawers/__tests__/NavigationDrawer.js
new file mode 100644
index 0000000000..a766fdf17b
--- /dev/null
+++ b/src/js/NavigationDrawers/__tests__/NavigationDrawer.js
@@ -0,0 +1,134 @@
+/*eslint-env jest*/
+jest.unmock('../_NavigationDrawer');
+
+import React from 'react';
+import CSSTransitionGroup from 'react-addons-css-transition-group';
+import { findDOMNode } from 'react-dom';
+import {
+  renderIntoDocument,
+  findRenderedComponentWithType,
+  scryRenderedComponentsWithType,
+} from 'react-addons-test-utils';
+
+import NavigationDrawer from '../_NavigationDrawer';
+import Drawer from '../Drawer';
+import DrawerToolbar from '../DrawerToolbar';
+import Overlay from '../../Transitions/Overlay';
+
+
+describe('NavigationDrawer', () => {
+  it('merges className and style', () => {
+    const style = { display: 'block' };
+    const className = 'test';
+    const navigationDrawer = renderIntoDocument(
+      <NavigationDrawer style={style} className={className} navItems={[]} />
+    );
+
+    const navigationDrawerNode = findDOMNode(navigationDrawer);
+    expect(navigationDrawerNode.style.display).toBe(style.display);
+    expect(navigationDrawerNode.classList.contains(className)).toBe(true);
+  });
+
+  it('renders the drawer component when open or mini with the correct props', () => {
+    const props = {
+      navItems: [{ primaryText: 'Hello' }],
+      drawerStyle: { background: 'black' },
+      drawerClassName: 'some-amazing-class-name',
+      drawerTitle: 'Some Amazing Title',
+      drawerChildren: <div>Some Stuff</div>,
+    };
+
+    let nav = renderIntoDocument(<NavigationDrawer {...props} />);
+    nav.setState({ mobile: false, tablet: false, desktop: true });
+
+    let drawers = scryRenderedComponentsWithType(nav, Drawer);
+    expect(drawers.length).toBe(1);
+    expect(drawers[0].props.style).toEqual(props.drawerStyle);
+    expect(drawers[0].props.className).toContain(props.drawerClassName);
+    expect(drawers[0].props.autoclose).toBe(NavigationDrawer.defaultProps.autoclose);
+    expect(drawers[0].props.title).toBe(props.drawerTitle);
+    expect(drawers[0].props.closeDrawer).toBe(nav.closeDrawer);
+    expect(drawers[0].props.closeIconChildren).toBe(NavigationDrawer.defaultProps.closeIconChildren);
+    expect(drawers[0].props.closeIconClassName).toBe(NavigationDrawer.defaultProps.closeIconClassName);
+    expect(drawers[0].props.temporary).toBe(false);
+    expect(drawers[0].props.persistent).toBe(false);
+    expect(drawers[0].props.mini).toBe(false);
+    expect(drawers[0].props.drawerType).toBe(NavigationDrawer.defaultProps.desktopDrawerType);
+    expect(drawers[0].props.children).toBe(props.drawerChildren);
+    expect(drawers[0].props.isOpen).toBe(true);
+    expect(drawers[0].props.navItems).toBe(props.navItems);
+
+    nav.closeDrawer();
+    drawers = scryRenderedComponentsWithType(nav, Drawer);
+    expect(drawers.length).toBe(0);
+
+    nav.setState({ drawerType: NavigationDrawer.DrawerType.TEMPORARY_MINI });
+    drawers = scryRenderedComponentsWithType(nav, Drawer);
+    expect(drawers.length).toBe(1);
+    expect(drawers[0].props.temporary).toBe(true);
+    expect(drawers[0].props.mini).toBe(true);
+    expect(drawers[0].props.drawerType).toBe(NavigationDrawer.DrawerType.TEMPORARY_MINI);
+  });
+
+  it('renders the content as a css transition group with relevant props', () => {
+    const props = {
+      navItems: [],
+      contentStyle: { border: '1px solid black' },
+      contentClassName: 'some-amazing-content',
+    };
+
+    const nav = renderIntoDocument(<NavigationDrawer {...props} />);
+    const css = scryRenderedComponentsWithType(nav, CSSTransitionGroup);
+    expect(css.length).toBe(2);
+
+    const content = css[1];
+
+    expect(content.props.style).toEqual(props.contentStyle);
+    expect(content.props.className).toContain(props.contentClassName);
+    expect(content.props.transitionName).toBe(NavigationDrawer.defaultProps.contentTransitionName);
+    expect(content.props.transitionEnterTimeout).toBe(NavigationDrawer.defaultProps.contentTransitionEnterTimeout);
+    expect(content.props.transitionLeaveTimeout).toBe(NavigationDrawer.defaultProps.contentTransitionLeaveTimeout);
+  });
+
+  it('renders a drawer toolbar with the correct props', () => {
+    const props = {
+      navItems: [],
+      toolbarStyle: { border: '1px solid blue' },
+      toolbarClassName: 'some-amazing-toolbar',
+      toolbarTitle: 'My Amazing Toolbar',
+      toolbarChildren: <div>Wow, So Amaze</div>,
+    };
+
+    const nav = renderIntoDocument(<NavigationDrawer {...props} />);
+    const toolbar = findRenderedComponentWithType(nav, DrawerToolbar);
+    expect(toolbar.props.isOpen).toBe(nav.state.isOpen);
+    expect(toolbar.props.drawerType).toBe(nav.state.drawerType);
+    expect(toolbar.props.style).toEqual(props.toolbarStyle);
+    expect(toolbar.props.className).toContain(props.toolbarClassName);
+    expect(toolbar.props.temporary).toBeDefined();
+    expect(toolbar.props.persistent).toBeDefined();
+    expect(toolbar.props.title).toBe(props.toolbarTitle);
+    expect(toolbar.props.children).toBe(props.toolbarChildren);
+    expect(toolbar.props.openDrawer).toBe(nav.openDrawer);
+    expect(toolbar.props.menuIconChildren).toBe(NavigationDrawer.defaultProps.menuIconChildren);
+    expect(toolbar.props.menuIconClassName).toBe(NavigationDrawer.defaultProps.menuIconClassName);
+  });
+
+  it('renders an overlay for temporary drawers', () => {
+    const nav = renderIntoDocument(<NavigationDrawer navItems={[]} />);
+    nav.setState({ mobile: false, tablet: false, desktop: true, drawerType: NavigationDrawer.defaultProps.FULL_HEIGHT });
+
+    let overlays = scryRenderedComponentsWithType(nav, Overlay);
+    expect(overlays.length).toBe(0);
+
+    nav.setState({ drawerType: NavigationDrawer.DrawerType.TEMPORARY });
+    overlays = scryRenderedComponentsWithType(nav, Overlay);
+    expect(overlays.length).toBe(1);
+    expect(overlays[0].props.isOpen).toBe(nav.state.isOpen);
+    expect(overlays[0].props.onClick).toBe(nav.closeDrawer);
+
+    nav.setState({ drawerType: NavigationDrawer.DrawerType.TEMPORARY_MINI });
+    overlays = scryRenderedComponentsWithType(nav, Overlay);
+    expect(overlays.length).toBe(1);
+  });
+});