Sidebar redux refactoring (#1126)

* Split components into separate files

* Refactoring to use redux
diff --git a/app/addons/documents/__tests__/fetch-actions.test.js b/app/addons/documents/__tests__/fetch-actions.test.js
index ba85e5f..ec7af92 100644
--- a/app/addons/documents/__tests__/fetch-actions.test.js
+++ b/app/addons/documents/__tests__/fetch-actions.test.js
@@ -296,7 +296,7 @@
 
       beforeEach(() => {
         notificationSpy = sinon.spy(FauxtonAPI, 'addNotification');
-        sidebarSpy = sinon.stub(SidebarActions, 'updateDesignDocs');
+        sidebarSpy = sinon.stub(SidebarActions, 'dispatchUpdateDesignDocs');
       });
 
       afterEach(() => {
@@ -325,7 +325,7 @@
         expect(sidebarSpy.calledOnce).toBe(false);
       });
 
-      it('calls updateDesignDocs when one of the deleted docs is a ddoc', () => {
+      it('calls dispatchUpdateDesignDocs when one of the deleted docs is a ddoc', () => {
         const res = [
           {
             id: '_design/foo',
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js b/app/addons/documents/__tests__/results-toolbar.test.js
index 255b87a..c830b2c 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -25,7 +25,8 @@
     hasSelectedItem: false,
     toggleSelectAll: () => {},
     isLoading: false,
-    queryOptionsParams: {}
+    queryOptionsParams: {},
+    databaseName: 'mydb'
   };
 
   beforeEach(() => {
diff --git a/app/addons/documents/components/results-toolbar.js b/app/addons/documents/components/results-toolbar.js
index e9f0b13..abaa037 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -13,12 +13,10 @@
 
 import React from 'react';
 import BulkDocumentHeaderController from "../header/header";
-import Stores from "../sidebar/stores";
 import Components from "../../components/react-components";
 import Helpers from '../helpers';
 
 const {BulkActionComponent} = Components;
-const store = Stores.sidebarStore;
 
 export class ResultsToolBar extends React.Component {
   shouldComponentUpdate (nextProps) {
@@ -26,7 +24,6 @@
   }
 
   render () {
-    const database = store.getDatabase();
     const {
       hasResults,
       isListDeletable,
@@ -34,7 +31,8 @@
       allDocumentsSelected,
       hasSelectedItem,
       toggleSelectAll,
-      isLoading
+      isLoading,
+      databaseName
     } = this.props;
 
     // Determine if we need to display the bulk action selector
@@ -56,10 +54,10 @@
     }
 
     let createDocumentLink = null;
-    if (database) {
+    if (databaseName) {
       createDocumentLink = (
         <div className="document-result-screen__toolbar-flex-container">
-          <a href={Helpers.getNewDocUrl(database.id)} className="btn save document-result-screen__toolbar-create-btn btn-primary">
+          <a href={Helpers.getNewDocUrl(databaseName)} className="btn save document-result-screen__toolbar-create-btn btn-primary">
             Create Document
           </a>
         </div>
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 3c2a2ac..a59d3a9 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -102,20 +102,19 @@
   let showReduce = false;
   // If a map/reduce view is selected, check if view contains reduce field
   if (designDocs && isViewSelected(selectedNavItem)) {
-    const ddocID = '_design/' + selectedNavItem.params.designDocName;
+    const ddocID = '_design/' + selectedNavItem.designDocName;
     const ddoc = designDocs.find(ddoc => ddoc._id === ddocID);
     showReduce = ddoc !== undefined && ddoc.views
-        && ddoc.views[selectedNavItem.params.indexName] !== undefined
-        && ddoc.views[selectedNavItem.params.indexName].reduce !== undefined;
+        && ddoc.views[selectedNavItem.indexName] !== undefined
+        && ddoc.views[selectedNavItem.indexName].reduce !== undefined;
   }
   return showReduce;
 };
 
 const isViewSelected = (selectedNavItem) => {
   return (selectedNavItem.navItem === 'designDoc'
-    && selectedNavItem.params
-    && selectedNavItem.params.designDocSection === 'Views'
-    && selectedNavItem.params.indexName);
+    && selectedNavItem.designDocSection === 'Views'
+    && selectedNavItem.indexName);
 };
 
 export default {
diff --git a/app/addons/documents/index-editor/__tests__/actions.test.js b/app/addons/documents/index-editor/__tests__/actions.test.js
index e55de6d..cba84f5 100644
--- a/app/addons/documents/index-editor/__tests__/actions.test.js
+++ b/app/addons/documents/index-editor/__tests__/actions.test.js
@@ -27,6 +27,7 @@
   describe('delete view', function () {
     var designDocs, database, designDoc, designDocCollection, designDocId, viewName;
     beforeEach(function () {
+      FauxtonAPI.reduxDispatch = sinon.stub();
       database = {
         safeID: function () { return 'safeid';}
       };
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index 350ea6c..22b9a9b 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -15,7 +15,6 @@
 import Documents from "../resources";
 import ActionTypes from "./actiontypes";
 import SidebarActions from "../sidebar/actions";
-import SidebarActionTypes from "../sidebar/actiontypes";
 
 function selectReduceChanged (reduceOption) {
   FauxtonAPI.dispatch({
@@ -94,7 +93,7 @@
       var oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
       safeDeleteIndex(oldDesignDoc, viewInfo.designDocs, 'views', viewInfo.originalViewName, {
         onSuccess: function () {
-          SidebarActions.updateDesignDocs(viewInfo.designDocs);
+          SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
         }
       });
     }
@@ -102,7 +101,7 @@
     if (viewInfo.designDocId === 'new-doc') {
       addDesignDoc(designDoc);
     }
-
+    SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
     FauxtonAPI.dispatch({ type: ActionTypes.VIEW_SAVED });
     var fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
     FauxtonAPI.navigate(fragment, { trigger: true });
@@ -132,7 +131,7 @@
       FauxtonAPI.navigate(url);
     }
 
-    SidebarActions.updateDesignDocs(options.designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(options.designDocs);
 
     FauxtonAPI.addNotification({
       msg: 'The <code>' + _.escape(options.indexName) + '</code> view has been deleted.',
@@ -140,7 +139,7 @@
       escape: false,
       clear: true
     });
-    FauxtonAPI.dispatch({ type: SidebarActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
+    SidebarActions.dispatchHideDeleteIndexModal();
   }
 
   return safeDeleteIndex(options.designDoc, options.designDocs, 'views', options.indexName, { onSuccess: onSuccess });
@@ -174,7 +173,7 @@
       type: 'success',
       clear: true
     });
-    SidebarActions.updateDesignDocs(params.designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
   },
   function (xhr) {
     params.onComplete();
diff --git a/app/addons/documents/index-results/actions/fetch.js b/app/addons/documents/index-results/actions/fetch.js
index 2521a36..fc8df4e 100644
--- a/app/addons/documents/index-results/actions/fetch.js
+++ b/app/addons/documents/index-results/actions/fetch.js
@@ -197,6 +197,6 @@
   }
 
   if (designDocs && hasDesignDocs) {
-    SidebarActions.updateDesignDocs(designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(designDocs);
   }
 };
diff --git a/app/addons/documents/index-results/containers/QueryOptionsContainer.js b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
index 0052577..344e9e4 100644
--- a/app/addons/documents/index-results/containers/QueryOptionsContainer.js
+++ b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
@@ -51,7 +51,7 @@
   return {
     contentVisible: queryOptionsPanel.isVisible,
     includeDocs: queryOptionsPanel.includeDocs,
-    showReduce: showReduce(sidebar.designDocs, ownProps.selectedNavItem),
+    showReduce: showReduce(sidebar.designDocList, ownProps.selectedNavItem),
     reduce: queryOptionsPanel.reduce,
     groupLevel: queryOptionsPanel.groupLevel,
     showByKeys: queryOptionsPanel.showByKeys,
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 85b7611..8a0c05c 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -94,12 +94,13 @@
   upperContent,
   fetchUrl,
   databaseName,
-  queryDocs
+  queryDocs,
+  selectedNavItem
 }) => {
   return (
     <div className="with-sidebar tabs-with-sidebar content-area">
       <aside id="sidebar-content" className="scrollable">
-        <SidebarControllerContainer />
+        <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
       </aside>
       <section id="dashboard-content" className="flex-layout flex-col">
         <div id="dashboard-upper-content">
@@ -126,6 +127,7 @@
   hideFooter: PropTypes.bool,
   lowerContent: PropTypes.object,
   upperContent: PropTypes.object,
+  selectedNavItem: PropTypes.object
 };
 
 export const DocsTabsSidebarLayout = ({
@@ -173,12 +175,13 @@
         fetchUrl={fetchUrl}
         databaseName={dbName}
         queryDocs={queryDocs}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
 };
 
-export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, dropDownLinks }) => {
+export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, dropDownLinks, selectedNavItem }) => {
   return (
     <div id="dashboard" className="with-sidebar">
       <TabsSidebarHeader
@@ -193,12 +196,15 @@
         upperContent={<Changes.ChangesTabContent />}
         lowerContent={<Changes.ChangesController />}
         hideFooter={true}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
 };
 
-export const ViewsTabsSidebarLayout = ({ showEditView, database, docURL, endpoint, dbName, dropDownLinks }) => {
+export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, endpoint,
+  dbName, dropDownLinks, selectedNavItem }) => {
+
   const content = showEditView ? <IndexEditorComponents.EditorController /> : <DesignDocInfoComponents.DesignDocInfo />;
   return (
     <div id="dashboard" className="with-sidebar">
@@ -215,6 +221,7 @@
       <TabsSidebarContent
         lowerContent={content}
         hideFooter={true}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index ceb33a3..453003a 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -16,7 +16,7 @@
 import ChangesActions from './changes/actions';
 import Databases from '../databases/base';
 import Resources from './resources';
-import SidebarActions from './sidebar/actions';
+import {SidebarItemSelection} from './sidebar/helpers';
 import DesignDocInfoActions from './designdocinfo/actions';
 import ComponentsActions from '../components/actions';
 import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout, ChangesSidebarLayout} from './layouts';
@@ -53,8 +53,7 @@
       ddocName: ddoc,
       designDocInfo: designDocInfo
     });
-
-    SidebarActions.selectNavItem('designDoc', {
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddoc,
       designDocSection: 'metadata'
     });
@@ -67,6 +66,7 @@
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   },
 
@@ -90,10 +90,7 @@
       tab = 'design-docs';
     }
 
-    const selectedNavItem = {
-      navItem: tab
-    };
-    SidebarActions.selectNavItem(selectedNavItem.navItem);
+    const selectedNavItem = new SidebarItemSelection(tab);
     ComponentsActions.showDeleteDatabaseModal({showDeleteModal: false, dbId: ''});
 
     const endpoint = this.database.allDocs.urlRef("apiurl", {});
@@ -117,7 +114,7 @@
     ChangesActions.initChanges({
       databaseName: this.database.id
     });
-    SidebarActions.selectNavItem('changes');
+    const selectedNavItem = new SidebarItemSelection('changes');
 
     return <ChangesSidebarLayout
       endpoint={FauxtonAPI.urls('changes', 'apiurl', this.database.id, '')}
@@ -125,6 +122,7 @@
       dbName={this.database.id}
       dropDownLinks={this.getCrumbs(this.database)}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   }
 
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index ad19c8c..935ff47 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -15,7 +15,8 @@
 import BaseRoute from "./shared-routes";
 import ActionsIndexEditor from "./index-editor/actions";
 import Databases from "../databases/base";
-import SidebarActions from "./sidebar/actions";
+import SidebarActions from './sidebar/actions';
+import {SidebarItemSelection} from './sidebar/helpers';
 import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout} from './layouts';
 
 const IndexEditorAndResults = BaseRoute.extend({
@@ -59,15 +60,12 @@
       designDocId: '_design/' + ddoc
     });
 
-    const selectedNavItem = {
-      navItem: 'designDoc',
-      params: {
-        designDocName: ddoc,
-        designDocSection: 'Views',
-        indexName: viewName
-      }
-    };
-    SidebarActions.selectNavItem(selectedNavItem.navItem, selectedNavItem.params);
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
+      designDocName: ddoc,
+      designDocSection: 'Views',
+      indexName: viewName
+    });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
 
     const url = FauxtonAPI.urls('view', 'server', encodeURIComponent(databaseName),
       encodeURIComponent(ddoc), encodeURIComponent(viewName));
@@ -107,8 +105,7 @@
       newDesignDoc: newDesignDoc
     });
 
-    SidebarActions.selectNavItem('');
-
+    const selectedNavItem = new SidebarItemSelection('');
     const dropDownLinks = this.getCrumbs(this.database);
 
     return <ViewsTabsSidebarLayout
@@ -117,6 +114,7 @@
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   },
 
@@ -129,11 +127,12 @@
       designDocId: '_design/' + ddocName
     });
 
-    SidebarActions.selectNavItem('designDoc', {
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddocName,
       designDocSection: 'Views',
       indexName: viewName
     });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
 
     const docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
     const endpoint = FauxtonAPI.urls('view', 'apiurl', databaseName, ddocName, viewName);
@@ -146,6 +145,7 @@
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   }
 
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index 26120bf..c59b6f5 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -15,7 +15,6 @@
 import FauxtonAPI from "../../core/api";
 import Databases from "../databases/resources";
 import Documents from "./shared-resources";
-import SidebarActions from "./sidebar/actions";
 import {MangoLayoutContainer} from './mangolayout';
 
 const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
@@ -40,8 +39,6 @@
   },
 
   findUsingIndex: function (database) {
-    SidebarActions.selectNavItem('mango-query');
-
     const url = FauxtonAPI.urls(
       'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
     );
diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js
index ef0d2bd..0016955 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -44,7 +44,7 @@
       options.selectedNavItem = selectedNavItem;
     }
 
-    SidebarActions.newOptions(options);
+    SidebarActions.dispatchNewOptions(options);
   },
 
   getCrumbs: function (database) {
diff --git a/app/addons/documents/sidebar/SidebarControllerContainer.js b/app/addons/documents/sidebar/SidebarControllerContainer.js
index 4da0628..3a3ea26 100644
--- a/app/addons/documents/sidebar/SidebarControllerContainer.js
+++ b/app/addons/documents/sidebar/SidebarControllerContainer.js
@@ -12,25 +12,97 @@
 
 import { connect } from 'react-redux';
 import SidebarComponents from './sidebar';
-import ActionTypes from './actiontypes';
+import Action from './actions';
+import { getDatabase } from './reducers';
 
-const reduxUpdatedDesignDocList = (designDocs) => {
-  return {
-    type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
-    options: {
-      designDocs: Array.isArray(designDocs) ? designDocs : []
-    }
-  };
+
+// returns a simple array of design doc IDs
+const getAvailableDesignDocs = (state) => {
+  const availableDocs = state.designDocs.filter((doc) => {
+    return !doc.isMangoDoc();
+  });
+  return _.map(availableDocs, (doc) => {
+    return doc.id;
+  });
 };
 
-const mapStateToProps = () => {
-  return {};
+const getDeleteIndexDesignDoc = (state) => {
+  const designDoc = state.designDocs.find((ddoc) => {
+    return '_design/' + state.deleteIndexModalDesignDocName === ddoc.id;
+  });
+
+  return designDoc ? designDoc.dDocModel() : null;
+};
+
+
+const selectedNavItem = (selectedItem) => {
+
+  // resets previous selection and sets new values
+  const settings = {
+    designDocName: '',
+    designDocSection: '',
+    indexName: '',
+    navItem: '',
+    ...selectedItem
+  };
+  return settings;
+};
+
+const mapStateToProps = ({ sidebar }, ownProps) => {
+  return {
+    database: getDatabase(sidebar),
+    selectedNav: selectedNavItem(ownProps.selectedNavItem),
+    designDocs: sidebar.designDocs,
+    // designDocList: getDesignDocList(sidebar),
+    designDocList: sidebar.designDocList,
+    availableDesignDocIds: getAvailableDesignDocs(sidebar),
+    toggledSections: sidebar.toggledSections,
+    isLoading: sidebar.loading,
+
+    deleteIndexModalVisible: sidebar.deleteIndexModalVisible,
+    deleteIndexModalText: sidebar.deleteIndexModalText,
+    deleteIndexModalOnSubmit: sidebar.deleteIndexModalOnSubmit,
+    deleteIndexModalIndexName: sidebar.deleteIndexModalIndexName,
+    deleteIndexModalDesignDoc: getDeleteIndexDesignDoc(sidebar),
+
+    cloneIndexModalVisible: sidebar.cloneIndexModalVisible,
+    cloneIndexModalTitle: sidebar.cloneIndexModalTitle,
+    cloneIndexModalSelectedDesignDoc: sidebar.cloneIndexModalSelectedDesignDoc,
+    cloneIndexModalNewDesignDocName: sidebar.cloneIndexModalNewDesignDocName,
+    cloneIndexModalOnSubmit: sidebar.cloneIndexModalOnSubmit,
+    cloneIndexDesignDocProp: sidebar.cloneIndexDesignDocProp,
+    cloneIndexModalNewIndexName: sidebar.cloneIndexModalNewIndexName,
+    cloneIndexSourceIndexName: sidebar.cloneIndexModalSourceIndexName,
+    cloneIndexSourceDesignDocName: sidebar.cloneIndexModalSourceDesignDocName,
+    cloneIndexModalIndexLabel: sidebar.cloneIndexModalIndexLabel
+  };
 };
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    reduxUpdatedDesignDocList: (designDocsList) => {
-      dispatch(reduxUpdatedDesignDocList(designDocsList));
+    toggleContent: (designDoc, indexGroup) => {
+      dispatch(Action.toggleContent(designDoc, indexGroup));
+    },
+    hideCloneIndexModal: () => {
+      dispatch(Action.hideCloneIndexModal());
+    },
+    hideDeleteIndexModal: () => {
+      dispatch(Action.hideDeleteIndexModal());
+    },
+    showDeleteIndexModal: (indexName, designDocName, indexLabel, onDelete) => {
+      dispatch(Action.showDeleteIndexModal(indexName, designDocName, indexLabel, onDelete));
+    },
+    showCloneIndexModal: (indexName, designDocName, indexLabel, onSubmit) => {
+      dispatch(Action.showCloneIndexModal(indexName, designDocName, indexLabel, onSubmit));
+    },
+    selectDesignDoc: (designDoc) => {
+      dispatch(Action.selectDesignDoc(designDoc));
+    },
+    updateNewDesignDocName: (designDocName) => {
+      dispatch(Action.updateNewDesignDocName(designDocName));
+    },
+    setNewCloneIndexName: (indexName) => {
+      dispatch(Action.setNewCloneIndexName(indexName));
     }
   };
 };
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js b/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
index e9393c1..a7fbb59 100644
--- a/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
+++ b/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
@@ -20,6 +20,15 @@
 
 describe('Sidebar actions', () => {
 
+  beforeEach(() => {
+    FauxtonAPI.reduxState = sinon.stub().returns({
+      sidebar:{
+        loading: true
+      }
+    });
+    FauxtonAPI.reduxDispatch = sinon.stub();
+  });
+
   afterEach(() => {
     restore(FauxtonAPI.navigate);
     restore(FauxtonAPI.addNotification);
@@ -47,7 +56,7 @@
       }
     };
 
-    Actions.newOptions(options);
+    Actions.dispatchNewOptions(options);
     process.nextTick(() => {
       assert.ok(notificationSpy.calledOnce);
       assert.ok(/not exist/.test(notificationSpy.args[0][0].msg));
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
index 75733cb..4e306b5 100644
--- a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
+++ b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
@@ -52,7 +52,9 @@
       designDocName={'doc-$-#-.1'}
       selectedNavInfo={selectedNavInfo}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     assert.include(wrapper.find('a.icon .fonticon-plus-circled').at(1).props()['href'], '/doc-%24-%23-.1');
     assert.include(wrapper.find('a.toggle-view .accordion-header').props()['href'], '/doc-%24-%23-.1');
@@ -70,7 +72,9 @@
       designDocName={'id#1'}
       selectedNavInfo={{}}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     // NOTE: wrapper.find doesn't work special chars so we use class name instead
     wrapper.find('div.accordion-list-item').simulate('click', {preventDefault: sinon.stub()});
@@ -89,6 +93,8 @@
       toggledSections={{}}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -114,6 +120,8 @@
       toggledSections={{}}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -139,6 +147,8 @@
       designDoc={{}} // note that this is empty
       designDocName={'doc-$-#-.1'}
       toggledSections={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -159,7 +169,9 @@
       }}
       designDocName={'doc-$-#-.1'}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     assert.equal(el.find('.accordion-body li.active a').text(), 'Metadata');
   });
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js b/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js
new file mode 100644
index 0000000..dc4ab10
--- /dev/null
+++ b/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js
@@ -0,0 +1,77 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import sidebar from "../reducers";
+import ActionTypes from "../actiontypes";
+import testUtils from "../../../../../test/mocha/testUtils";
+
+const assert = testUtils.assert;
+
+function isVisible (state, designDoc, indexGroup) {
+  if (!state.toggledSections[designDoc]) {
+    return false;
+  }
+  if (indexGroup) {
+    return state.toggledSections[designDoc].indexGroups[indexGroup];
+  }
+  return state.toggledSections[designDoc].visible;
+}
+
+describe('Sidebar Reducer', () => {
+
+  describe('toggle state', () => {
+
+    it('should be visible after being toggled', () => {
+      const designDoc = 'designDoc';
+      const action = {
+        type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+        designDoc: designDoc
+      };
+      const newState = sidebar(undefined, action);
+      assert.ok(isVisible(newState, designDoc));
+    });
+
+    it('should not be visible after being toggled twice', () => {
+      const designDoc = 'designDoc2';
+      const action = {
+        type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+        designDoc: designDoc
+      };
+      let newState = sidebar(undefined, action);
+      newState = sidebar(newState, action);
+      assert.notOk(isVisible(newState, designDoc));
+    });
+
+  });
+
+  describe('toggle state for index', () => {
+    const designDoc = 'design-doc';
+    const indexGroup = 'index';
+    const action = {
+      type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+      designDoc: designDoc,
+      indexGroup: indexGroup
+    };
+
+    it('should toggle the state', () => {
+      let newState = sidebar(undefined, action);
+      assert.ok(isVisible(newState, designDoc));
+
+      newState = sidebar(newState, action);
+      assert.ok(isVisible(newState, designDoc, indexGroup));
+
+      newState = sidebar(newState, action);
+      assert.notOk(isVisible(newState, designDoc, indexGroup));
+    });
+
+  });
+});
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js b/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js
deleted file mode 100644
index 227b66c..0000000
--- a/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../../core/api";
-import Stores from "../stores";
-import testUtils from "../../../../../test/mocha/testUtils";
-const assert = testUtils.assert;
-let dispatchToken;
-let store;
-
-describe('Sidebar Store', () => {
-  beforeEach(() => {
-    store = new Stores.SidebarStore();
-    dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch.bind(store));
-  });
-
-  afterEach(() => {
-    FauxtonAPI.dispatcher.unregister(dispatchToken);
-  });
-
-  describe('toggle state', () => {
-
-    it('should not be visible if never toggled', () => {
-      assert.notOk(store.isVisible('designDoc'));
-    });
-
-    it('should be visible after being toggled', () => {
-      var designDoc = 'designDoc';
-      store.toggleContent(designDoc);
-      assert.ok(store.isVisible(designDoc));
-    });
-
-    it('should not be visible after being toggled twice', () => {
-      var designDoc = 'designDoc';
-      store.toggleContent(designDoc);
-      store.toggleContent(designDoc);
-      assert.notOk(store.isVisible(designDoc));
-    });
-
-  });
-
-  describe('toggle state for index', () => {
-    var designDoc = 'design-doc';
-
-    beforeEach(() => {
-      store.toggleContent(designDoc);
-    });
-
-    it('should be hidden if never toggled', () => {
-      assert.notOk(store.isVisible(designDoc, 'index'));
-    });
-
-    it('should be if toggled', () => {
-      store.toggleContent(designDoc, 'index');
-      assert.ok(store.isVisible(designDoc, 'index'));
-    });
-
-    it('should be hidden after being toggled twice', () => {
-      store.toggleContent(designDoc, 'index');
-      store.toggleContent(designDoc, 'index');
-      assert.notOk(store.isVisible(designDoc, 'index'));
-    });
-
-  });
-});
diff --git a/app/addons/documents/sidebar/actions.js b/app/addons/documents/sidebar/actions.js
index ab93c2c..7564b3c 100644
--- a/app/addons/documents/sidebar/actions.js
+++ b/app/addons/documents/sidebar/actions.js
@@ -12,18 +12,23 @@
 
 import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./actiontypes";
-import Stores from "./stores";
-var store = Stores.sidebarStore;
 
-function newOptions (options) {
-  if (options.database.safeID() !== store.getDatabaseName()) {
-    FauxtonAPI.dispatch({
+const _getDatabaseName = ({sidebar}) => {
+  if (!sidebar || sidebar.loading) {
+    return '';
+  }
+  return sidebar.database.safeID();
+};
+
+const dispatchNewOptions = (options) => {
+  if (options.database.safeID() !== _getDatabaseName(FauxtonAPI.reduxState())) {
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_FETCHING
     });
   }
 
   options.designDocs.fetch().then(() => {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_NEW_OPTIONS,
       options: options
     });
@@ -40,56 +45,48 @@
       clear:  true
     });
   });
-}
+};
 
-function updateDesignDocs (designDocs) {
-  FauxtonAPI.dispatch({
+const dispatchUpdateDesignDocs = (designDocs) => {
+  FauxtonAPI.reduxDispatch({
     type: ActionTypes.SIDEBAR_FETCHING
   });
 
   designDocs.fetch().then(function () {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
       options: {
         designDocs: designDocs
       }
     });
   });
-}
+};
 
-function toggleContent (designDoc, indexGroup) {
-  FauxtonAPI.dispatch({
+const dispatchHideDeleteIndexModal = () => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL
+  });
+};
+
+const dispatchExpandSelectedItem = (selectedNavItem) => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SIDEBAR_EXPAND_SELECTED_ITEM,
+    options: {
+      selectedNavItem: selectedNavItem
+    }
+  });
+};
+
+const toggleContent = (designDoc, indexGroup) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
     designDoc: designDoc,
     indexGroup: indexGroup
   });
-}
+};
 
-// This selects any item in the sidebar, including nested nav items to ensure the appropriate item is visible
-// and highlighted. Params:
-// - `navItem`: 'permissions', 'changes', 'all-docs', 'compact', 'mango-query', 'designDoc' (or anything thats been
-//    extended)
-// - `params`: optional object if you passed designDoc as the first param. This lets you specify which sub-page
-//    should be selected, e.g.
-//       Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', section: 'metadata' });
-//       Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', section: 'Views', indexName: 'my-view' });
-function selectNavItem (navItem, params) {
-  const settings = {
-    designDocName: '',
-    designDocSection: '',
-    indexName: '',
-    ...params
-  };
-  settings.navItem = navItem;
-
-  FauxtonAPI.dispatch({
-    type: ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM,
-    options: settings
-  });
-}
-
-function showDeleteIndexModal (indexName, designDocName, indexLabel, onDelete) {
-  FauxtonAPI.dispatch({
+const showDeleteIndexModal = (indexName, designDocName, indexLabel, onDelete) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL,
     options: {
       indexName: indexName,
@@ -98,14 +95,16 @@
       onDelete: onDelete
     }
   });
-}
+};
 
-function hideDeleteIndexModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
-}
+const hideDeleteIndexModal = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL
+  });
+};
 
-function showCloneIndexModal (indexName, designDocName, indexLabel, onSubmit) {
-  FauxtonAPI.dispatch({
+const showCloneIndexModal = (indexName, designDocName, indexLabel, onSubmit) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL,
     options: {
       sourceIndexName: indexName,
@@ -115,50 +114,52 @@
       cloneIndexModalTitle: 'Clone ' + indexLabel
     }
   });
-}
+};
 
-function hideCloneIndexModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL });
-}
+const hideCloneIndexModal = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL
+  });
+};
 
-function updateNewDesignDocName (designDocName) {
-  FauxtonAPI.dispatch({
+const updateNewDesignDocName = (designDocName) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED,
     options: {
       value: designDocName
     }
   });
-}
+};
 
-function selectDesignDoc (designDoc) {
-  FauxtonAPI.dispatch({
+const selectDesignDoc = (designDoc) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE,
     options: {
       value: designDoc
     }
   });
-}
+};
 
-function setNewCloneIndexName (indexName) {
-  FauxtonAPI.dispatch({
+const setNewCloneIndexName = (indexName) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME,
     options: {
       value: indexName
     }
   });
-}
-
+};
 
 export default {
-  newOptions: newOptions,
-  updateDesignDocs: updateDesignDocs,
-  toggleContent: toggleContent,
-  selectNavItem: selectNavItem,
-  showDeleteIndexModal: showDeleteIndexModal,
-  hideDeleteIndexModal: hideDeleteIndexModal,
-  showCloneIndexModal: showCloneIndexModal,
-  hideCloneIndexModal: hideCloneIndexModal,
-  updateNewDesignDocName: updateNewDesignDocName,
-  selectDesignDoc: selectDesignDoc,
-  setNewCloneIndexName: setNewCloneIndexName
+  dispatchNewOptions,
+  dispatchUpdateDesignDocs,
+  toggleContent,
+  showDeleteIndexModal,
+  hideDeleteIndexModal,
+  dispatchHideDeleteIndexModal,
+  showCloneIndexModal,
+  hideCloneIndexModal,
+  updateNewDesignDocName,
+  selectDesignDoc,
+  setNewCloneIndexName,
+  dispatchExpandSelectedItem
 };
diff --git a/app/addons/documents/sidebar/actiontypes.js b/app/addons/documents/sidebar/actiontypes.js
index f08d4bc..666b8a9 100644
--- a/app/addons/documents/sidebar/actiontypes.js
+++ b/app/addons/documents/sidebar/actiontypes.js
@@ -11,7 +11,7 @@
 // the License.
 
 export default {
-  SIDEBAR_SET_SELECTED_NAV_ITEM: 'SIDEBAR_SET_SELECTED_NAV_ITEM',
+  SIDEBAR_EXPAND_SELECTED_ITEM: 'SIDEBAR_EXPAND_SELECTED_ITEM',
   SIDEBAR_NEW_OPTIONS: 'SIDEBAR_NEW_OPTIONS',
   SIDEBAR_TOGGLE_CONTENT: 'SIDEBAR_TOGGLE_CONTENT',
   SIDEBAR_FETCHING: 'SIDEBAR_FETCHING',
diff --git a/app/addons/documents/sidebar/components/CloneIndexModal.js b/app/addons/documents/sidebar/components/CloneIndexModal.js
new file mode 100644
index 0000000..220e8ab
--- /dev/null
+++ b/app/addons/documents/sidebar/components/CloneIndexModal.js
@@ -0,0 +1,114 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import IndexEditorComponents from '../../index-editor/components';
+
+const { DesignDocSelector } = IndexEditorComponents;
+
+export default class CloneIndexModal extends React.Component {
+  static propTypes = {
+    visible: PropTypes.bool.isRequired,
+    title: PropTypes.string,
+    close: PropTypes.func.isRequired,
+    submit: PropTypes.func.isRequired,
+    designDocArray: PropTypes.array.isRequired,
+    selectedDesignDoc: PropTypes.string.isRequired,
+    newDesignDocName: PropTypes.string.isRequired,
+    newIndexName: PropTypes.string.isRequired,
+    indexLabel: PropTypes.string.isRequired,
+    selectDesignDoc: PropTypes.func.isRequired,
+    updateNewDesignDocName: PropTypes.func.isRequired,
+    setNewCloneIndexName: PropTypes.func.isRequired
+  };
+
+  static defaultProps = {
+    title: 'Clone Index',
+    visible: false
+  };
+
+  constructor(props) {
+    super(props);
+    this.props.setNewCloneIndexName('');
+  }
+
+  submit = () => {
+    if (!this.designDocSelector.validate()) {
+      return;
+    }
+    if (this.props.newIndexName === '') {
+      FauxtonAPI.addNotification({
+        msg: 'Please enter the new index name.',
+        type: 'error',
+        clear: true
+      });
+      return;
+    }
+    this.props.submit();
+  };
+
+  close = (e) => {
+    if (e) {
+      e.preventDefault();
+    }
+    this.props.close();
+  };
+
+  setNewIndexName = (e) => {
+    this.props.setNewCloneIndexName(e.target.value);
+  };
+
+  render() {
+    return (
+      <Modal dialogClassName="clone-index-modal" show={this.props.visible} onHide={this.close}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>{this.props.title}</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+
+          <form className="form" method="post" onSubmit={this.submit}>
+            <p>
+              Select the design document where the cloned {this.props.indexLabel} will be created, and then enter
+              a name for the cloned {this.props.indexLabel}.
+            </p>
+
+            <div className="row">
+              <DesignDocSelector
+                ref={node => this.designDocSelector = node}
+                designDocList={this.props.designDocArray}
+                selectedDesignDocName={this.props.selectedDesignDoc}
+                newDesignDocName={this.props.newDesignDocName}
+                onSelectDesignDoc={this.props.selectDesignDoc}
+                onChangeNewDesignDocName={this.props.updateNewDesignDocName} />
+            </div>
+
+            <div className="clone-index-name-row">
+              <label className="new-index-title-label" htmlFor="new-index-name">{this.props.indexLabel} Name</label>
+              <input type="text" id="new-index-name" value={this.props.newIndexName} onChange={this.setNewIndexName}
+                placeholder="New view name" />
+            </div>
+          </form>
+
+        </Modal.Body>
+        <Modal.Footer>
+          <a href="#" className="cancel-link" onClick={this.close} data-bypass="true">Cancel</a>
+          <button onClick={this.submit} data-bypass="true" className="btn btn-primary save">
+            <i className="icon fonticon-ok-circled" /> Clone {this.props.indexLabel}</button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/DesignDoc.js b/app/addons/documents/sidebar/components/DesignDoc.js
new file mode 100644
index 0000000..c0cc2e5
--- /dev/null
+++ b/app/addons/documents/sidebar/components/DesignDoc.js
@@ -0,0 +1,158 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Collapse } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import Components from '../../../components/react-components';
+import IndexEditorActions from '../../index-editor/actions';
+import IndexSection from './IndexSection';
+
+const { MenuDropDown } = Components;
+
+export default class DesignDoc extends React.Component {
+  static propTypes = {
+    database: PropTypes.object.isRequired,
+    sidebarListTypes: PropTypes.array.isRequired,
+    isExpanded: PropTypes.bool.isRequired,
+    selectedNavInfo: PropTypes.object.isRequired,
+    toggledSections: PropTypes.object.isRequired,
+    designDocName:  PropTypes.string.isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      updatedSidebarListTypes: this.props.sidebarListTypes
+    };
+    if (_.isEmpty(this.state.updatedSidebarListTypes) ||
+      (_.has(this.state.updatedSidebarListTypes[0], 'selector') && this.state.updatedSidebarListTypes[0].selector !== 'views')) {
+
+      const newList = this.state.updatedSidebarListTypes;
+      newList.unshift({
+        selector: 'views',
+        name: 'Views',
+        urlNamespace: 'view',
+        indexLabel: 'view',
+        onDelete: IndexEditorActions.deleteView,
+        onClone: IndexEditorActions.cloneView,
+        onEdit: IndexEditorActions.gotoEditViewPage
+      });
+      this.state = { updatedSidebarListTypes: newList };
+    }
+  }
+
+  indexList = () => {
+    return _.map(this.state.updatedSidebarListTypes, (index, key) => {
+      const expanded = _.has(this.props.toggledSections, index.name) && this.props.toggledSections[index.name];
+
+      // if an index in this list is selected, pass that down
+      let selectedIndex = '';
+      if (this.props.selectedNavInfo.designDocSection === index.name) {
+        selectedIndex = this.props.selectedNavInfo.indexName;
+      }
+
+      return (
+        <IndexSection
+          icon={index.icon}
+          isExpanded={expanded}
+          urlNamespace={index.urlNamespace}
+          indexLabel={index.indexLabel}
+          onEdit={index.onEdit}
+          onDelete={index.onDelete}
+          onClone={index.onClone}
+          selectedIndex={selectedIndex}
+          toggle={this.props.toggle}
+          database={this.props.database}
+          designDocName={this.props.designDocName}
+          key={key}
+          title={index.name}
+          selector={index.selector}
+          items={_.keys(this.props.designDoc[index.selector])}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+      );
+    });
+  };
+
+  toggle = (e) => {
+    e.preventDefault();
+    this.props.toggle(this.props.designDocName);
+  };
+
+  getNewButtonLinks = () => {
+    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', encodeURIComponent(this.props.database.id));
+    const designDocName = this.props.designDocName;
+
+    const addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
+      menuLinks.push({
+        title: link.title,
+        url: '#' + newUrlPrefix + '/' + link.url + '/' + encodeURIComponent(designDocName),
+        icon: 'fonticon-plus-circled'
+      });
+      return menuLinks;
+    }, [{
+      title: 'New View',
+      url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
+      icon: 'fonticon-plus-circled'
+    }]);
+
+    return [{
+      title: 'Add New',
+      links: addNewLinks
+    }];
+  };
+
+  render () {
+    const buttonLinks = this.getNewButtonLinks();
+    let toggleClassNames = 'design-doc-section accordion-header';
+    let toggleBodyClassNames = 'design-doc-body accordion-body collapse';
+
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
+    }
+    const designDocName = this.props.designDocName;
+    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', encodeURIComponent(this.props.database.id), designDocName);
+    const metadataRowClass = (this.props.selectedNavInfo.designDocSection === 'metadata') ? 'active' : '';
+
+    return (
+      <li className="nav-header">
+        <div id={"sidebar-tab-" + designDocName} className={toggleClassNames}>
+          <div id={"nav-header-" + designDocName} onClick={this.toggle} className='accordion-list-item'>
+            <div className="fonticon-play"></div>
+            <p className='design-doc-name'>
+              <span title={'_design/' + designDocName}>{designDocName}</span>
+            </p>
+          </div>
+          <div className='new-button add-dropdown'>
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </div>
+        <Collapse in={this.props.isExpanded}>
+          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
+            <li className={metadataRowClass}>
+              <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
+                Metadata
+              </a>
+            </li>
+            {this.indexList()}
+          </ul>
+        </Collapse>
+      </li>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/DesignDocList.js b/app/addons/documents/sidebar/components/DesignDocList.js
new file mode 100644
index 0000000..79deace
--- /dev/null
+++ b/app/addons/documents/sidebar/components/DesignDocList.js
@@ -0,0 +1,82 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import DesignDoc from './DesignDoc';
+
+export default class DesignDocList extends React.Component {
+  static propTypes = {
+    database: PropTypes.object.isRequired,
+    toggle: PropTypes.func.isRequired,
+    designDocs: PropTypes.array,
+    toggledSections: PropTypes.object,
+    selectedNav: PropTypes.shape({
+      designDocName: PropTypes.string,
+      designDocSection: PropTypes.string,
+      indexName: PropTypes.string,
+      navItem: PropTypes.string
+    }).isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    const list = FauxtonAPI.getExtensions('sidebar:list');
+    this.sidebarListTypes = _.isUndefined(list) ? [] : list;
+  }
+
+  designDocList = () => {
+    return _.map(this.props.designDocs, (designDoc, key) => {
+      const ddName = decodeURIComponent(designDoc.safeId);
+
+      // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
+      let expanded = false,
+          toggledSections = {};
+      if (_.has(this.props.toggledSections, ddName)) {
+        expanded = this.props.toggledSections[ddName].visible;
+        toggledSections = this.props.toggledSections[ddName].indexGroups;
+      }
+
+      let selectedNavInfo = {};
+      if (this.props.selectedNav.navItem === 'designDoc' && this.props.selectedNav.designDocName === ddName) {
+        selectedNavInfo = this.props.selectedNav;
+      }
+
+      return (
+        <DesignDoc
+          toggle={this.props.toggle}
+          sidebarListTypes={this.sidebarListTypes}
+          isExpanded={expanded}
+          toggledSections={toggledSections}
+          selectedNavInfo={selectedNavInfo}
+          key={key}
+          designDoc={designDoc}
+          designDocName={ddName}
+          database={this.props.database}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+      );
+    });
+  };
+
+  render() {
+    return (
+      <ul className="nav nav-list">
+        {this.designDocList()}
+      </ul>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/IndexSection.js b/app/addons/documents/sidebar/components/IndexSection.js
new file mode 100644
index 0000000..cbee3a8
--- /dev/null
+++ b/app/addons/documents/sidebar/components/IndexSection.js
@@ -0,0 +1,151 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Collapse, OverlayTrigger, Popover } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+
+export default class IndexSection extends React.Component {
+  static propTypes = {
+    urlNamespace: PropTypes.string.isRequired,
+    indexLabel: PropTypes.string.isRequired,
+    database: PropTypes.object.isRequired,
+    designDocName: PropTypes.string.isRequired,
+    items: PropTypes.array.isRequired,
+    isExpanded: PropTypes.bool.isRequired,
+    selectedIndex: PropTypes.string.isRequired,
+    onDelete: PropTypes.func.isRequired,
+    onClone: PropTypes.func.isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  state = {
+    placement: 'bottom'
+  };
+
+  // this dynamically changes the placement of the menu (top/bottom) to prevent it going offscreen and causing some
+  // unsightly shifting
+  setPlacement = (rowId) => {
+    const rowTop = document.getElementById(rowId).getBoundingClientRect().top;
+    const toggleHeight = 150; // the height of the menu overlay, arrow, view row
+    const placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 'bottom';
+    this.setState({ placement: placement });
+  };
+
+  createItems = () => {
+
+    // sort the indexes alphabetically
+    const sortedItems = this.props.items.sort();
+
+    return _.map(sortedItems, (indexName, index) => {
+      const href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), encodeURIComponent(this.props.designDocName));
+      const className = (this.props.selectedIndex === indexName) ? 'active' : '';
+
+      return (
+        <li className={className} key={index}>
+          <a
+            id={this.props.designDocName + '_' + indexName}
+            href={"#/" + href + encodeURIComponent(indexName)}
+            className="toggle-view">
+            {indexName}
+          </a>
+          <OverlayTrigger
+            trigger="click"
+            onEnter={this.setPlacement.bind(this, this.props.designDocName + '_' + indexName)}
+            placement={this.state.placement}
+            rootClose={true}
+            ref={overlay => this.itemOverlay = overlay}
+            overlay={
+              <Popover id="index-menu-component-popover">
+                <ul>
+                  <li onClick={this.indexAction.bind(this, 'edit', { indexName: indexName, onEdit: this.props.onEdit })}>
+                    <span className="fonticon fonticon-file-code-o"></span>
+                    Edit
+                  </li>
+                  <li onClick={this.indexAction.bind(this, 'clone', { indexName: indexName, onClone: this.props.onClone })}>
+                    <span className="fonticon fonticon-files-o"></span>
+                    Clone
+                  </li>
+                  <li onClick={this.indexAction.bind(this, 'delete', { indexName: indexName, onDelete: this.props.onDelete })}>
+                    <span className="fonticon fonticon-trash"></span>
+                    Delete
+                  </li>
+                </ul>
+              </Popover>
+            }>
+            <span className="index-menu-toggle fonticon fonticon-wrench2"></span>
+          </OverlayTrigger>
+        </li>
+      );
+    });
+  };
+
+  indexAction = (action, params, e) => {
+    e.preventDefault();
+
+    this.itemOverlay.hide();
+
+    switch (action) {
+      case 'delete':
+        this.props.showDeleteIndexModal(params.indexName, this.props.designDocName, this.props.indexLabel, params.onDelete);
+        break;
+      case 'clone':
+        this.props.showCloneIndexModal(params.indexName, this.props.designDocName, this.props.indexLabel, params.onClone);
+        break;
+      case 'edit':
+        params.onEdit(this.props.database.id, this.props.designDocName, params.indexName);
+        break;
+    }
+  };
+
+  toggle = (e) => {
+    e.preventDefault();
+    this.props.toggle(this.props.designDocName, this.props.title);
+  };
+
+  render() {
+
+    // if this section has no content, omit it to prevent clutter. Otherwise it would show a toggle option that
+    // would hide/show nothing
+    if (this.props.items.length === 0) {
+      return null;
+    }
+
+    let toggleClassNames = 'accordion-header index-group-header';
+    let toggleBodyClassNames = 'index-list accordion-body collapse';
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
+    }
+
+    const title = this.props.title;
+    const designDocName = this.props.designDocName;
+    const linkId = "nav-design-function-" + designDocName + this.props.selector;
+
+    return (
+      <li id={linkId}>
+        <a className={toggleClassNames} data-toggle="collapse" onClick={this.toggle}>
+          <div className="fonticon-play"></div>
+          {title}
+        </a>
+        <Collapse in={this.props.isExpanded}>
+          <ul className={toggleBodyClassNames}>
+            {this.createItems()}
+          </ul>
+        </Collapse>
+      </li>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/MainSidebar.js b/app/addons/documents/sidebar/components/MainSidebar.js
new file mode 100644
index 0000000..14c40c0
--- /dev/null
+++ b/app/addons/documents/sidebar/components/MainSidebar.js
@@ -0,0 +1,98 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import app from "../../../../app";
+import FauxtonAPI from '../../../../core/api';
+import DocumentHelper from "../../../documents/helpers";
+import Components from '../../../components/react-components';
+
+const { MenuDropDown } = Components;
+
+export default class MainSidebar extends React.Component {
+  static propTypes = {
+    selectedNavItem: PropTypes.string.isRequired
+  };
+
+  getNewButtonLinks = () => {  // these are links for the sidebar '+' on All Docs and All Design Docs
+    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
+  };
+
+  buildDocLinks = () => {
+    const base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
+    return FauxtonAPI.getExtensions('docLinks').map((link) => {
+      return (
+        <li key={link.url} className={this.getNavItemClass(link.url)}>
+          <a id={link.url} href={base + link.url}>{link.title}</a>
+        </li>
+      );
+    });
+  };
+
+  getNavItemClass = (navItem) => {
+    return (navItem === this.props.selectedNavItem) ? 'active' : '';
+  };
+
+  render() {
+    const docLinks = this.buildDocLinks();
+    const dbEncoded = FauxtonAPI.url.encode(this.props.databaseName);
+    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, '');
+    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', dbEncoded);
+    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, '');
+    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded);
+    const runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
+    const buttonLinks = this.getNewButtonLinks();
+
+    return (
+      <ul className="nav nav-list">
+        <li className={this.getNavItemClass('all-docs')}>
+          <a id="all-docs"
+            href={"#/" + databaseUrl}
+            className="toggle-view">
+            All Documents
+          </a>
+          <div id="new-all-docs-button" className="add-dropdown">
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </li>
+        <li className={this.getNavItemClass('mango-query')}>
+          <a
+            id="mango-query"
+            href={'#' + mangoQueryUrl}
+            className="toggle-view">
+            {runQueryWithMangoText}
+          </a>
+        </li>
+        <li className={this.getNavItemClass('permissions')}>
+          <a id="permissions" href={permissionsUrl}>Permissions</a>
+        </li>
+        <li className={this.getNavItemClass('changes')}>
+          <a id="changes" href={changesUrl}>Changes</a>
+        </li>
+        {docLinks}
+        <li className={this.getNavItemClass('design-docs')}>
+          <a
+            id="design-docs"
+            href={"#/" + databaseUrl + '?startkey="_design"&endkey="_design0"'}
+            className="toggle-view">
+            Design Documents
+          </a>
+          <div id="new-design-docs-button" className="add-dropdown">
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </li>
+      </ul>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/SidebarController.js b/app/addons/documents/sidebar/components/SidebarController.js
new file mode 100644
index 0000000..d9a5484
--- /dev/null
+++ b/app/addons/documents/sidebar/components/SidebarController.js
@@ -0,0 +1,146 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import ComponentsActions from "../../../components/actions";
+import Components from '../../../components/react-components';
+import ComponentsStore from '../../../components/stores';
+import GeneralComponents from '../../../fauxton/components';
+import CloneIndexModal from './CloneIndexModal';
+import DesignDocList from './DesignDocList';
+import MainSidebar from './MainSidebar';
+
+const { DeleteDatabaseModal, LoadLines } = Components;
+const { ConfirmationModal } = GeneralComponents;
+const { deleteDbModalStore } = ComponentsStore;
+
+export default class SidebarController extends React.Component {
+
+  static propTypes = {
+    selectedNav: PropTypes.shape({
+      designDocName: PropTypes.string,
+      designDocSection: PropTypes.string,
+      indexName: PropTypes.string,
+      navItem: PropTypes.string
+    }).isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.state = this.getDeleteDbStoreState();
+    this.deleteIndex = this.deleteIndex.bind(this);
+    this.cloneIndex = this.cloneIndex.bind(this);
+  }
+
+  componentDidMount() {
+    deleteDbModalStore.on('change', this.onChange, this);
+  }
+
+  componentWillUnmount() {
+    deleteDbModalStore.off('change', this.onChange, this);
+  }
+
+  getDeleteDbStoreState() {
+    return {
+      deleteDbModalProperties: deleteDbModalStore.getShowDeleteDatabaseModal()
+    };
+  }
+
+  onChange = () => {
+    const newState = this.getDeleteDbStoreState();
+    this.setState(newState);
+  };
+
+  showDeleteDatabaseModal = (payload) => {
+    ComponentsActions.showDeleteDatabaseModal(payload);
+  };
+
+  // handles deleting of any index regardless of type. The delete handler and all relevant info is set when the user
+  // clicks the delete action for a particular index
+  deleteIndex = () => {
+
+    // if the user is currently on the index that's being deleted, pass that info along to the delete handler. That can
+    // be used to redirect the user to somewhere appropriate
+    const isOnIndex = this.props.selectedNav.navItem === 'designDoc' &&
+      ('_design/' + this.props.selectedNav.designDocName) === this.props.deleteIndexModalDesignDoc.id &&
+      this.props.selectedNav.indexName === this.props.deleteIndexModalIndexName;
+
+    this.props.deleteIndexModalOnSubmit({
+      isOnIndex: isOnIndex,
+      indexName: this.props.deleteIndexModalIndexName,
+      designDoc: this.props.deleteIndexModalDesignDoc,
+      designDocs: this.props.designDocs,
+      database: this.props.database
+    });
+  };
+
+  cloneIndex = () => {
+    this.props.cloneIndexModalOnSubmit({
+      sourceIndexName: this.props.cloneIndexSourceIndexName,
+      sourceDesignDocName: this.props.cloneIndexSourceDesignDocName,
+      targetDesignDocName: this.props.cloneIndexModalSelectedDesignDoc,
+      newDesignDocName: this.props.cloneIndexModalNewDesignDocName,
+      newIndexName: this.props.cloneIndexModalNewIndexName,
+      designDocs: this.props.designDocs,
+      database: this.props.database,
+      onComplete: this.props.hideCloneIndexModal
+    });
+  };
+
+  render() {
+    if (this.props.isLoading) {
+      return <LoadLines />;
+    }
+
+    return (
+      <nav className="sidenav">
+        <MainSidebar
+          selectedNavItem={this.props.selectedNav.navItem}
+          databaseName={this.props.database.id} />
+        <DesignDocList
+          selectedNav={this.props.selectedNav}
+          toggle={this.props.toggleContent}
+          toggledSections={this.props.toggledSections}
+          designDocs={this.props.designDocList}
+          database={this.props.database}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+        <DeleteDatabaseModal
+          showHide={this.showDeleteDatabaseModal}
+          modalProps={this.state.deleteDbModalProperties} />
+
+        {/* the delete and clone index modals handle all index types, hence the props all being pulled from the store */}
+        <ConfirmationModal
+          title="Confirm Deletion"
+          visible={this.props.deleteIndexModalVisible}
+          text={this.props.deleteIndexModalText}
+          onClose={this.props.hideDeleteIndexModal}
+          onSubmit={this.deleteIndex} />
+        <CloneIndexModal
+          visible={this.props.cloneIndexModalVisible}
+          title={this.props.cloneIndexModalTitle}
+          close={this.props.hideCloneIndexModal}
+          submit={this.cloneIndex}
+          designDocArray={this.props.availableDesignDocIds}
+          selectedDesignDoc={this.props.cloneIndexModalSelectedDesignDoc}
+          newDesignDocName={this.props.cloneIndexModalNewDesignDocName}
+          newIndexName={this.props.cloneIndexModalNewIndexName}
+          indexLabel={this.props.cloneIndexModalIndexLabel}
+          selectDesignDoc={this.props.selectDesignDoc}
+          updateNewDesignDocName={this.props.updateNewDesignDocName}
+          setNewCloneIndexName={this.props.setNewCloneIndexName} />
+      </nav>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/helpers.js b/app/addons/documents/sidebar/helpers.js
new file mode 100644
index 0000000..5950208
--- /dev/null
+++ b/app/addons/documents/sidebar/helpers.js
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+/**
+ * Represents the selected item in the sidebar, including nested nav items to ensure the appropriate item
+ * is visible and highlighted.
+ */
+export class SidebarItemSelection {
+
+  /**
+   * Creates a new sidebar selection.
+   *
+   * @param {string} navItem 'permissions', 'changes', 'all-docs', 'compact', 'mango-query', 'designDoc'
+   *   (or anything thats beenextended)
+   * @param {string} [params] (optional) If you passed 'designDoc' as the first param. This lets you
+   *   specify which sub-item should be selected, e.g.:
+   *      Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', section: 'metadata' });
+   *      Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', section: 'Views', indexName: 'my-view' });
+   */
+  constructor(navItem, params) {
+    this.navItem = navItem;
+    if (params) {
+      const {designDocName, designDocSection, indexName} = params;
+      this.designDocName = designDocName ? designDocName : '';
+      this.designDocSection = designDocSection ? designDocSection : '';
+      this.indexName = indexName ? indexName : '';
+    }
+  }
+}
+
diff --git a/app/addons/documents/sidebar/reducers.js b/app/addons/documents/sidebar/reducers.js
index 6359823..f1fa140 100644
--- a/app/addons/documents/sidebar/reducers.js
+++ b/app/addons/documents/sidebar/reducers.js
@@ -10,19 +10,223 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import ActionTypes from './actiontypes';
+import React from "react";
+import app from "../../../app";
+import ActionTypes from "./actiontypes";
 
 const initialState = {
-  designDocs: []
+  designDocs: new Backbone.Collection(),
+  designDocList: [],
+  selected: {
+    navItem: 'all-docs',
+    designDocName: '',
+    designDocSection: '', // 'metadata' / name of index group ("Views", etc.)
+    indexName: ''
+  },
+  loading: true,
+  toggledSections: {},
+
+  deleteIndexModalVisible: false,
+  deleteIndexModalDesignDocName: '',
+  deleteIndexModalText: '',
+  deleteIndexModalIndexName: '',
+  deleteIndexModalOnSubmit: () => {},
+
+  cloneIndexModalVisible: false,
+  cloneIndexDesignDocProp: '',
+  cloneIndexModalTitle: '',
+  cloneIndexModalSelectedDesignDoc: '',
+  cloneIndexModalNewDesignDocName: '',
+  cloneIndexModalNewIndexName: '',
+  cloneIndexModalSourceIndexName: '',
+  cloneIndexModalSourceDesignDocName: '',
+  cloneIndexModalIndexLabel: '',
+  cloneIndexModalOnSubmit: () => {}
 };
 
-export default function resultsState(state = initialState, action) {
+function setNewOptions(state, options) {
+  const newState = {
+    ...state,
+    database: options.database,
+    designDocs: options.designDocs,
+    designDocList: getDesignDocList(options.designDocs),
+    loading: false,
+  };
+  // this can be expanded in future as we need. Right now it can only set a top-level nav item ('all docs',
+  // 'permissions' etc.) and not a nested page
+  if (options.selectedNavItem) {
+    newState.selected = {
+      navItem: options.selectedNavItem,
+      designDocName: '',
+      designDocSection: '',
+      indexName: ''
+    };
+  }
+
+  return newState;
+}
+
+function toggleContent(state, designDoc, indexGroup) {
+  // used to toggle both design docs, and any index groups within them
+  const newState = {
+    ...state
+  };
+
+  if (!state.toggledSections[designDoc]) {
+    newState.toggledSections[designDoc] = {
+      visible: true,
+      indexGroups: {}
+    };
+    return newState;
+  }
+
+  if (indexGroup) {
+    const expanded = state.toggledSections[designDoc].indexGroups[indexGroup];
+
+    if (_.isUndefined(expanded)) {
+      newState.toggledSections[designDoc].indexGroups[indexGroup] = true;
+    } else {
+      newState.toggledSections[designDoc].indexGroups[indexGroup] = !expanded;
+    }
+    return newState;
+  }
+
+  newState.toggledSections[designDoc].visible = !state.toggledSections[designDoc].visible;
+
+  return newState;
+}
+
+function expandSelectedItem(state, {selectedNavItem}) {
+  const newState = {
+    ...state
+  };
+
+  if (selectedNavItem.designDocName) {
+    if (!_.has(state.toggledSections, selectedNavItem.designDocName)) {
+      newState.toggledSections[selectedNavItem.designDocName] = {
+        visible: true,
+        indexGroups: {}
+      };
+    }
+    newState.toggledSections[selectedNavItem.designDocName].visible = true;
+
+    if (selectedNavItem.designDocSection) {
+      newState.toggledSections[selectedNavItem.designDocName].indexGroups[selectedNavItem.designDocSection] = true;
+    }
+  }
+  return newState;
+}
+
+function getDesignDocList (designDocs) {
+  if (!designDocs) {
+    return [];
+  }
+  let docs = designDocs.toJSON();
+  docs = _.filter(docs, (doc) => {
+    if (_.has(doc.doc, 'language')) {
+      return doc.doc.language !== 'query';
+    }
+    return true;
+  });
+
+  const ddocsList = docs.map((doc) => {
+    doc.safeId = app.utils.safeURLName(doc._id.replace(/^_design\//, ''));
+    return _.extend(doc, doc.doc);
+  });
+  return ddocsList;
+}
+
+export const getDatabase = (state) => {
+  if (state.loading) {
+    return {};
+  }
+  return state.database;
+};
+
+export default function sidebar(state = initialState, action) {
+  const { options } = action;
   switch (action.type) {
 
+    case ActionTypes.SIDEBAR_EXPAND_SELECTED_ITEM:
+      return expandSelectedItem(state, options);
+
+    case ActionTypes.SIDEBAR_NEW_OPTIONS:
+      return setNewOptions(state, options);
+
+    case ActionTypes.SIDEBAR_TOGGLE_CONTENT:
+      return toggleContent(state, action.designDoc, action.indexGroup);
+
+    case ActionTypes.SIDEBAR_FETCHING:
+      return {
+        ...state,
+        loading: true
+      };
+
+    case ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL:
+      return {
+        ...state,
+        deleteIndexModalIndexName: options.indexName,
+        deleteIndexModalDesignDocName: options.designDocName,
+        deleteIndexModalVisible: true,
+        deleteIndexModalText: (
+          <div>
+            Are you sure you want to delete the <code>{options.indexName}</code> {options.indexLabel}?
+          </div>
+        ),
+        deleteIndexModalOnSubmit: options.onDelete
+      };
+
+
+    case ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL:
+      return {
+        ...state,
+        deleteIndexModalVisible: false
+      };
+
+    case ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL:
+      return {
+        ...state,
+        cloneIndexModalIndexLabel: options.indexLabel,
+        cloneIndexModalTitle: options.cloneIndexModalTitle,
+        cloneIndexModalSourceIndexName: options.sourceIndexName,
+        cloneIndexModalSourceDesignDocName: options.sourceDesignDocName,
+        cloneIndexModalSelectedDesignDoc: '_design/' + options.sourceDesignDocName,
+        cloneIndexDesignDocProp: '',
+        cloneIndexModalVisible: true,
+        cloneIndexModalOnSubmit: options.onSubmit
+      };
+
+    case ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL:
+      return {
+        ...state,
+        cloneIndexModalVisible: false
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE:
+      return {
+        ...state,
+        cloneIndexModalSelectedDesignDoc: options.value
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED:
+      return {
+        ...state,
+        cloneIndexModalNewDesignDocName: options.value
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME:
+      return {
+        ...state,
+        cloneIndexModalNewIndexName: options.value
+      };
+
     case ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS:
-      return Object.assign({}, state, {
-        designDocs: action.options.designDocs
-      });
+      return {
+        ...state,
+        designDocs: options.designDocs,
+        designDocList: getDesignDocList(options.designDocs),
+        loading: false
+      };
 
     default:
       return state;
diff --git a/app/addons/documents/sidebar/sidebar.js b/app/addons/documents/sidebar/sidebar.js
index 60624fd..a20420b 100644
--- a/app/addons/documents/sidebar/sidebar.js
+++ b/app/addons/documents/sidebar/sidebar.js
@@ -10,639 +10,12 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import PropTypes from 'prop-types';
-import React from "react";
-import ReactDOM from "react-dom";
-import app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import Stores from "./stores";
-import Actions from "./actions";
-import Components from "../../components/react-components";
-import ComponentsStore from "../../components/stores";
-import ComponentsActions from "../../components/actions";
-import IndexEditorActions from "../index-editor/actions";
-import IndexEditorComponents from "../index-editor/components";
-import GeneralComponents from "../../fauxton/components";
-import DocumentHelper from "../../documents/helpers";
-import { Collapse, OverlayTrigger, Popover, Modal } from "react-bootstrap";
-import "../../../../assets/js/plugins/prettify";
-
-const store = Stores.sidebarStore;
-const { DeleteDatabaseModal, LoadLines, MenuDropDown } = Components;
-const { DesignDocSelector } = IndexEditorComponents;
-const { ConfirmationModal } = GeneralComponents;
-const { deleteDbModalStore } = ComponentsStore;
-
-class MainSidebar extends React.Component {
-  static propTypes = {
-    selectedNavItem: PropTypes.string.isRequired
-  };
-
-  getNewButtonLinks = () => {  // these are links for the sidebar '+' on All Docs and All Design Docs
-    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
-  };
-
-  buildDocLinks = () => {
-    const base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
-    return FauxtonAPI.getExtensions('docLinks').map((link) => {
-      return (
-        <li key={link.url} className={this.getNavItemClass(link.url)}>
-          <a id={link.url} href={base + link.url}>{link.title}</a>
-        </li>
-      );
-    });
-  };
-
-  getNavItemClass = (navItem) => {
-    return (navItem === this.props.selectedNavItem) ? 'active' : '';
-  };
-
-  render() {
-    const docLinks = this.buildDocLinks();
-    const dbEncoded = FauxtonAPI.url.encode(this.props.databaseName);
-    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, '');
-    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', dbEncoded);
-    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, '');
-    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded);
-    const runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
-    const buttonLinks = this.getNewButtonLinks();
-
-    return (
-      <ul className="nav nav-list">
-        <li className={this.getNavItemClass('all-docs')}>
-          <a id="all-docs"
-            href={"#/" + databaseUrl}
-            className="toggle-view">
-            All Documents
-          </a>
-          <div id="new-all-docs-button" className="add-dropdown">
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </li>
-        <li className={this.getNavItemClass('mango-query')}>
-          <a
-            id="mango-query"
-            href={'#' + mangoQueryUrl}
-            className="toggle-view">
-            {runQueryWithMangoText}
-          </a>
-        </li>
-        <li className={this.getNavItemClass('permissions')}>
-          <a id="permissions" href={permissionsUrl}>Permissions</a>
-        </li>
-        <li className={this.getNavItemClass('changes')}>
-          <a id="changes" href={changesUrl}>Changes</a>
-        </li>
-        {docLinks}
-        <li className={this.getNavItemClass('design-docs')}>
-          <a
-            id="design-docs"
-            href={"#/" + databaseUrl + '?startkey="_design"&endkey="_design0"'}
-            className="toggle-view">
-            Design Documents
-          </a>
-          <div id="new-design-docs-button" className="add-dropdown">
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </li>
-      </ul>
-    );
-  }
-}
-
-class IndexSection extends React.Component {
-  static propTypes = {
-    urlNamespace: PropTypes.string.isRequired,
-    indexLabel: PropTypes.string.isRequired,
-    database: PropTypes.object.isRequired,
-    designDocName: PropTypes.string.isRequired,
-    items: PropTypes.array.isRequired,
-    isExpanded: PropTypes.bool.isRequired,
-    selectedIndex: PropTypes.string.isRequired,
-    onDelete: PropTypes.func.isRequired,
-    onClone: PropTypes.func.isRequired
-  };
-
-  state = {
-    placement: 'bottom'
-  };
-
-  // this dynamically changes the placement of the menu (top/bottom) to prevent it going offscreen and causing some
-  // unsightly shifting
-  setPlacement = (rowId) => {
-    const rowTop = document.getElementById(rowId).getBoundingClientRect().top;
-    const toggleHeight = 150; // the height of the menu overlay, arrow, view row
-    const placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 'bottom';
-    this.setState({ placement: placement });
-  };
-
-  createItems = () => {
-
-    // sort the indexes alphabetically
-    const sortedItems = this.props.items.sort();
-
-    return _.map(sortedItems, (indexName, index) => {
-      const href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), encodeURIComponent(this.props.designDocName));
-      const className = (this.props.selectedIndex === indexName) ? 'active' : '';
-
-      return (
-        <li className={className} key={index}>
-          <a
-            id={this.props.designDocName + '_' + indexName}
-            href={"#/" + href + encodeURIComponent(indexName)}
-            className="toggle-view">
-            {indexName}
-          </a>
-          <OverlayTrigger
-            trigger="click"
-            onEnter={this.setPlacement.bind(this, this.props.designDocName + '_' + indexName)}
-            placement={this.state.placement}
-            rootClose={true}
-            ref={overlay => this.itemOverlay = overlay}
-            overlay={
-              <Popover id="index-menu-component-popover">
-                <ul>
-                  <li onClick={this.indexAction.bind(this, 'edit', { indexName: indexName, onEdit: this.props.onEdit })}>
-                    <span className="fonticon fonticon-file-code-o"></span>
-                    Edit
-                  </li>
-                  <li onClick={this.indexAction.bind(this, 'clone', { indexName: indexName, onClone: this.props.onClone })}>
-                    <span className="fonticon fonticon-files-o"></span>
-                    Clone
-                  </li>
-                  <li onClick={this.indexAction.bind(this, 'delete', { indexName: indexName, onDelete: this.props.onDelete })}>
-                    <span className="fonticon fonticon-trash"></span>
-                    Delete
-                  </li>
-                </ul>
-              </Popover>
-            }>
-            <span className="index-menu-toggle fonticon fonticon-wrench2"></span>
-          </OverlayTrigger>
-        </li>
-      );
-    });
-  };
-
-  indexAction = (action, params, e) => {
-    e.preventDefault();
-
-    this.itemOverlay.hide();
-
-    switch (action) {
-      case 'delete':
-        Actions.showDeleteIndexModal(params.indexName, this.props.designDocName, this.props.indexLabel, params.onDelete);
-        break;
-      case 'clone':
-        Actions.showCloneIndexModal(params.indexName, this.props.designDocName, this.props.indexLabel, params.onClone);
-        break;
-      case 'edit':
-        params.onEdit(this.props.database.id, this.props.designDocName, params.indexName);
-        break;
-    }
-  };
-
-  toggle = (e) => {
-    e.preventDefault();
-    this.props.toggle(this.props.designDocName, this.props.title);
-  };
-
-  render() {
-
-    // if this section has no content, omit it to prevent clutter. Otherwise it would show a toggle option that
-    // would hide/show nothing
-    if (this.props.items.length === 0) {
-      return null;
-    }
-
-    let toggleClassNames = 'accordion-header index-group-header';
-    let toggleBodyClassNames = 'index-list accordion-body collapse';
-    if (this.props.isExpanded) {
-      toggleClassNames += ' down';
-      toggleBodyClassNames += ' in';
-    }
-
-    const title = this.props.title;
-    const designDocName = this.props.designDocName;
-    const linkId = "nav-design-function-" + designDocName + this.props.selector;
-
-    return (
-      <li id={linkId}>
-        <a className={toggleClassNames} data-toggle="collapse" onClick={this.toggle}>
-          <div className="fonticon-play"></div>
-          {title}
-        </a>
-        <Collapse in={this.props.isExpanded}>
-          <ul className={toggleBodyClassNames}>
-            {this.createItems()}
-          </ul>
-        </Collapse>
-      </li>
-    );
-  }
-}
-
-class DesignDoc extends React.Component {
-  static propTypes = {
-    database: PropTypes.object.isRequired,
-    sidebarListTypes: PropTypes.array.isRequired,
-    isExpanded: PropTypes.bool.isRequired,
-    selectedNavInfo: PropTypes.object.isRequired,
-    toggledSections: PropTypes.object.isRequired,
-    designDocName:  PropTypes.string.isRequired
-  };
-
-  state = {
-    updatedSidebarListTypes: this.props.sidebarListTypes
-  };
-
-  UNSAFE_componentWillMount() {
-    if (_.isEmpty(this.state.updatedSidebarListTypes) ||
-      (_.has(this.state.updatedSidebarListTypes[0], 'selector') && this.state.updatedSidebarListTypes[0].selector !== 'views')) {
-
-      const newList = this.state.updatedSidebarListTypes;
-      newList.unshift({
-        selector: 'views',
-        name: 'Views',
-        urlNamespace: 'view',
-        indexLabel: 'view',
-        onDelete: IndexEditorActions.deleteView,
-        onClone: IndexEditorActions.cloneView,
-        onEdit: IndexEditorActions.gotoEditViewPage
-      });
-      this.setState({ updatedSidebarListTypes: newList });
-    }
-  }
-
-  indexList = () => {
-    return _.map(this.state.updatedSidebarListTypes, (index, key) => {
-      const expanded = _.has(this.props.toggledSections, index.name) && this.props.toggledSections[index.name];
-
-      // if an index in this list is selected, pass that down
-      let selectedIndex = '';
-      if (this.props.selectedNavInfo.designDocSection === index.name) {
-        selectedIndex = this.props.selectedNavInfo.indexName;
-      }
-
-      return (
-        <IndexSection
-          icon={index.icon}
-          isExpanded={expanded}
-          urlNamespace={index.urlNamespace}
-          indexLabel={index.indexLabel}
-          onEdit={index.onEdit}
-          onDelete={index.onDelete}
-          onClone={index.onClone}
-          selectedIndex={selectedIndex}
-          toggle={this.props.toggle}
-          database={this.props.database}
-          designDocName={this.props.designDocName}
-          key={key}
-          title={index.name}
-          selector={index.selector}
-          items={_.keys(this.props.designDoc[index.selector])} />
-      );
-    });
-  };
-
-  toggle = (e) => {
-    e.preventDefault();
-    this.props.toggle(this.props.designDocName);
-  };
-
-  getNewButtonLinks = () => {
-    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', encodeURIComponent(this.props.database.id));
-    const designDocName = this.props.designDocName;
-
-    const addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
-      menuLinks.push({
-        title: link.title,
-        url: '#' + newUrlPrefix + '/' + link.url + '/' + encodeURIComponent(designDocName),
-        icon: 'fonticon-plus-circled'
-      });
-      return menuLinks;
-    }, [{
-      title: 'New View',
-      url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
-      icon: 'fonticon-plus-circled'
-    }]);
-
-    return [{
-      title: 'Add New',
-      links: addNewLinks
-    }];
-  };
-
-  render () {
-    const buttonLinks = this.getNewButtonLinks();
-    let toggleClassNames = 'design-doc-section accordion-header';
-    let toggleBodyClassNames = 'design-doc-body accordion-body collapse';
-
-    if (this.props.isExpanded) {
-      toggleClassNames += ' down';
-      toggleBodyClassNames += ' in';
-    }
-    const designDocName = this.props.designDocName;
-    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.database.id, designDocName);
-    const metadataRowClass = (this.props.selectedNavInfo.designDocSection === 'metadata') ? 'active' : '';
-
-    return (
-      <li className="nav-header">
-        <div id={"sidebar-tab-" + designDocName} className={toggleClassNames}>
-          <div id={"nav-header-" + designDocName} onClick={this.toggle} className='accordion-list-item'>
-            <div className="fonticon-play"></div>
-            <p className='design-doc-name'>
-              <span title={'_design/' + designDocName}>{designDocName}</span>
-            </p>
-          </div>
-          <div className='new-button add-dropdown'>
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </div>
-        <Collapse in={this.props.isExpanded}>
-          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
-            <li className={metadataRowClass}>
-              <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
-                Metadata
-              </a>
-            </li>
-            {this.indexList()}
-          </ul>
-        </Collapse>
-      </li>
-    );
-  }
-}
-
-class DesignDocList extends React.Component {
-  UNSAFE_componentWillMount() {
-    const list = FauxtonAPI.getExtensions('sidebar:list');
-    this.sidebarListTypes = _.isUndefined(list) ? [] : list;
-  }
-
-  designDocList = () => {
-    return _.map(this.props.designDocs, (designDoc, key) => {
-      const ddName = decodeURIComponent(designDoc.safeId);
-
-      // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
-      let expanded = false,
-          toggledSections = {};
-      if (_.has(this.props.toggledSections, ddName)) {
-        expanded = this.props.toggledSections[ddName].visible;
-        toggledSections = this.props.toggledSections[ddName].indexGroups;
-      }
-
-      let selectedNavInfo = {};
-      if (this.props.selectedNav.navItem === 'designDoc' && this.props.selectedNav.designDocName === ddName) {
-        selectedNavInfo = this.props.selectedNav;
-      }
-
-      return (
-        <DesignDoc
-          toggle={this.props.toggle}
-          sidebarListTypes={this.sidebarListTypes}
-          isExpanded={expanded}
-          toggledSections={toggledSections}
-          selectedNavInfo={selectedNavInfo}
-          key={key}
-          designDoc={designDoc}
-          designDocName={ddName}
-          database={this.props.database} />
-      );
-    });
-  };
-
-  render() {
-    return (
-      <ul className="nav nav-list">
-        {this.designDocList()}
-      </ul>
-    );
-  }
-}
-
-class SidebarController extends React.Component {
-  getStoreState = () => {
-    return {
-      database: store.getDatabase(),
-      selectedNav: store.getSelected(),
-      designDocs: store.getDesignDocs(),
-      designDocList: store.getDesignDocList(),
-      availableDesignDocIds: store.getAvailableDesignDocs(),
-      toggledSections: store.getToggledSections(),
-      isLoading: store.isLoading(),
-      deleteDbModalProperties: deleteDbModalStore.getShowDeleteDatabaseModal(),
-
-      deleteIndexModalVisible: store.isDeleteIndexModalVisible(),
-      deleteIndexModalText: store.getDeleteIndexModalText(),
-      deleteIndexModalOnSubmit: store.getDeleteIndexModalOnSubmit(),
-      deleteIndexModalIndexName: store.getDeleteIndexModalIndexName(),
-      deleteIndexModalDesignDoc: store.getDeleteIndexDesignDoc(),
-
-      cloneIndexModalVisible: store.isCloneIndexModalVisible(),
-      cloneIndexModalTitle: store.getCloneIndexModalTitle(),
-      cloneIndexModalSelectedDesignDoc: store.getCloneIndexModalSelectedDesignDoc(),
-      cloneIndexModalNewDesignDocName: store.getCloneIndexModalNewDesignDocName(),
-      cloneIndexModalOnSubmit: store.getCloneIndexModalOnSubmit(),
-      cloneIndexDesignDocProp: store.getCloneIndexDesignDocProp(),
-      cloneIndexModalNewIndexName: store.getCloneIndexModalNewIndexName(),
-      cloneIndexSourceIndexName: store.getCloneIndexModalSourceIndexName(),
-      cloneIndexSourceDesignDocName: store.getCloneIndexModalSourceDesignDocName(),
-      cloneIndexModalIndexLabel: store.getCloneIndexModalIndexLabel()
-    };
-  };
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-    deleteDbModalStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
-    deleteDbModalStore.off('change', this.onChange, this);
-  }
-
-  onChange = () => {
-
-    const newState = this.getStoreState();
-    // Workaround to signal Redux store that the design doc list was updated
-    // which is currently required by QueryOptionsContainer
-    // It should be removed once Sidebar components are refactored to use Redux
-    if (this.props.reduxUpdatedDesignDocList) {
-      this.props.reduxUpdatedDesignDocList(newState.designDocList);
-    }
-
-    this.setState(newState);
-  };
-
-  showDeleteDatabaseModal = (payload) => {
-    ComponentsActions.showDeleteDatabaseModal(payload);
-  };
-
-  // handles deleting of any index regardless of type. The delete handler and all relevant info is set when the user
-  // clicks the delete action for a particular index
-  deleteIndex = () => {
-
-    // if the user is currently on the index that's being deleted, pass that info along to the delete handler. That can
-    // be used to redirect the user to somewhere appropriate
-    const isOnIndex = this.state.selectedNav.navItem === 'designDoc' &&
-      ('_design/' + this.state.selectedNav.designDocName) === this.state.deleteIndexModalDesignDoc.id &&
-      this.state.selectedNav.indexName === this.state.deleteIndexModalIndexName;
-
-    this.state.deleteIndexModalOnSubmit({
-      isOnIndex: isOnIndex,
-      indexName: this.state.deleteIndexModalIndexName,
-      designDoc: this.state.deleteIndexModalDesignDoc,
-      designDocs: this.state.designDocs,
-      database: this.state.database
-    });
-  };
-
-  cloneIndex = () => {
-    this.state.cloneIndexModalOnSubmit({
-      sourceIndexName: this.state.cloneIndexSourceIndexName,
-      sourceDesignDocName: this.state.cloneIndexSourceDesignDocName,
-      targetDesignDocName: this.state.cloneIndexModalSelectedDesignDoc,
-      newDesignDocName: this.state.cloneIndexModalNewDesignDocName,
-      newIndexName: this.state.cloneIndexModalNewIndexName,
-      designDocs: this.state.designDocs,
-      database: this.state.database,
-      onComplete: Actions.hideCloneIndexModal
-    });
-  };
-
-  state = this.getStoreState();
-
-  render() {
-    if (this.state.isLoading) {
-      return <LoadLines />;
-    }
-
-    return (
-      <nav className="sidenav">
-        <MainSidebar
-          selectedNavItem={this.state.selectedNav.navItem}
-          databaseName={this.state.database.id} />
-        <DesignDocList
-          selectedNav={this.state.selectedNav}
-          toggle={Actions.toggleContent}
-          toggledSections={this.state.toggledSections}
-          designDocs={this.state.designDocList}
-          database={this.state.database} />
-        <DeleteDatabaseModal
-          showHide={this.showDeleteDatabaseModal}
-          modalProps={this.state.deleteDbModalProperties} />
-
-        {/* the delete and clone index modals handle all index types, hence the props all being pulled from the store */}
-        <ConfirmationModal
-          title="Confirm Deletion"
-          visible={this.state.deleteIndexModalVisible}
-          text={this.state.deleteIndexModalText}
-          onClose={Actions.hideDeleteIndexModal}
-          onSubmit={this.deleteIndex} />
-        <CloneIndexModal
-          visible={this.state.cloneIndexModalVisible}
-          title={this.state.cloneIndexModalTitle}
-          close={Actions.hideCloneIndexModal}
-          submit={this.cloneIndex}
-          designDocArray={this.state.availableDesignDocIds}
-          selectedDesignDoc={this.state.cloneIndexModalSelectedDesignDoc}
-          newDesignDocName={this.state.cloneIndexModalNewDesignDocName}
-          newIndexName={this.state.cloneIndexModalNewIndexName}
-          indexLabel={this.state.cloneIndexModalIndexLabel} />
-      </nav>
-    );
-  }
-}
-
-class CloneIndexModal extends React.Component {
-  static propTypes = {
-    visible: PropTypes.bool.isRequired,
-    title: PropTypes.string,
-    close: PropTypes.func.isRequired,
-    submit: PropTypes.func.isRequired,
-    designDocArray: PropTypes.array.isRequired,
-    selectedDesignDoc: PropTypes.string.isRequired,
-    newDesignDocName: PropTypes.string.isRequired,
-    newIndexName: PropTypes.string.isRequired,
-    indexLabel: PropTypes.string.isRequired
-  };
-
-  static defaultProps = {
-    title: 'Clone Index',
-    visible: false
-  };
-
-  submit = () => {
-    if (!this.designDocSelector.validate()) {
-      return;
-    }
-    if (this.props.newIndexName === '') {
-      FauxtonAPI.addNotification({
-        msg: 'Please enter the new index name.',
-        type: 'error',
-        clear: true
-      });
-      return;
-    }
-    this.props.submit();
-  };
-
-  close = (e) => {
-    if (e) {
-      e.preventDefault();
-    }
-    this.props.close();
-  };
-
-  setNewIndexName = (e) => {
-    Actions.setNewCloneIndexName(e.target.value);
-  };
-
-  render() {
-    return (
-      <Modal dialogClassName="clone-index-modal" show={this.props.visible} onHide={this.close}>
-        <Modal.Header closeButton={true}>
-          <Modal.Title>{this.props.title}</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
-
-          <form className="form" method="post" onSubmit={this.submit}>
-            <p>
-              Select the design document where the cloned {this.props.indexLabel} will be created, and then enter
-              a name for the cloned {this.props.indexLabel}.
-            </p>
-
-            <div className="row">
-              <DesignDocSelector
-                ref={node => this.designDocSelector = node}
-                designDocList={this.props.designDocArray}
-                selectedDesignDocName={this.props.selectedDesignDoc}
-                newDesignDocName={this.props.newDesignDocName}
-                onSelectDesignDoc={Actions.selectDesignDoc}
-                onChangeNewDesignDocName={Actions.updateNewDesignDocName} />
-            </div>
-
-            <div className="clone-index-name-row">
-              <label className="new-index-title-label" htmlFor="new-index-name">{this.props.indexLabel} Name</label>
-              <input type="text" id="new-index-name" value={this.props.newIndexName} onChange={this.setNewIndexName}
-                placeholder="New view name" />
-            </div>
-          </form>
-
-        </Modal.Body>
-        <Modal.Footer>
-          <a href="#" className="cancel-link" onClick={this.close} data-bypass="true">Cancel</a>
-          <button onClick={this.submit} data-bypass="true" className="btn btn-primary save">
-            <i className="icon fonticon-ok-circled" /> Clone {this.props.indexLabel}</button>
-        </Modal.Footer>
-      </Modal>
-    );
-  }
-}
+import CloneIndexModal from './components/CloneIndexModal';
+import DesignDoc from './components/DesignDoc';
+import SidebarController from './components/SidebarController';
 
 export default {
-  SidebarController: SidebarController,
-  DesignDoc: DesignDoc,
-  CloneIndexModal: CloneIndexModal
+  SidebarController,
+  DesignDoc,
+  CloneIndexModal
 };
diff --git a/app/addons/documents/sidebar/stores.js b/app/addons/documents/sidebar/stores.js
deleted file mode 100644
index 37ece86..0000000
--- a/app/addons/documents/sidebar/stores.js
+++ /dev/null
@@ -1,337 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import React from "react";
-import ActionTypes from "./actiontypes";
-var Stores = {};
-
-Stores.SidebarStore = FauxtonAPI.Store.extend({
-
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._designDocs = new Backbone.Collection();
-    this._selected = {
-      navItem: 'all-docs',
-      designDocName: '',
-      designDocSection: '', // 'metadata' / name of index group ("Views", etc.)
-      indexName: ''
-    };
-    this._loading = true;
-    this._toggledSections = {};
-
-    this._deleteIndexModalVisible = false;
-    this._deleteIndexModalDesignDocName = '';
-    this._deleteIndexModalText = '';
-    this._deleteIndexModalIndexName = '';
-    this._deleteIndexModalOnSubmit = function () { };
-
-    this._cloneIndexModalVisible = false;
-    this._cloneIndexDesignDocProp = '';
-    this._cloneIndexModalTitle = '';
-    this._cloneIndexModalSelectedDesignDoc = '';
-    this._cloneIndexModalNewDesignDocName = '';
-    this._cloneIndexModalNewIndexName = '';
-    this._cloneIndexModalIndexLabel = '';
-    this._cloneIndexModalOnSubmit = function () { };
-  },
-
-  newOptions: function (options) {
-    this._database = options.database;
-    this._designDocs = options.designDocs;
-    this._loading = false;
-
-    // this can be expanded in future as we need. Right now it can only set a top-level nav item ('all docs',
-    // 'permissions' etc.) and not a nested page
-    if (options.selectedNavItem) {
-      this._selected = {
-        navItem: options.selectedNavItem,
-        designDocName: '',
-        designDocSection: '',
-        indexName: ''
-      };
-    }
-  },
-
-  updatedDesignDocs: function (designDocs) {
-    this._designDocs = designDocs;
-  },
-
-  isDeleteIndexModalVisible: function () {
-    return this._deleteIndexModalVisible;
-  },
-
-  getDeleteIndexModalText: function () {
-    return this._deleteIndexModalText;
-  },
-
-  getDeleteIndexModalOnSubmit: function () {
-    return this._deleteIndexModalOnSubmit;
-  },
-
-  isLoading: function () {
-    return this._loading;
-  },
-
-  getDatabase: function () {
-    if (this.isLoading()) {
-      return {};
-    }
-    return this._database;
-  },
-
-  // used to toggle both design docs, and any index groups within them
-  toggleContent: function (designDoc, indexGroup) {
-    if (!this._toggledSections[designDoc]) {
-      this._toggledSections[designDoc] = {
-        visible: true,
-        indexGroups: {}
-      };
-      return;
-    }
-
-    if (indexGroup) {
-      return this.toggleIndexGroup(designDoc, indexGroup);
-    }
-
-    this._toggledSections[designDoc].visible = !this._toggledSections[designDoc].visible;
-  },
-
-  toggleIndexGroup: function (designDoc, indexGroup) {
-    var expanded = this._toggledSections[designDoc].indexGroups[indexGroup];
-
-    if (_.isUndefined(expanded)) {
-      this._toggledSections[designDoc].indexGroups[indexGroup] = true;
-      return;
-    }
-
-    this._toggledSections[designDoc].indexGroups[indexGroup] = !expanded;
-  },
-
-  isVisible: function (designDoc, indexGroup) {
-    if (!this._toggledSections[designDoc]) {
-      return false;
-    }
-    if (indexGroup) {
-      return this._toggledSections[designDoc].indexGroups[indexGroup];
-    }
-    return this._toggledSections[designDoc].visible;
-  },
-
-  getSelected: function () {
-    return this._selected;
-  },
-
-  setSelected: function (params) {
-    this._selected = {
-      navItem: params.navItem,
-      designDocName: params.designDocName,
-      designDocSection: params.designDocSection,
-      indexName: params.indexName
-    };
-
-    if (params.designDocName) {
-      if (!_.has(this._toggledSections, params.designDocName)) {
-        this._toggledSections[params.designDocName] = { visible: true, indexGroups: {} };
-      }
-      this._toggledSections[params.designDocName].visible = true;
-
-      if (params.designDocSection) {
-        this._toggledSections[params.designDocName].indexGroups[params.designDocSection] = true;
-      }
-    }
-  },
-
-  getToggledSections: function () {
-    return this._toggledSections;
-  },
-
-  getDatabaseName: function () {
-    if (this.isLoading()) {
-      return '';
-    }
-    return this._database.safeID();
-  },
-
-  getDesignDocs: function () {
-    return this._designDocs;
-  },
-
-  // returns a simple array of design doc IDs
-  getAvailableDesignDocs: function () {
-    var availableDocs = this.getDesignDocs().filter(function (doc) {
-      return !doc.isMangoDoc();
-    });
-    return _.map(availableDocs, function (doc) {
-      return doc.id;
-    });
-  },
-
-  getDesignDocList: function () {
-    if (this.isLoading()) {
-      return {};
-    }
-    var docs = this._designDocs.toJSON();
-
-    docs = _.filter(docs, function (doc) {
-      if (_.has(doc.doc, 'language')) {
-        return doc.doc.language !== 'query';
-      }
-      return true;
-    });
-
-    return docs.map(function (doc) {
-      doc.safeId = app.utils.safeURLName(doc._id.replace(/^_design\//, ""));
-      return _.extend(doc, doc.doc);
-    });
-  },
-
-  showDeleteIndexModal: function (params) {
-    this._deleteIndexModalIndexName = params.indexName;
-    this._deleteIndexModalDesignDocName = params.designDocName;
-    this._deleteIndexModalVisible = true;
-    this._deleteIndexModalText = (<div>Are you sure you want to delete the <code>{this._deleteIndexModalIndexName}</code> {params.indexLabel}?</div>);
-    this._deleteIndexModalOnSubmit = params.onDelete;
-  },
-
-  getDeleteIndexModalIndexName: function () {
-    return this._deleteIndexModalIndexName;
-  },
-
-  getDeleteIndexDesignDoc: function () {
-    var designDoc = this._designDocs.find((ddoc) => {
-      return '_design/' + this._deleteIndexModalDesignDocName === ddoc.id;
-    });
-
-    return (designDoc) ? designDoc.dDocModel() : null;
-  },
-
-  isCloneIndexModalVisible: function () {
-    return this._cloneIndexModalVisible;
-  },
-
-  getCloneIndexModalTitle: function () {
-    return this._cloneIndexModalTitle;
-  },
-
-  showCloneIndexModal: function (params) {
-    this._cloneIndexModalIndexLabel = params.indexLabel;
-    this._cloneIndexModalTitle = params.cloneIndexModalTitle;
-    this._cloneIndexModalSourceIndexName = params.sourceIndexName;
-    this._cloneIndexModalSourceDesignDocName = params.sourceDesignDocName;
-    this._cloneIndexModalSelectedDesignDoc = '_design/' + params.sourceDesignDocName;
-    this._cloneIndexDesignDocProp = '';
-    this._cloneIndexModalVisible = true;
-    this._cloneIndexModalOnSubmit = params.onSubmit;
-  },
-
-  getCloneIndexModalIndexLabel: function () {
-    return this._cloneIndexModalIndexLabel;
-  },
-
-  getCloneIndexModalOnSubmit: function () {
-    return this._cloneIndexModalOnSubmit;
-  },
-
-  getCloneIndexModalSourceIndexName: function () {
-    return this._cloneIndexModalSourceIndexName;
-  },
-
-  getCloneIndexModalSourceDesignDocName: function () {
-    return this._cloneIndexModalSourceDesignDocName;
-  },
-
-  getCloneIndexDesignDocProp: function () {
-    return this._cloneIndexDesignDocProp;
-  },
-
-  getCloneIndexModalSelectedDesignDoc: function () {
-    return this._cloneIndexModalSelectedDesignDoc;
-  },
-
-  getCloneIndexModalNewDesignDocName: function () {
-    return this._cloneIndexModalNewDesignDocName;
-  },
-
-  getCloneIndexModalNewIndexName: function () {
-    return this._cloneIndexModalNewIndexName;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM:
-        this.setSelected(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_NEW_OPTIONS:
-        this.newOptions(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_TOGGLE_CONTENT:
-        this.toggleContent(action.designDoc, action.indexGroup);
-        break;
-
-      case ActionTypes.SIDEBAR_FETCHING:
-        this._loading = true;
-        break;
-
-      case ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL:
-        this.showDeleteIndexModal(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL:
-        this._deleteIndexModalVisible = false;
-        break;
-
-      case ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL:
-        this.showCloneIndexModal(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL:
-        this._cloneIndexModalVisible = false;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE:
-        this._cloneIndexModalSelectedDesignDoc = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED:
-        this._cloneIndexModalNewDesignDocName = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME:
-        this._cloneIndexModalNewIndexName = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS:
-        this.updatedDesignDocs(action.options.designDocs);
-        this._loading = false;
-        break;
-
-      default:
-        return;
-      // do nothing
-    }
-
-    this.triggerChange();
-  }
-
-});
-
-Stores.sidebarStore = new Stores.SidebarStore();
-Stores.sidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.sidebarStore.dispatch.bind(Stores.sidebarStore));
-
-export default Stores;
diff --git a/app/addons/permissions/layout.js b/app/addons/permissions/layout.js
index 2569911..3534733 100644
--- a/app/addons/permissions/layout.js
+++ b/app/addons/permissions/layout.js
@@ -13,9 +13,11 @@
 import React from 'react';
 import {TabsSidebarHeader} from '../documents/layouts';
 import PermissionsContainer from './container/PermissionsContainer';
-import SidebarComponents from "../documents/sidebar/sidebar";
+import SidebarControllerContainer from "../documents/sidebar/SidebarControllerContainer";
+import {SidebarItemSelection} from '../documents/sidebar/helpers';
 
 export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownLinks}) => {
+  const selectedNavItem = new SidebarItemSelection('permissions');
   return (
     <div id="dashboard" className="with-sidebar">
       <TabsSidebarHeader
@@ -29,7 +31,7 @@
       />
       <div className="with-sidebar tabs-with-sidebar content-area">
         <aside id="sidebar-content" className="scrollable">
-          <SidebarComponents.SidebarController />
+          <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
         </aside>
         <section id="dashboard-content" className="flex-layout flex-col">
           <PermissionsContainer url={endpoint} />
diff --git a/app/main.js b/app/main.js
index 1176692..4089e77 100644
--- a/app/main.js
+++ b/app/main.js
@@ -28,6 +28,12 @@
   combineReducers(FauxtonAPI.reducers),
   applyMiddleware(...FauxtonAPI.middlewares)
 );
+FauxtonAPI.reduxDispatch = (action) => {
+  store.dispatch(action);
+};
+FauxtonAPI.reduxState = () => {
+  return store.getState();
+};
 
 app.addons = LoadAddons;
 FauxtonAPI.router = app.router = new FauxtonAPI.Router(app.addons);