You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by be...@apache.org on 2016/02/18 02:11:13 UTC

fauxton commit: updated refs/heads/master to abc3e69

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master a62af899a -> abc3e697f


Misc database sidebar updates

This contains various updates to the database sidebar, as per
Justin's feedback.
- Order and labels of buttons are changed.
- a few unclear icons (map reduce, Views) have been removed
- properly highlights what page you're on.
- Now allows us to select any nav item in the sidebar
regardless of nesting to highlight the nav item. This also
now works on full page refreshes.

Note this feature paves the way for the next thing I'll work
on: updating Views so that they retain the sidebar. Once
that's changed, it will need to clearly highlight what
View you're on in the sidebar.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/abc3e697
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/abc3e697
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/abc3e697

Branch: refs/heads/master
Commit: abc3e697f3ba5bb76dbb32af8d3068a6d568909b
Parents: a62af89
Author: Ben Keen <be...@gmail.com>
Authored: Fri Feb 5 08:40:19 2016 -0800
Committer: Ben Keen <be...@gmail.com>
Committed: Wed Feb 17 09:17:18 2016 -0800

----------------------------------------------------------------------
 app/addons/documents/assets/less/sidenav.less   | 256 +++++++++----------
 app/addons/documents/routes-documents.js        |  12 +-
 app/addons/documents/routes-mango.js            |   2 +-
 app/addons/documents/shared-routes.js           |   7 +-
 app/addons/documents/sidebar/actions.js         |  26 +-
 app/addons/documents/sidebar/actiontypes.js     |   2 +-
 app/addons/documents/sidebar/sidebar.react.jsx  | 249 ++++++++++--------
 app/addons/documents/sidebar/stores.js          | 110 +++++---
 .../tests/sidebar.componentsSpec.react.jsx      |  30 +++
 .../sidebar/tests/sidebar.storesSpec.js         |   2 +-
 assets/less/templates.less                      |   4 +-
 11 files changed, 390 insertions(+), 310 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/assets/less/sidenav.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/sidenav.less b/app/addons/documents/assets/less/sidenav.less
index 11df376..9c6fb35 100644
--- a/app/addons/documents/assets/less/sidenav.less
+++ b/app/addons/documents/assets/less/sidenav.less
@@ -9,6 +9,7 @@
 // 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 "../../../../../assets/less/variables.less";
 
 #sidebar-content {
   .loading-lines {
@@ -16,176 +17,147 @@
   }  
 }
 
-.sidenav{
-  .sidebar-toggler {
-    top: 5px;
-    position: absolute;
-    right: 7px;
-    z-index: 100;
-    width: 60px;
-    height: 35px;
-    text-align: center;
-    line-height: 35px;
+.sidenav {
+  a {
     text-decoration: none;
-    .icon {
-      padding-right: 3px;
-    }
-
-    &.sidebar-hidden {
-      &:hover {
-        background-color: rgba(153, 153, 153, 0.1);
-      }
-      width: 120px;
-      right: 210px;
-    }
-  }
-  .nav li + .nav-header {
-    margin-top: 0px;
   }
-  .nav-list > .active > a{
+
+  // selected nav item row styles
+  .nav-list .active > a {
     text-shadow: none;
-    background-color: rgba(0, 0, 0, 0.05);
-  }
-  .nav-list > .active > a:hover {
-    color: white;
-  }
-  .nav-list > li > a:hover + div.add-dropdown .dropdown-toggle{
-    color: white;
+    background-color: #f1f0f1;
+    color: @brandPrimaryDark;
+    &:hover {
+      color: white;
+    }
   }
+
   .dropdown-toggle:hover {
     color: @linkColor;
   }
-  li{
-    position: relative;
-    a{
-      text-shadow: none;
+
+  // ugly! This styles the (+) icon to make it white when a user hovers over a row. Better solution would be to move the
+  // active class to the <li> instead of the child <a>
+  .accordion-list-item:hover, .nav-list > li a:hover {
+    & + div.add-dropdown .dropdown-toggle {
+      color: white;
     }
   }
-  li.nav-header {
+
+  li {
     position: relative;
-    > .accordion-body{
-      &.in {
-        border-bottom: 1px solid #d3d7db;
-      }
+    a {
+      text-shadow: none;
     }
-    .accordion-body{
-      color: #eee;
-      margin-left: 0;
-      li.active > a{
-        background-color: rgba(0, 0, 0, 0.055);
+  }
+
+  .design-doc-section {
+    border-bottom: 1px solid #d3d7db;
+    .accordion-list-item:hover {
+      p, .fonticon-play {
+        color: white;
       }
-      li a{
-        font-size: 14px;
-        background-color: rgba(0, 0, 0, 0.02);
-        &:hover {
-          color: #fff;
-          text-decoration: none;
-          background-color: @darkRed;
-        }
+      p {
+        background-color: @darkRed;
       }
     }
-    .fonticon-play{
-        color: @subListGray;
-        position: absolute;
-        display: block;
-        vertical-align: bottom;
-      }
-    .accordion-header:hover .fonticon-play {
-            color: white;
-     }
-    .fonticon-play{
+    .accordion-list-item p {
       .transition(all 0.25s linear);
     }
-    > .accordion-header{
-      position: relative;
-      display: block;
+    .fonticon-play {
+      font-size: 12px;
+      top: 12px;
+      left: 12px;
+    }
+    &.down .fonticon-play {
+      .rotate(90deg);
+    }
+    .add-dropdown {
+      right: 15px;
+    }
+  }
+
+  li.nav-header .index-list li a {
+    padding-left: 46px;
+  }
+
+  li.nav-header {
+    margin-top: 0;
+    position: relative;
+
+    .design-doc-body.in {
       border-bottom: 1px solid #d3d7db;
-      .accordion-list-item:hover {
-        p, .fonticon-play{
-          color: white;
-        }
-        p{
-          background-color: @darkRed;
-        }
-      }
-      .accordion-list-item:hover + div.add-dropdown .dropdown-toggle{
-        color: white;
-      }
-      .accordion-list-item p{
-        .transition(all 0.25s linear);
-      }
-      .design-doc-name {
-        cursor: pointer;
-        margin: 0;
-        color: @linkColor;
-        span {
-          width: @sidebarWidth - 30px;
-          display: block;
-          padding: 10px 13px 10px 36px;
-          text-overflow: ellipsis;
-          overflow: hidden;
-        }
-      }
-      .fonticon-play{
-        font-size: 12px;
-        top: 12px;
-        left: 12px;
-      }
-      &.down .fonticon-play {
-        .rotate(90deg);
-      }
-      .add-dropdown{
-        right: 15px;
-      }
-      .dropdown-toggle:hover{
-        color: @linkColor;
-      }
     }
-    > ul.accordion-body{
-      > li {
-        cursor: pointer;
-        a{
-          border-top: none;
-          &.accordion-header{
-            padding: 8px 5px 8px 36px;
-          }
-        }
-        .toggle-view{
-          padding: 4px 5px 4px 36px;
-        }
-        .fonticon-play{
-          font-size: 8px;
-          top: 11px;
-          left: 15px;
-        }
-        .fonticon:before{
-          margin-right: 6px;
-          font-size: 20px;
-          top: 3px;
-          position: relative;
-        }
-      }
 
-      li {
-        > a.down .fonticon-play {
-          .rotate(90deg);
-        }
-      }
+    .accordion-body {
+      color: #eee;
+      margin-left: 0;
 
       li {
+        cursor: pointer;
+        &.active > a {
+          background-color: #f1f0f1;
+        }
         > a.down .fonticon-play {
           .rotate(90deg);
         }
-
-        &:hover {
-          .fonticon-play {
-            color: white;
-
+        &:hover .fonticon-play {
+          color: #dddddd;
+        }
+        a {
+          font-size: 14px;
+          background-color: rgba(0, 0, 0, 0.02);
+          &:hover {
+            color: #fff;
+            text-decoration: none;
+            background-color: @darkRed;
+          }
+          border-top: none;
+          &.accordion-header{
+            padding: 8px 5px 8px 36px;
           }
         }
-
       }
+    }
+
+    .toggle-view {
+      padding: 4px 5px 4px 36px;
+    }
+    .fonticon-play {
+      font-size: 8px;
+      top: 11px;
+      left: 15px;
+      color: @subListGray;
+      position: absolute;
+      display: block;
+      vertical-align: bottom;
+      .transition(all 0.25s linear);
+    }
+    .fonticon:before {
+      margin-right: 6px;
+      font-size: 20px;
+      top: 3px;
+      position: relative;
+    }
+    .accordion-header:hover .fonticon-play {
+      color: white;
+    }
+  }
 
+  .design-doc-name {
+    cursor: pointer;
+    margin: 0;
+    color: #222222;
+    span {
+      width: @sidebarWidth - 30px;
+      display: block;
+      padding: 10px 13px 10px 36px;
+      text-overflow: ellipsis;
+      overflow: hidden;
     }
   }
+
+  .index-group-header {
+    font-weight: bold;
+  }
 }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/routes-documents.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index 3829981..e6bd8b1 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -50,7 +50,6 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, Databa
           roles: ['fx_loggedIn']
         },
         'database/:database/_changes': 'changes'
-
       },
 
       events: {
@@ -96,7 +95,10 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, Databa
         });
         this.setComponent("#dashboard-lower-content", DesignDocInfoComponents.DesignDocInfo);
 
-        SidebarActions.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + "_metadata");
+        SidebarActions.selectNavItem('designDoc', {
+          designDocName: ddoc,
+          designDocSection: 'metadata'
+        });
 
         this.leftheader.updateCrumbs(this.getCrumbs(this.database));
         this.rightHeader.hideQueryOptions();
@@ -137,7 +139,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, Databa
           tab = 'design-docs';
         }
 
-        SidebarActions.setSelectedTab(tab);
+        SidebarActions.selectNavItem(tab);
         ComponentsActions.showDeleteDatabaseModal({showDeleteModal: false, dbId: ''});
 
         this.removeComponent('#dashboard-upper-content');
@@ -171,7 +173,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, Databa
       //TODO: REMOVE
       reloadDesignDocs: function (event) {
         if (event && event.selectedTab) {
-          SidebarActions.setSelectedTab(event.selectedTab);
+          SidebarActions.selectNavItem(event.selectedTab);
         }
       },
 
@@ -187,7 +189,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, Databa
 
         this.viewEditor && this.viewEditor.remove();
 
-        SidebarActions.setSelectedTab('changes');
+        SidebarActions.selectNavItem('changes');
         this.leftheader.updateCrumbs(this.getCrumbs(this.database));
         this.rightHeader.hideQueryOptions();
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/routes-mango.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index 379150b..b24b206 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -86,7 +86,7 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Databases,
             }
           });
 
-      SidebarActions.setSelectedTab('mango-query');
+      SidebarActions.selectNavItem('mango-query');
       this.setComponent('#react-headerbar', ReactHeader.BulkDocumentHeaderController, {showIncludeAllDocs: false});
       this.setComponent('#footer', ReactPagination.Footer);
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/shared-routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js
index 2238cf2..da95edb 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -101,14 +101,13 @@ define([
       }));
     },
 
-    addSidebar: function (selectedTab) {
+    addSidebar: function (selectedNavItem) {
       var options = {
         designDocs: this.designDocs,
         database: this.database
       };
-
-      if (selectedTab) {
-        options.selectedTab = selectedTab;
+      if (selectedNavItem) {
+        options.selectedNavItem = selectedNavItem;
       }
 
       SidebarActions.newOptions(options);

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/actions.js b/app/addons/documents/sidebar/actions.js
index 4dbd83f..3419da0 100644
--- a/app/addons/documents/sidebar/actions.js
+++ b/app/addons/documents/sidebar/actions.js
@@ -18,6 +18,7 @@ define([
 ],
 function (app, FauxtonAPI, ActionTypes, Stores) {
   var store = Stores.sidebarStore;
+
   return {
     newOptions: function (options) {
       if (options.database.safeID() !== store.getDatabaseName()) {
@@ -34,18 +35,33 @@ function (app, FauxtonAPI, ActionTypes, Stores) {
       });
     },
 
-    toggleContent: function (designDoc, index) {
+    toggleContent: function (designDoc, indexGroup) {
       FauxtonAPI.dispatch({
         type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
         designDoc: designDoc,
-        index: index
+        indexGroup: indexGroup
       });
     },
 
-    setSelectedTab: function (tab) {
+    // 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' });
+    selectNavItem: function (navItem, params) {
+      var settings = $.extend(true, {}, {
+        designDocName: '',
+        designDocSection: '',
+        indexName: ''
+      }, params);
+      settings.navItem = navItem;
+
       FauxtonAPI.dispatch({
-        type: ActionTypes.SIDEBAR_SET_SELECTED_TAB,
-        tab: tab
+        type: ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM,
+        options: settings
       });
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/actiontypes.js b/app/addons/documents/sidebar/actiontypes.js
index 157c3fa..70a5547 100644
--- a/app/addons/documents/sidebar/actiontypes.js
+++ b/app/addons/documents/sidebar/actiontypes.js
@@ -12,7 +12,7 @@
 
 define([], function () {
   return {
-    SIDEBAR_SET_SELECTED_TAB: 'SIDEBAR_SET_SELECTED_TAB',
+    SIDEBAR_SET_SELECTED_NAV_ITEM: 'SIDEBAR_SET_SELECTED_NAV_ITEM',
     SIDEBAR_NEW_OPTIONS: 'SIDEBAR_NEW_OPTIONS',
     SIDEBAR_TOGGLE_CONTENT: 'SIDEBAR_TOGGLE_CONTENT',
     SIDEBAR_FETCHING: 'SIDEBAR_FETCHING',

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/sidebar.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/sidebar.react.jsx b/app/addons/documents/sidebar/sidebar.react.jsx
index 471aeb6..326e10b 100644
--- a/app/addons/documents/sidebar/sidebar.react.jsx
+++ b/app/addons/documents/sidebar/sidebar.react.jsx
@@ -28,7 +28,11 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
   var store = Stores.sidebarStore;
   var LoadLines = Components.LoadLines;
 
+
   var MainSidebar = React.createClass({
+    propTypes: {
+      selectedNavItem: React.PropTypes.string.isRequired
+    },
 
     getNewButtonLinks: function () {  // these are links for the sidebar '+' on All Docs and All Design Docs
       return DocumentHelper.getNewButtonLinks(this.props.databaseName);
@@ -36,37 +40,31 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
 
     buildDocLinks: function () {
       var base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
-      var isActive = this.props.isActive;
-
       return FauxtonAPI.getExtensions('docLinks').map(function (link) {
         return (
-          <li key={link.url} className={isActive(link.url)}>
+          <li key={link.url} className={this.getNavItemClass(link.url)}>
             <a id={link.url} href={base + link.url}>{link.title}</a>
           </li>
         );
-      });
+      }, this);
+    },
+
+    getNavItemClass: function (navItem) {
+      return (navItem === this.props.selectedNavItem) ? 'active' : '';
     },
 
     render: function () {
-      var isActive = this.props.isActive;
       var docLinks = this.buildDocLinks();
-      var changesUrl = '#' + FauxtonAPI.urls('changes', 'app', this.props.databaseName, '');
+      var changesUrl     = '#' + FauxtonAPI.urls('changes', 'app', this.props.databaseName, '');
       var permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', this.props.databaseName);
-      var databaseUrl = FauxtonAPI.urls('allDocs', 'app', this.props.databaseName, '');
-      var mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', this.props.databaseName);
+      var databaseUrl    = FauxtonAPI.urls('allDocs', 'app', this.props.databaseName, '');
+      var mangoQueryUrl  = FauxtonAPI.urls('mango', 'query-app', this.props.databaseName);
       var runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
       var buttonLinks = this.getNewButtonLinks();
 
       return (
         <ul className="nav nav-list">
-          <li className={isActive('permissions')}>
-            <a id="permissions" href={permissionsUrl}>Permissions</a>
-          </li>
-          <li className={isActive('changes')}>
-            <a id="changes" href={changesUrl}>Changes</a>
-          </li>
-          {docLinks}
-          <li className={isActive('all-docs')}>
+          <li className={this.getNavItemClass('all-docs')}>
             <a id="all-docs"
               href={"#/" + databaseUrl}
               className="toggle-view">
@@ -75,24 +73,28 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
             <div id="new-all-docs-button" className="add-dropdown">
               <Components.MenuDropDown links={buttonLinks} />
             </div>
-           </li>
-          <li className={isActive('mango-query')}>
+          </li>
+          <li className={this.getNavItemClass('mango-query')}>
             <a
               id="mango-query"
               href={'#' + mangoQueryUrl}
               className="toggle-view">
               {runQueryWithMangoText}
             </a>
-            <div id="mango-query-button" className="add-dropdown">
-              <Components.MenuDropDown links={buttonLinks} />
-            </div>
           </li>
-          <li className={isActive('design-docs')}>
+          <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">
-              All Design Docs
+              Design Documents
             </a>
             <div id="new-design-docs-button" className="add-dropdown">
               <Components.MenuDropDown links={buttonLinks} />
@@ -108,24 +110,26 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
 
     propTypes: {
       urlNamespace: React.PropTypes.string.isRequired,
-      icon: React.PropTypes.string.isRequired,
       databaseName: React.PropTypes.string.isRequired,
       designDocName: React.PropTypes.string.isRequired,
-      items: React.PropTypes.array.isRequired
+      items: React.PropTypes.array.isRequired,
+      isExpanded: React.PropTypes.bool.isRequired,
+      selectedIndex: React.PropTypes.string.isRequired
     },
 
     createItems: function () {
       return _.map(this.props.items, function (index, key) {
         var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', this.props.databaseName, this.props.designDocName);
+        var className = (this.props.selectedIndex === index) ? 'active' : '';
 
         return (
-          <li key={key}>
-          <a
-            id={this.props.designDocName + '_' + index}
-            href={"#/" + href + index}
-            className="toggle-view">
-            {index}
-          </a>
+          <li className={className} key={key}>
+            <a
+              id={this.props.designDocName + '_' + index}
+              href={"#/" + href + index}
+              className="toggle-view">
+              {index}
+            </a>
           </li>
         );
       }, this);
@@ -133,7 +137,7 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
 
     toggle: function (e) {
       e.preventDefault();
-      var newToggleState = !this.props.contentVisible;
+      var newToggleState = !this.props.isExpanded;
       var state = newToggleState ? 'show' : 'hide';
       $(ReactDOM.findDOMNode(this)).find('.accordion-body').collapse(state);
       this.props.toggle(this.props.designDocName, this.props.title);
@@ -147,22 +151,21 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
         return null;
       }
 
-      var toggleClassNames = 'accordion-header';
-      var toggleBodyClassNames = 'accordion-body collapse';
-      if (this.props.contentVisible) {
+      var toggleClassNames = 'accordion-header index-group-header';
+      var toggleBodyClassNames = 'index-list accordion-body collapse';
+      if (this.props.isExpanded) {
         toggleClassNames += ' down';
         toggleBodyClassNames += ' in';
       }
 
       var title = this.props.title;
-      var icon = this.props.icon;
       var designDocName = this.props.designDocName;
       var 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>
-            <span className={icon + " fonticon"}></span>
             {title}
           </a>
           <ul className={toggleBodyClassNames}>
@@ -171,13 +174,15 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
         </li>
       );
     }
-
   });
 
-  var DesignDoc = React.createClass({
 
+  var DesignDoc = React.createClass({
     propTypes: {
-      sidebarListTypes: React.PropTypes.array.isRequired
+      sidebarListTypes: React.PropTypes.array.isRequired,
+      isExpanded: React.PropTypes.bool.isRequired,
+      selectedNavInfo: React.PropTypes.object.isRequired,
+      toggledSections: React.PropTypes.object.isRequired
     },
 
     getInitialState: function () {
@@ -194,32 +199,42 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
         newList.unshift({
           selector: 'views',
           name: 'Views',
-          icon: 'fonticon-sidenav-map-reduce',
           urlNamespace: 'view'
         });
         this.setState({ updatedSidebarListTypes: newList });
       }
     },
 
-    createIndexList: function () {
+    indexList: function () {
       return _.map(this.state.updatedSidebarListTypes, function (index, key) {
-        return <IndexSection
-          icon={index.icon}
-          urlNamespace={index.urlNamespace}
-          contentVisible={this.props.isVisible(this.props.designDocName, index.name)}
-          toggle={this.props.toggle}
-          databaseName={this.props.databaseName}
-          designDocName={this.props.designDocName}
-          key={key}
-          title={index.name}
-          selector={index.selector}
-          items={_.keys(this.props.designDoc[index.selector])} />;
+        var expanded = _.has(this.props.toggledSections, index.name) && this.props.toggledSections[index.name];
+
+        // if an index in this list is selected, pass that down
+        var selectedIndex = '';
+        if (this.props.selectedNavInfo.designDocSection === index.name) {
+          selectedIndex = this.props.selectedNavInfo.indexName;
+        }
+
+        return (
+          <IndexSection
+            icon={index.icon}
+            isExpanded={expanded}
+            urlNamespace={index.urlNamespace}
+            selectedIndex={selectedIndex}
+            toggle={this.props.toggle}
+            databaseName={this.props.databaseName}
+            designDocName={this.props.designDocName}
+            key={key}
+            title={index.name}
+            selector={index.selector}
+            items={_.keys(this.props.designDoc[index.selector])} />
+        );
       }.bind(this));
     },
 
     toggle: function (e) {
       e.preventDefault();
-      var newToggleState = !this.props.contentVisible;
+      var newToggleState = !this.props.isExpanded;
       var state = newToggleState ? 'show' : 'hide';
       $(ReactDOM.findDOMNode(this)).find('#' + this.props.designDocName).collapse(state);
       this.props.toggle(this.props.designDocName);
@@ -236,7 +251,6 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
           url: '#' + newUrlPrefix + '/' + link.url + '/' + designDocName,
           icon: 'fonticon-plus-circled'
         });
-
         return menuLinks;
       }, [{
         title: 'New View',
@@ -252,76 +266,93 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
 
     render: function () {
       var buttonLinks = this.getNewButtonLinks();
-      var toggleClassNames = 'accordion-header';
-      var toggleBodyClassNames = 'accordion-body collapse';
+      var toggleClassNames = 'design-doc-section accordion-header';
+      var toggleBodyClassNames = 'design-doc-body accordion-body collapse';
 
-      if (this.props.contentVisible) {
+      if (this.props.isExpanded) {
         toggleClassNames += ' down';
         toggleBodyClassNames += ' in';
       }
       var designDocName = this.props.designDocName;
       var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.databaseName, designDocName);
+      var 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}>{'_design/' + designDocName}</span>
-            </p>
-          </div>
-          <div className='new-button add-dropdown'>
-            <Components.MenuDropDown links={buttonLinks} />
+        <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'>
+              <Components.MenuDropDown links={buttonLinks} />
+            </div>
           </div>
-        </div>
-        <ul className={toggleBodyClassNames} id={this.props.designDocName}>
-          <li>
-            <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
-              <span className="fonticon-sidenav-info fonticon"></span>
-              Design Doc Metadata
-            </a>
-          </li>
-          {this.createIndexList()}
-        </ul>
+          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
+            <li className={metadataRowClass}>
+              <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
+                Metadata
+              </a>
+            </li>
+            {this.indexList()}
+          </ul>
         </li>
       );
     }
-
   });
 
+
   var DesignDocList = React.createClass({
     componentWillMount: function () {
       var list = FauxtonAPI.getExtensions('sidebar:list');
       this.sidebarListTypes = _.isUndefined(list) ? [] : list;
     },
 
-    createDesignDocs: function () {
+    designDocList: function () {
       return _.map(this.props.designDocs, function (designDoc, key) {
-        return <DesignDoc
-          toggle={this.props.toggle}
-          sidebarListTypes={this.sidebarListTypes}
-          contentVisible={this.props.isVisible(designDoc.safeId)}
-          isVisible={this.props.isVisible}
-          key={key}
-          designDoc={designDoc}
-          designDocName={designDoc.safeId}
-          databaseName={this.props.databaseName} />;
+        var ddName = designDoc.safeId;
+
+        // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
+        var expanded = false,
+          toggledSections = {};
+        if (_.has(this.props.toggledSections, ddName)) {
+          expanded = this.props.toggledSections[ddName].visible;
+          toggledSections = this.props.toggledSections[ddName].indexGroups;
+        }
+
+        var 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}
+            databaseName={this.props.databaseName} />
+        );
       }.bind(this));
     },
 
     render: function () {
       return (
         <ul className="nav nav-list">
-          {this.createDesignDocs()}
+          {this.designDocList()}
         </ul>
       );
     }
-
   });
 
-  var DeleteDBModalWrapper = React.createClass({
 
+  var DeleteDBModalWrapper = React.createClass({
     componentDidMount: function () {
       this.dbModal = new DeleteDBModal({
         database: this.props.database,
@@ -344,29 +375,21 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
     render: function () {
       return <div id="delete-db-modal"> </div>;
     }
-
   });
 
+
   var SidebarController = React.createClass({
     getStoreState: function () {
       return {
         databaseName: store.getDatabaseName(),
-        selectedTab: store.getSelectedTab(),
+        selectedNav: store.getSelected(),
         designDocs: store.getDesignDocs(),
-        isVisible: _.bind(store.isVisible, store),
+        toggledSections: store.getToggledSections(),
         isLoading: store.isLoading(),
         database: store.getDatabase()
       };
     },
 
-    isActive: function (id) {
-      if (id === this.state.selectedTab) {
-        return 'active';
-      }
-
-      return '';
-    },
-
     getInitialState: function () {
       return this.getStoreState();
     },
@@ -380,24 +403,28 @@ function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, Documen
     },
 
     onChange: function () {
-      this.setState(this.getStoreState());
+      if (this.isMounted()) {
+        this.setState(this.getStoreState());
+      }
     },
 
-
     render: function () {
       if (this.state.isLoading) {
         return <LoadLines />;
       }
-
       return (
         <nav className="sidenav">
-          <MainSidebar isActive={this.isActive} databaseName={this.state.databaseName} />
+          <MainSidebar
+            selectedNavItem={this.state.selectedNav.navItem}
+            databaseName={this.state.databaseName} />
           <DesignDocList
+            selectedNav={this.state.selectedNav}
             toggle={Actions.toggleContent}
-            isVisible={this.state.isVisible}
+            toggledSections={this.state.toggledSections}
             designDocs={this.state.designDocs}
             databaseName={this.state.databaseName} />
-          <DeleteDBModalWrapper database={this.state.database}/>
+          <DeleteDBModalWrapper
+            database={this.state.database} />
         </nav>
       );
     }

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/stores.js b/app/addons/documents/sidebar/stores.js
index 8fdbefd..f351fcd 100644
--- a/app/addons/documents/sidebar/stores.js
+++ b/app/addons/documents/sidebar/stores.js
@@ -22,7 +22,12 @@ function (app, FauxtonAPI, ActionTypes) {
   Stores.SidebarStore = FauxtonAPI.Store.extend({
 
     initialize: function () {
-      this._selectedTab = 'all-docs';
+      this._selected = {
+        navItem: 'all-docs',
+        designDocName: '',
+        designDocSection: '', // metadata / name of index group ("Views", etc.)
+        indexName: ''
+      };
       this._loading = true;
       this._toggledSections = {};
     },
@@ -32,8 +37,15 @@ function (app, FauxtonAPI, ActionTypes) {
       this._designDocs = options.designDocs;
       this._loading = false;
 
-      if (options.selectedTab) {
-        this.setSelectedTab(options.selectedTab);
+      // 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: ''
+        };
       }
     },
 
@@ -42,62 +54,89 @@ function (app, FauxtonAPI, ActionTypes) {
     },
 
     getDatabase: function () {
-      if (this.isLoading()) {return {};}
-
+      if (this.isLoading()) {
+        return {};
+      }
       return this._database;
     },
 
-    toggleContent: function (designDoc, index) {
+    // 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,
-          indexes: {}
+          indexGroups: {}
         };
         return;
       }
 
-      if (index) {
-        return this.toggleIndex(designDoc, index);
+      if (indexGroup) {
+        return this.toggleIndexGroup(designDoc, indexGroup);
       }
 
       this._toggledSections[designDoc].visible = !this._toggledSections[designDoc].visible;
     },
 
-    toggleIndex: function (designDoc, indexName) {
-      var index = this._toggledSections[designDoc].indexes[indexName];
+    toggleIndexGroup: function (designDoc, indexGroup) {
+      var expanded = this._toggledSections[designDoc].indexGroups[indexGroup];
 
-      if (_.isUndefined(index)) {
-        this._toggledSections[designDoc].indexes[indexName] = true;
+      if (_.isUndefined(expanded)) {
+        this._toggledSections[designDoc].indexGroups[indexGroup] = true;
         return;
       }
 
-      this._toggledSections[designDoc].indexes[indexName] = !index;
+      this._toggledSections[designDoc].indexGroups[indexGroup] = !expanded;
     },
 
-    isVisible: function (designDoc, index) {
+    isVisible: function (designDoc, indexGroup) {
       if (!this._toggledSections[designDoc]) {
         return false;
       }
-
-      if (index) {
-        return this._toggledSections[designDoc].indexes[index];
+      if (indexGroup) {
+        return this._toggledSections[designDoc].indexGroups[indexGroup];
       }
-
       return this._toggledSections[designDoc].visible;
     },
 
-    setSelectedTab: function (tab) {
-      this._selectedTab = tab;
+    getSelected: function () {
+      return this._selected;
     },
 
-    getDatabaseName: function () {
-      if (this.isLoading()) { return '';}
+    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 () {
-      if (this.isLoading()) { return {};}
+      if (this.isLoading()) {
+        return {};
+      }
       var docs = this._designDocs.toJSON();
 
       docs = _.filter(docs, function (doc) {
@@ -109,47 +148,42 @@ function (app, FauxtonAPI, ActionTypes) {
 
       return docs.map(function (doc) {
         doc.safeId = app.utils.safeURLName(doc._id.replace(/^_design\//, ""));
-
         return _.extend(doc, doc.doc);
       });
     },
 
-    getSelectedTab: function () {
-      return this._selectedTab;
-    },
-
     dispatch: function (action) {
       switch (action.type) {
-        case ActionTypes.SIDEBAR_SET_SELECTED_TAB:
-          this.setSelectedTab(action.tab);
-          this.triggerChange();
+        case ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM:
+          this.setSelected(action.options);
         break;
+
         case ActionTypes.SIDEBAR_NEW_OPTIONS:
           this.newOptions(action.options);
-          this.triggerChange();
         break;
+
         case ActionTypes.SIDEBAR_TOGGLE_CONTENT:
-          this.toggleContent(action.designDoc, action.index);
-          this.triggerChange();
+          this.toggleContent(action.designDoc, action.indexGroup);
         break;
+
         case ActionTypes.SIDEBAR_FETCHING:
           this._loading = true;
-          this.triggerChange();
         break;
+
         case ActionTypes.SIDEBAR_REFRESH:
-          this.triggerChange();
         break;
 
         default:
         return;
         // do nothing
       }
+
+      this.triggerChange();
     }
 
   });
 
   Stores.sidebarStore = new Stores.SidebarStore();
-
   Stores.sidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.sidebarStore.dispatch);
 
   return Stores;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx b/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx
index a57fc28..68436f8 100644
--- a/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx
+++ b/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx
@@ -24,6 +24,13 @@ define([
   describe('DesignDoc', function () {
     var container;
 
+    var selectedNavInfo = {
+      navItem: 'all-docs',
+      designDocName: '',
+      designDocSection: '',
+      indexName: ''
+    };
+
     beforeEach(function () {
       container = document.createElement('div');
     });
@@ -40,6 +47,7 @@ define([
         contentVisible={true}
         isVisible={stub}
         designDoc={{}}
+        selectedNavInfo={selectedNavInfo}
         designDocName="id"
         databaseName="db-name" />, container);
       var subOptions = $(ReactDOM.findDOMNode(el)).find('.accordion-body li');
@@ -63,6 +71,7 @@ define([
             one: 'something'
           }
         }}
+        selectedNavInfo={selectedNavInfo}
         designDocName="id"
         databaseName="db-name" />, container);
       var subOptions = $(ReactDOM.findDOMNode(el)).find('.accordion-body li');
@@ -81,6 +90,7 @@ define([
         }]}
         contentVisible={true}
         isVisible={stub}
+        selectedNavInfo={selectedNavInfo}
         designDoc={{}} // note that this is empty
         designDocName="id"
         databaseName="db-name" />, container);
@@ -88,6 +98,26 @@ define([
       assert.equal(subOptions.length, 1);
     });
 
+    it('confirm doc metadata page is highlighted if selected', function () {
+      var stub = function () { return true; };
+      var el = TestUtils.renderIntoDocument(<DesignDoc
+        toggle={stub}
+        sidebarListTypes={[]}
+        contentVisible={true}
+        isVisible={stub}
+        selectedNavInfo={{
+          navItem: 'designDoc',
+          designDocName: 'id',
+          designDocSection: 'metadata',
+          indexName: ''
+        }}
+        designDoc={{}}
+        designDocName="id"
+        databaseName="db-name" />, container);
+
+      assert.equal($(ReactDOM.findDOMNode(el)).find('.accordion-body li.active a').html(), 'Metadata');
+    });
+
   });
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/app/addons/documents/sidebar/tests/sidebar.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/tests/sidebar.storesSpec.js b/app/addons/documents/sidebar/tests/sidebar.storesSpec.js
index dbcbee2..3012820 100644
--- a/app/addons/documents/sidebar/tests/sidebar.storesSpec.js
+++ b/app/addons/documents/sidebar/tests/sidebar.storesSpec.js
@@ -19,7 +19,7 @@ define([
   var assert = testUtils.assert;
   var dispatchToken;
   var store;
-  var opts;
+
 
   describe('Sidebar Store', function () {
     beforeEach(function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/abc3e697/assets/less/templates.less
----------------------------------------------------------------------
diff --git a/assets/less/templates.less b/assets/less/templates.less
index 26098dd..172de0f 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -453,10 +453,10 @@ with_tabs_sidebar.html
       color: #fff;
     }
     li.active > a {
-      color: @linkColorHover;
+      color: @brandPrimaryDark;
     }
     > li > a {
-      color: @linkColor;
+      color: #333333;
       padding: 10px 13px 10px 24px;
       border-bottom: 1px solid #d3d7db;
       span {