couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [couchdb-fauxton] branch master updated: (#883) - Navbar refactor
Date Wed, 05 Apr 2017 14:54:21 GMT
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

The following commit(s) were added to refs/heads/master by this push:
       new  b212dd5   (#883) - Navbar refactor
b212dd5 is described below

commit b212dd5b5b8e9aa2f90f125d8c9d14263fa90b99
Author: Ryan Millay <rmillay@us.ibm.com>
AuthorDate: Wed Apr 5 10:54:19 2017 -0400

    (#883) - Navbar refactor
    
    * NavBar: Rework NavBar
    
    Clean up of the Navbar up and changed the styling a bit.
    
    Big internal refactor of the codebase.
    
    * the death of the burger + minimized by default
    * Removed jquery dependency from navbar and repaired bugs
    * dynamic image paths and hardening a stubborn test
---
 app/addons/auth/__tests__/base.test.js             |  83 ++++++++
 app/addons/auth/base.js                            |  49 ++---
 app/addons/auth/routes.js                          |   5 +-
 app/addons/auth/test/baseSpec.js                   | 100 ---------
 app/addons/cluster/routes.js                       |   2 +
 app/addons/config/routes.js                        |   1 +
 .../tests/nightwatch/checkDatabaseTooltip.js       |   2 +-
 .../tests/nightwatch/selectDocViaTypeahead.js      |   2 -
 app/addons/fauxton/appwrapper.js                   |  65 ++++--
 app/addons/fauxton/assets/less/fauxton.less        |   1 +
 app/addons/fauxton/assets/less/navigation.less     | 202 ++++++++++++++++++
 app/addons/fauxton/base.js                         |   1 -
 .../__tests__/burger-test.js}                      |  32 ++-
 .../navigation/__tests__/login-logout-test.js      |  80 ++++++++
 .../fauxton/navigation/__tests__/navbar-test.js    |  43 ++++
 .../navigation-store-test.js}                      | 109 +++-------
 .../fauxton/navigation/__tests__/navlink-test.js   |  48 +++++
 app/addons/fauxton/navigation/actiontypes.js       |   5 +-
 app/addons/fauxton/navigation/components.react.jsx | 155 --------------
 .../{actiontypes.js => components/Brand.js}        |  33 ++-
 app/addons/fauxton/navigation/components/Burger.js |  41 ++++
 .../{actiontypes.js => components/Footer.js}       |  30 ++-
 .../fauxton/navigation/components/LoginButton.js   |  42 ++++
 .../fauxton/navigation/components/LogoutButton.js  |  47 +++++
 app/addons/fauxton/navigation/components/NavBar.js | 115 +++++++++++
 .../fauxton/navigation/components/NavLink.js       |  51 +++++
 app/addons/fauxton/navigation/container/NavBar.js  |  68 ++++++
 app/addons/fauxton/navigation/stores.js            |  37 +++-
 .../navigation/tests/componentsSpec.react.jsx      |  61 ------
 .../fauxton/tests/nightwatch/highlightsidebar.js   |   2 +-
 .../fauxton/tests/nightwatch/notificationCenter.js |   1 +
 app/app.js                                         |  19 ++
 app/constants.js                                   |   4 -
 assets/less/fauxton.less                           |  17 +-
 assets/less/templates.less                         | 228 ---------------------
 assets/less/variables.less                         |   7 +
 package.json                                       |   7 +-
 settings.json.default.json                         |  12 +-
 webpack.config.dev.js                              |  10 +-
 webpack.config.release.js                          |  10 +-
 40 files changed, 1081 insertions(+), 746 deletions(-)

diff --git a/app/addons/auth/__tests__/base.test.js b/app/addons/auth/__tests__/base.test.js
new file mode 100644
index 0000000..ae56b16
--- /dev/null
+++ b/app/addons/auth/__tests__/base.test.js
@@ -0,0 +1,83 @@
+// 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 Auth from "../../../core/auth";
+import Base from "../base";
+import sinon from "sinon";
+
+
+describe("Auth", function () {
+  FauxtonAPI.auth = new Auth();
+  Base.initialize();
+
+  describe("failed login", function () {
+
+    it("redirects with replace: true set", function () {
+      const navigateSpy = sinon.spy(FauxtonAPI, 'navigate');
+      FauxtonAPI.router.trigger = () => {};
+      FauxtonAPI.session.isLoggedIn = function () { return false; };
+      FauxtonAPI.auth.authDeniedCb();
+      expect(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce).toBeTruthy();
+      FauxtonAPI.navigate.restore();
+    });
+  });
+
+  describe('auth session change', function () {
+
+    afterEach(function () {
+      FauxtonAPI.addHeaderLink.restore && FauxtonAPI.addHeaderLink.restore();
+      FauxtonAPI.session.isLoggedIn.restore && FauxtonAPI.session.isLoggedIn.restore();
+      FauxtonAPI.session.isAdminParty.restore();
+    });
+
+    it('for admin party changes title to admin party', function () {
+      const spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
+      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true);
+      FauxtonAPI.session.trigger('change');
+
+      expect(spy.calledOnce).toBeTruthy();
+      const args = spy.getCall(0).args[0];
+      expect(args.title).toMatch("Admin Party!");
+    });
+
+    it('for login changes title to Your Account', function () {
+      var spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
+      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
+      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
+      FauxtonAPI.session.trigger('change');
+
+      expect(spy.calledOnce).toBeTruthy();
+      var args = spy.getCall(0).args[0];
+      expect(args.title).toMatch("Your Account");
+    });
+
+    it('for login adds logout link', function () {
+      var spy = sinon.spy(FauxtonAPI, 'showLogout');
+      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
+      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
+      FauxtonAPI.session.trigger('change');
+
+      expect(spy.calledOnce).toBeTruthy();
+      FauxtonAPI.showLogout.restore();
+    });
+
+    it('for logout, removes logout link', function () {
+      var spy = sinon.spy(FauxtonAPI, 'showLogin');
+      sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
+      sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false);
+      FauxtonAPI.session.trigger('change');
+
+      expect(spy.calledOnce).toBeTruthy();
+      FauxtonAPI.showLogin.restore();
+    });
+  });
+});
diff --git a/app/addons/auth/base.js b/app/addons/auth/base.js
index 237a450..c7c32c7 100644
--- a/app/addons/auth/base.js
+++ b/app/addons/auth/base.js
@@ -19,19 +19,16 @@ Auth.session = new Auth.Session();
 FauxtonAPI.setSession(Auth.session);
 app.session = Auth.session;
 
-Auth.initialize = function () {
 
-  FauxtonAPI.addHeaderLink({
-    id: 'auth',
-    title: 'Login',
-    href: '#/login',
-    icon: 'fonticon-user',
-    bottomNav: true
-  });
+function cleanupAuthSection () {
+  FauxtonAPI.removeHeaderLink({ id: 'auth', bottomNav: true });
+}
+
+Auth.initialize = function () {
 
   Auth.session.on('change', function () {
-    var session = Auth.session;
-    var link = {};
+    const session = Auth.session;
+    let link;
 
     if (session.isAdminParty()) {
       link = {
@@ -41,36 +38,28 @@ Auth.initialize = function () {
         icon: 'fonticon-user',
         bottomNav: true
       };
+
+      cleanupAuthSection();
+      FauxtonAPI.addHeaderLink(link);
+      FauxtonAPI.hideLogin();
+
     } else if (session.isLoggedIn()) {
       link = {
         id: 'auth',
-        title: session.user().name,
+        title: 'Your Account',
         href: '#/changePassword',
         icon: 'fonticon-user',
         bottomNav: true
       };
 
-      // ensure the footer link is removed before adding it
-      FauxtonAPI.removeHeaderLink({ id: 'logout', footerNav: true });
-      FauxtonAPI.addHeaderLink({
-        id: 'logout',
-        footerNav: true,
-        href: '#logout',
-        title: 'Logout',
-        icon: '',
-        className: 'logout'
-      });
+      cleanupAuthSection();
+      FauxtonAPI.addHeaderLink(link);
+      FauxtonAPI.showLogout();
     } else {
-      link = {
-        id: 'auth',
-        title: 'Login',
-        href: '#/login',
-        icon: 'fonticon-user',
-        bottomNav: true
-      };
-      FauxtonAPI.removeHeaderLink({ id: 'logout', footerNav: true });
+      cleanupAuthSection();
+      FauxtonAPI.showLogin();
     }
-    FauxtonAPI.updateHeaderLink(link);
+
   });
 
   Auth.session.fetchUser().then(function () {
diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js
index b4a0e68..1225619 100644
--- a/app/addons/auth/routes.js
+++ b/app/addons/auth/routes.js
@@ -65,6 +65,7 @@ var AuthRouteObject = FauxtonAPI.RouteObject.extend({
 var UserRouteObject = FauxtonAPI.RouteObject.extend({
   hideNotificationCenter: true,
   hideApiBar: true,
+  selectedHeader: 'Your Account',
 
   routes: {
     'changePassword': {
@@ -93,10 +94,6 @@ var UserRouteObject = FauxtonAPI.RouteObject.extend({
     ClusterActions.navigateToNodeBasedOnNodeCount('/addAdmin/');
   },
 
-  selectedHeader: function () {
-    return FauxtonAPI.session.user().name;
-  },
-
   changePassword: function () {
     ClusterActions.fetchNodes();
     AuthActions.selectPage('changePassword');
diff --git a/app/addons/auth/test/baseSpec.js b/app/addons/auth/test/baseSpec.js
deleted file mode 100644
index c566287..0000000
--- a/app/addons/auth/test/baseSpec.js
+++ /dev/null
@@ -1,100 +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 Base from "../base";
-import Auth from "../../../core/auth";
-import testUtils from "../../../../test/mocha/testUtils";
-import sinon from "sinon";
-var assert = testUtils.assert;
-
-describe("Auth: Login", function () {
-
-  describe("failed login", function () {
-
-    it("redirects with replace: true set", function () {
-      var navigateSpy = sinon.spy(FauxtonAPI, 'navigate');
-      FauxtonAPI.auth = new Auth();
-      FauxtonAPI.session.isLoggedIn = function () { return false; };
-      Base.initialize();
-      FauxtonAPI.auth.authDeniedCb();
-      assert.ok(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce);
-    });
-  });
-});
-
-describe('auth session change', function () {
-
-  afterEach(function () {
-    FauxtonAPI.updateHeaderLink.restore && FauxtonAPI.updateHeaderLink.restore();
-    FauxtonAPI.session.isAdminParty.restore && FauxtonAPI.session.isAdminParty.restore();
-  });
-
-  it('for admin party changes title to admin party', function () {
-    var spy = sinon.spy(FauxtonAPI, 'updateHeaderLink');
-    var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true);
-    FauxtonAPI.session.trigger('change');
-
-    assert.ok(spy.calledOnce);
-    var args = spy.getCall(0).args[0];
-
-    assert.ok(args.title.match(/Admin Party/));
-    FauxtonAPI.session.isAdminParty.restore();
-  });
-
-  it('for login changes title to login', function () {
-    var spy = sinon.spy(FauxtonAPI, 'updateHeaderLink');
-    var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-    sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'});
-    sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-    FauxtonAPI.session.trigger('change');
-
-    assert.ok(spy.calledOnce);
-    var args = spy.getCall(0).args[0];
-
-    assert.equal(args.title, 'test-user');
-    FauxtonAPI.session.isLoggedIn.restore();
-    FauxtonAPI.session.user.restore();
-    FauxtonAPI.session.isAdminParty.restore();
-  });
-
-  it('for login adds logout link', function () {
-    var spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
-    var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-    sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'});
-    sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-    FauxtonAPI.session.trigger('change');
-
-    assert.ok(spy.calledOnce);
-    var args = spy.getCall(0).args[0];
-
-    assert.equal(args.title, 'Logout');
-    FauxtonAPI.session.isLoggedIn.restore();
-    FauxtonAPI.session.user.restore();
-    FauxtonAPI.session.isAdminParty.restore();
-  });
-
-  it('for logout, removes logout link', function () {
-    var spy = sinon.spy(FauxtonAPI, 'removeHeaderLink');
-    var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-    sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false);
-    FauxtonAPI.session.trigger('change');
-
-    assert.ok(spy.calledOnce);
-    var args = spy.getCall(0).args[0];
-
-    assert.equal(args.id, 'logout');
-    FauxtonAPI.session.isLoggedIn.restore();
-    FauxtonAPI.session.isAdminParty.restore();
-  });
-
-
-});
diff --git a/app/addons/cluster/routes.js b/app/addons/cluster/routes.js
index 591541f..c4b924d 100644
--- a/app/addons/cluster/routes.js
+++ b/app/addons/cluster/routes.js
@@ -19,6 +19,8 @@ import {OnePaneSimpleLayout} from '../components/layouts';
 
 
 var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({
+  selectedHeader: 'Configuration',
+
   routes: {
     'cluster/disabled': 'showDisabledFeatureScreen'
   },
diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js
index e1dadb7..370d126 100644
--- a/app/addons/config/routes.js
+++ b/app/addons/config/routes.js
@@ -20,6 +20,7 @@ import Layout from './layout';
 
 
 var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({
+  selectedHeader: 'Configuration',
 
   routes: {
     '_config': 'checkNodes',
diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
index d2c6606..e5b0353 100644
--- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
+++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
@@ -38,7 +38,7 @@ module.exports = {
 
       .checkForStringPresent(newDatabaseName, '"doc_del_count":1')
 
-      .clickWhenVisible('#nav-links a[href="#/_all_dbs"]')
+      .clickWhenVisible('#primary-navbar a[href="#/_all_dbs"]')
       // now let's look at the actual UI to confirm the tooltip appears
       .waitForElementPresent('.js-db-graveyard', waitTime, false)
       .moveToElement('.js-db-graveyard', 1, 1)
diff --git a/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js b/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js
index b96940e..0a083f2 100644
--- a/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js
+++ b/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js
@@ -27,7 +27,6 @@ module.exports = {
       .keys(['\uE00C'])
       .waitForElementPresent('.prettyprint', waitTime, false)
       .waitForElementPresent('.documents-pagination', waitTime, false)
-      .click('.burger')
       // we need to explicitly show the doc field because it's hidden on Travis due to screen width
       .execute("$('.searchbox-wrapper').show();")
       .setValue('.jump-to-doc .Select-input input', ['_des'])
@@ -50,7 +49,6 @@ module.exports = {
       .keys(['\uE00C'])
       .waitForElementPresent('.prettyprint', waitTime, false)
       .waitForElementPresent('.documents-pagination', waitTime, false)
-      .click('.burger')
       // we need to explicitly show the doc field because it's hidden on Travis due to screen width
       .execute("$('.searchbox-wrapper').show();")
       .setValue('.jump-to-doc .Select-input input', ['MY_CAP'])
diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js
index f377bbc..573521b 100644
--- a/app/addons/fauxton/appwrapper.js
+++ b/app/addons/fauxton/appwrapper.js
@@ -12,8 +12,12 @@
 
 import React from 'react';
 import {NotificationController, PermanentNotification} from "./notifications/notifications.react";
-import {NavBar} from './navigation/components.react';
+import NavBar from './navigation/container/NavBar';
 import NavbarActions from './navigation/actions';
+import Stores from './navigation/stores';
+import classNames from 'classnames';
+
+const navBarStore = Stores.navBarStore;
 
 class ContentWrapper extends React.Component {
   constructor(props) {
@@ -49,27 +53,52 @@ class ContentWrapper extends React.Component {
   }
 }
 
-const App = ({router}) => {
-  return (
-    <div>
-      <PermanentNotification />
-      <div id="notifications">
-        <NotificationController />
-      </div>
-      <div role="main" id="main">
-        <div id="app-container">
-          <div className="wrapper">
-            <div className="pusher">
-              <ContentWrapper router={router} />
-            </div>
-            <div id="primary-navbar">
-              <NavBar/>
+class App extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = this.getStoreState();
+  }
+
+  getStoreState () {
+    return {
+      isPrimaryNavMinimized: navBarStore.isMinimized()
+    };
+  }
+
+  componentDidMount () {
+    navBarStore.on('change', this.onChange, this);
+  }
+
+  onChange () {
+    this.setState(this.getStoreState());
+  }
+
+  render () {
+    const mainClass = classNames(
+      {'closeMenu': this.state.isPrimaryNavMinimized}
+    );
+
+    return (
+      <div>
+        <PermanentNotification />
+        <div id="notifications">
+          <NotificationController />
+        </div>
+        <div role="main" id="main"  className={mainClass}>
+          <div id="app-container">
+            <div className="wrapper">
+              <div className="pusher">
+                <ContentWrapper router={this.props.router} />
+              </div>
+              <div id="primary-navbar">
+                <NavBar/>
+              </div>
             </div>
           </div>
         </div>
       </div>
-    </div>
-  );
+    );
+  }
 };
 
 export default App;
diff --git a/app/addons/fauxton/assets/less/fauxton.less b/app/addons/fauxton/assets/less/fauxton.less
index 255589d..c248f69 100644
--- a/app/addons/fauxton/assets/less/fauxton.less
+++ b/app/addons/fauxton/assets/less/fauxton.less
@@ -1 +1,2 @@
 @import "components.less";
+@import "navigation.less";
diff --git a/app/addons/fauxton/assets/less/navigation.less b/app/addons/fauxton/assets/less/navigation.less
new file mode 100644
index 0000000..1493c62
--- /dev/null
+++ b/app/addons/fauxton/assets/less/navigation.less
@@ -0,0 +1,202 @@
+.faux-navbar {
+  margin-top: 64px;
+  background-color: @brandDark2;
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  z-index: 5;
+  overflow: scroll;
+}
+
+.faux-navbar nav {
+  height: 100%;
+}
+
+.faux-navbar__linkcontainer {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  height: 100%;
+}
+
+.faux-navbar__itemarea {
+  .box-sizing(border-box);
+  border-bottom: 1px solid @brandDark2;
+  height: 48px;
+  padding: 10px 20px;
+  line-height: 24px;
+}
+
+.faux-navbar__version-footer {
+  color: @buttonText;
+  font-size: 10px;
+  text-align: center;
+  background-color: @brandDark2;
+  padding-bottom: 10px;
+}
+
+.faux-navbar__burger {
+  background-color: @brandDark2;
+  padding: 19px 0 18px 18px;
+  position: fixed;
+  z-index: 100;
+  top: 0;
+}
+
+.faux-navbar--narrow {
+  width: @collapsedNavWidth;
+}
+
+.faux-navbar--wide {
+  width: @navWidth;
+}
+
+.faux-navbar__burger:hover .faux-navbar__burger__icon {
+  color: @navIconActive;
+}
+
+.faux-navbar__burger__icon {
+  color: @navIconColor;
+  font-size: 27px;
+}
+
+.faux-navbar__burger__icon--flipped:before{
+  -moz-transform: scale(-1, 1);
+  -webkit-transform: scale(-1, 1);
+  -o-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+
+.faux-navbar__link, .faux-logout__link, .faux-login__link {
+  display: block;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.faux-navbar__link--active {
+  background-color: @brandHighlight;
+  text-decoration: none;
+}
+
+.faux-navbar__link--inactive {
+  background-color: @brandDark1;
+}
+
+.faux-navbar__link:hover, .faux-logout__link:hover, .faux-login__link:hover {
+  background-color: @hoverHighlight;
+  text-decoration: none;
+}
+
+.faux-navbar__link:active {
+  text-decoration: none;
+}
+
+.faux-navbar__link:hover .faux-navbar__icon:before {
+  color: @navIconActive;
+}
+
+.faux-navbar__link--active .faux-navbar__icon:before {
+  color: @navIconActive;
+}
+
+.faux-navbar__icon {
+  margin-right: 14px;
+  color: @navIconColor;
+  font-size: 24px;
+  vertical-align: middle;
+}
+
+.faux-navbar__text {
+  margin: 0;
+  color: @buttonText;
+  vertical-align: middle;
+  font-size: 16px;
+  font-weight: normal;
+  font-family: Helvetica,sans-serif;
+  font-weight: 400;
+}
+
+.faux-navbar__logout__text {
+  font-size: 12px;
+  color: @buttonText;
+}
+
+.faux-navbar__logout__textcontainer {
+  text-align: center;
+  color: @buttonText;
+}
+
+.faux-navbar__logout__textcontainer--narrow {
+  padding-bottom: 4px;
+  padding: 15px 0;
+}
+
+.faux-navbar__logout__textcontainer--wide {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  padding: 15px;
+}
+
+.faux-navbar__login__textcontainer {
+  text-align: center;
+  color: @buttonText;
+}
+
+.faux-navbar__login__textcontainer--narrow {
+  padding-bottom: 4px;
+  padding: 15px 0;
+}
+
+.faux-navbar__login__textcontainer--wide {
+  padding: 15px;
+}
+
+.faux-navbar__brand {
+  margin: 20px 0 20px 0;
+  height: 50px;
+  padding: 10px 10px 10px 10px;
+  float: none;
+  background: @brandDark2;
+}
+
+.faux-navbar__brand-logo {
+  display: block;
+  height: 100%;
+  margin-top: 10px;
+}
+
+.faux-navbar__brand-logo--wide {
+  background: url(@largeLogoPath) no-repeat 23px 0px;
+  background-size: 150px;
+  width: 200px;
+}
+
+.faux-navbar__brand-logo--narrow {
+  width: 43px;
+  height: 40px;
+  background: url(@smallLogoPath) no-repeat 3px 0;
+  background-size: 40px;
+}
+
+.faux-navbar__footer {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-end;
+  height: 100%;
+  min-height: 200px;
+  width: 100%;
+}
+
+.faux-navbar__logout__username {
+  color: @buttonText;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 150px;
+  white-space: nowrap;
+  padding-right: 5px;
+  padding-left: 10px;
+  font-size: 12px;
+}
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index fa83b97..17812d8 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -19,7 +19,6 @@ import "./assets/less/fauxton.less";
 const Fauxton = FauxtonAPI.addon();
 
 Fauxton.initialize = () => {
-
   const versionInfo = new Fauxton.VersionInfo();
   versionInfo.fetch().then(function () {
     NavigationActions.setNavbarVersionInfo(versionInfo.get("version"));
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/navigation/__tests__/burger-test.js
similarity index 52%
copy from app/addons/fauxton/base.js
copy to app/addons/fauxton/navigation/__tests__/burger-test.js
index fa83b97..5f9640f 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/navigation/__tests__/burger-test.js
@@ -9,27 +9,21 @@
 // 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 Burger from "../components/Burger";
+import React from "react";
+import ReactDOM from "react-dom";
+import sinon from "sinon";
+import {mount} from 'enzyme';
 
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import NavigationActions from "./navigation/actions";
+describe('Navigation Bar', () => {
 
-import "./assets/less/fauxton.less";
+  describe('Burger', () => {
+    it('dispatch TOGGLE_NAVBAR_MENU on click', () => {
+      const toggleMenu = sinon.spy();
+      const burgerEl = mount(<Burger toggleMenu={toggleMenu} isMinimized={false} />);
+      burgerEl.simulate('click');
+      expect(toggleMenu.calledOnce).toBeTruthy();
+    });
 
-const Fauxton = FauxtonAPI.addon();
-
-Fauxton.initialize = () => {
-
-  const versionInfo = new Fauxton.VersionInfo();
-  versionInfo.fetch().then(function () {
-    NavigationActions.setNavbarVersionInfo(versionInfo.get("version"));
   });
-};
-
-Fauxton.VersionInfo = Backbone.Model.extend({
-  url: function () {
-    return app.host;
-  }
 });
-
-export default Fauxton;
diff --git a/app/addons/fauxton/navigation/__tests__/login-logout-test.js b/app/addons/fauxton/navigation/__tests__/login-logout-test.js
new file mode 100644
index 0000000..097e495
--- /dev/null
+++ b/app/addons/fauxton/navigation/__tests__/login-logout-test.js
@@ -0,0 +1,80 @@
+// 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 React from 'react';
+import { mount } from 'enzyme';
+
+import NavBar from '../components/NavBar';
+
+describe('Navigation Bar', () => {
+
+  it('renders with login button when logged out', () => {
+    const props = {
+      activeLink: '',
+      isMinimized: false,
+      version: '42',
+      navLinks: [],
+      bottomNavLinks: [],
+      footerNavLinks: [],
+      isNavBarVisible: true,
+      isLoginSectionVisible: true,
+      isLoginVisibleInsteadOfLogout: true
+    };
+
+    const navBar = mount(<NavBar {...props} />);
+
+    const button = navBar.find('[href="#/login"]');
+    expect(button.text()).toContain('Login');
+  });
+
+  it('renders with logout button when logged in', () => {
+    const props = {
+      activeLink: '',
+      isMinimized: false,
+      version: '42',
+      navLinks: [],
+      bottomNavLinks: [],
+      footerNavLinks: [],
+      username: 'Rocko',
+      isNavBarVisible: true,
+      isLoginSectionVisible: true,
+      isLoginVisibleInsteadOfLogout: false
+    };
+
+    const navBar = mount(<NavBar {...props} />);
+
+    const button = navBar.find('[href="#/logout"]');
+    expect(button.text()).toContain('Log Out');
+  });
+
+  it('Admin Party has no Logout button and no Login button', () => {
+    const props = {
+      activeLink: '',
+      isMinimized: false,
+      version: '42',
+      navLinks: [],
+      bottomNavLinks: [],
+      footerNavLinks: [],
+      username: 'Rocko',
+      isNavBarVisible: true,
+      isLoginSectionVisible: false,
+      isLoginVisibleInsteadOfLogout: false
+    };
+
+    const navBar = mount(<NavBar {...props} />);
+
+    expect(navBar.text()).not.toMatch(/Login/);
+    expect(navBar.text()).not.toMatch(/Log Out/);
+  });
+
+});
diff --git a/app/addons/fauxton/navigation/__tests__/navbar-test.js b/app/addons/fauxton/navigation/__tests__/navbar-test.js
new file mode 100644
index 0000000..7a6331e
--- /dev/null
+++ b/app/addons/fauxton/navigation/__tests__/navbar-test.js
@@ -0,0 +1,43 @@
+// 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 NavBarContainer from "../container/NavBar";
+import FauxtonAPI from "../../../../core/api";
+import ActionTypes from "../actiontypes";
+import React from "react";
+import ReactDOM from "react-dom";
+import {mount} from 'enzyme';
+
+describe('Navigation Bar', () => {
+  FauxtonAPI.session = {
+    user: () => {}
+  };
+
+  it('is displayed by default', () => {
+    const NavBar = mount(<NavBarContainer />);
+    expect(NavBar.find('.faux-navbar').length).toBe(1);
+  });
+
+  it('is dynamically displayed by isNavBarVisible', () => {
+    const NavBar = mount(<NavBarContainer />);
+
+    FauxtonAPI.dispatch({
+      type: ActionTypes.NAVBAR_HIDE
+    });
+    expect(NavBar.find('.faux-navbar').length).toBe(0);
+
+    FauxtonAPI.dispatch({
+      type: ActionTypes.NAVBAR_SHOW
+    });
+    expect(NavBar.find('.faux-navbar').length).toBe(1);
+  });
+
+});
diff --git a/app/addons/fauxton/navigation/tests/storeSpec.js b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js
similarity index 52%
rename from app/addons/fauxton/navigation/tests/storeSpec.js
rename to app/addons/fauxton/navigation/__tests__/navigation-store-test.js
index 778dac1..c664dce 100644
--- a/app/addons/fauxton/navigation/tests/storeSpec.js
+++ b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js
@@ -9,24 +9,21 @@
 // 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 testUtils from "../../../../../test/mocha/testUtils";
 import FauxtonAPI from "../../../../core/api";
 import Stores from "../stores";
-var assert = testUtils.assert;
-var navBarStore = Stores.navBarStore;
+const navBarStore = Stores.navBarStore;
 
-describe('NavBarStore', function () {
-  beforeEach(function () {
+describe('NavBarStore', () => {
+  beforeEach(() => {
     FauxtonAPI.dispatch({
       type: 'CLEAR_NAVBAR_LINK',
     });
 
   });
 
-  describe('add links', function () {
+  describe('add links', () => {
 
-    it('to nav links', function () {
+    it('to nav links', () => {
       var link = {
         id: 'mylink'
       };
@@ -35,10 +32,10 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getNavLinks()[0].id, link.id);
+      expect(navBarStore.getNavLinks()[0].id).toMatch(link.id);
     });
 
-    it('to top nav links', function () {
+    it('to top nav links', () => {
       var link1 = {
         id: 'mylink1'
       };
@@ -58,10 +55,10 @@ describe('NavBarStore', function () {
         link: link2
       });
 
-      assert.equal(navBarStore.getNavLinks()[0].id, link2.id);
+      expect(navBarStore.getNavLinks()[0].id).toMatch(link2.id);
     });
 
-    it('to bottom nav', function () {
+    it('to bottom nav', () => {
       var link = {
         id: 'bottomNav',
         bottomNav: true
@@ -71,10 +68,10 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getBottomNavLinks()[0].id, link.id);
+      expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id);
     });
 
-    it('to top of bottom nav', function () {
+    it('to top of bottom nav', () => {
       var link = {
         id: 'bottomNav',
         bottomNav: true,
@@ -85,10 +82,10 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getBottomNavLinks()[0].id, link.id);
+      expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id);
     });
 
-    it('to footer nav', function () {
+    it('to footer nav', () => {
       var link = {
         id: 'footerNav',
         footerNav: true
@@ -98,12 +95,12 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getFooterNavLinks()[0].id, link.id);
+      expect(navBarStore.getFooterNavLinks()[0].id).toMatch(link.id);
     });
   });
 
-  describe('remove link', function () {
-    it('from nav links', function () {
+  describe('remove link', () => {
+    it('from nav links', () => {
       var link = {
         id: 'remove_link',
       };
@@ -117,11 +114,11 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getNavLinks().length, 0);
+      expect(navBarStore.getNavLinks().length).toBe(0);
     });
 
-    it('remove link from list', function () {
-      function addLink (id) {
+    it('remove link from list', () => {
+      const addLink = (id) => {
         FauxtonAPI.dispatch({
           type: 'ADD_NAVBAR_LINK',
           link: {
@@ -129,8 +126,8 @@ describe('NavBarStore', function () {
             footerNav: true
           }
         });
-      }
-      function removeLink () {
+      };
+      const removeLink = () => {
         FauxtonAPI.dispatch({
           type: 'REMOVE_NAVBAR_LINK',
           link: {
@@ -138,7 +135,7 @@ describe('NavBarStore', function () {
             footerNav: true
           }
         });
-      }
+      };
       addLink('remove_link1');
       addLink('remove_link2');
       addLink('remove_link3');
@@ -147,10 +144,10 @@ describe('NavBarStore', function () {
       removeLink();
       removeLink();
 
-      assert.equal(navBarStore.getFooterNavLinks().length, 2);
+      expect(navBarStore.getFooterNavLinks().length).toBe(2);
     });
 
-    it('from bottom nav links', function () {
+    it('from bottom nav links', () => {
       var link = {
         id: 'remove_link',
         bottomNav: true
@@ -165,10 +162,10 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getBottomNavLinks().length, 0);
+      expect(navBarStore.getBottomNavLinks().length).toBe(0);
     });
 
-    it('from footer nav links', function () {
+    it('from footer nav links', () => {
       var link = {
         id: 'remove_link',
         footerNav: true
@@ -183,12 +180,12 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getFooterNavLinks().length, 0);
+      expect(navBarStore.getFooterNavLinks().length).toBe(0);
     });
   });
 
-  describe('update link', function () {
-    it('for nav links', function () {
+  describe('update link', () => {
+    it('for nav links', () => {
       var link = {
         id: 'update-link',
         title: 'first'
@@ -205,59 +202,19 @@ describe('NavBarStore', function () {
         link: link
       });
 
-      assert.equal(navBarStore.getNavLinks()[0].title, 'second');
+      expect(navBarStore.getNavLinks()[0].title).toMatch('second');
     });
 
   });
 
-  describe('set version', function () {
-    it('stores version number', function () {
+  describe('set version', () => {
+    it('stores version number', () => {
       FauxtonAPI.dispatch({
         type: 'NAVBAR_SET_VERSION_INFO',
         version: 1234
       });
 
-      assert.equal(navBarStore.getVersion(), 1234);
-    });
-
-  });
-
-  describe('is Minimized', function () {
-
-    it('returns true if localstorage is true', function () {
-      app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, true);
-      assert.ok(navBarStore.isMinimized());
-    });
-
-    it('returns false if localstorage is false', function () {
-      app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, false);
-      assert.notOk(navBarStore.isMinimized(), false);
-    });
-
-    it('returns false if localstorage is undefined', function () {
-      window.localStorage.removeItem(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED);
-      assert.notOk(navBarStore.isMinimized(), false);
-    });
-  });
-
-  describe('toggleMenu', function () {
-
-    it('that is minimized changes to false', function () {
-      app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, true);
-      navBarStore.toggleMenu();
-      assert.notOk(navBarStore.isMinimized());
-    });
-
-    it('that is not minimized changes to true', function () {
-      app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, false);
-      navBarStore.toggleMenu();
-      assert.ok(navBarStore.isMinimized());
-    });
-
-    it('that is undefined changes to true', function () {
-      window.localStorage.removeItem(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED);
-      navBarStore.toggleMenu();
-      assert.ok(navBarStore.isMinimized());
+      expect(navBarStore.getVersion()).toBe(1234);
     });
 
   });
diff --git a/app/addons/fauxton/navigation/__tests__/navlink-test.js b/app/addons/fauxton/navigation/__tests__/navlink-test.js
new file mode 100644
index 0000000..8201f4e
--- /dev/null
+++ b/app/addons/fauxton/navigation/__tests__/navlink-test.js
@@ -0,0 +1,48 @@
+// 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 NavLink from "../components/NavLink";
+import React from "react";
+import ReactDOM from "react-dom";
+import {mount} from 'enzyme';
+
+describe('Navigation Bar', () => {
+  const dbLink = {
+    href: "#/_all_dbs",
+    title: "Databases",
+    icon: "fonticon-database",
+    className: 'databases'
+  };
+
+  describe('Active Link', () => {
+    it('matching title sets active css class', () => {
+      const linkEl = mount(<NavLink link={dbLink} active={"Databases"} isMinimized={false} />);
+      expect(linkEl.find('a.faux-navbar__link--active').length).toBe(1);
+    });
+
+    it('different title sets inactive css class', () => {
+      const linkEl = mount(<NavLink link={dbLink} active={"Replication"} isMinimized={false} />);
+      expect(linkEl.find('a.faux-navbar__link--inactive').length).toBe(1);
+    });
+  });
+
+  describe('Minimized Link', () => {
+    it('shows title when not minimized', () => {
+      const linkEl = mount(<NavLink link={dbLink} active={"Databases"} isMinimized={false} />);
+      expect(linkEl.text()).toMatch("Databases");
+    });
+
+    it('does not show title when minimized', () => {
+      const linkEl = mount(<NavLink link={dbLink} active={"Databases"} isMinimized={true} />);
+      expect(linkEl.find('span.faux-navbar__text').length).toBe(0);
+    });
+  });
+});
diff --git a/app/addons/fauxton/navigation/actiontypes.js b/app/addons/fauxton/navigation/actiontypes.js
index b5515bd..e5f685f 100644
--- a/app/addons/fauxton/navigation/actiontypes.js
+++ b/app/addons/fauxton/navigation/actiontypes.js
@@ -19,5 +19,8 @@ export default {
   NAVBAR_SET_VERSION_INFO: 'NAVBAR_SET_VERSION_INFO',
   NAVBAR_ACTIVE_LINK: 'NAVBAR_ACTIVE_LINK',
   NAVBAR_HIDE: 'NAVBAR_HIDE',
-  NAVBAR_SHOW: 'NAVBAR_SHOW'
+  NAVBAR_SHOW: 'NAVBAR_SHOW',
+  NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION',
+  NAVBAR_SHOW_LOGIN_BUTTON: 'NAVBAR_SHOW_LOGIN_BUTTON',
+  NAVBAR_SHOW_LOGOUT_BUTTON: 'NAVBAR_SHOW_LOGOUT_BUTTON'
 };
diff --git a/app/addons/fauxton/navigation/components.react.jsx b/app/addons/fauxton/navigation/components.react.jsx
deleted file mode 100644
index a269726..0000000
--- a/app/addons/fauxton/navigation/components.react.jsx
+++ /dev/null
@@ -1,155 +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 React from "react";
-import ReactDOM from "react-dom";
-import Stores from "./stores";
-import Actions from "./actions";
-const navBarStore = Stores.navBarStore;
-
-const Footer = React.createClass({
-  render () {
-    const version = this.props.version;
-
-    if (!version) { return null; }
-    return (
-      <div className="version-footer">
-        Fauxton on {" "}
-        <a href="http://couchdb.apache.org/">Apache CouchDB</a>
-        {" "} v. {version}
-      </div>
-    );
-  }
-});
-
-const Burger = React.createClass({
-  render () {
-    return (
-      <div className="burger" onClick={this.props.toggleMenu}>
-        <div></div>
-        <div></div>
-        <div></div>
-      </div>
-    );
-  }
-});
-
-const NavLink = React.createClass({
-  render () {
-    const link = this.props.link;
-    const liClassName = this.props.active === link.title ? 'active' : '';
-
-    return (
-      <li data-nav-name={link.title} className={liClassName} >
-        <a href={link.href} target={link.target ? '_blank' : null} data-bypass={link.target ? 'true' : null}>
-          <i className={link.icon + " fonticon "}></i>
-          <span dangerouslySetInnerHTML={{__html: link.title }} />
-        </a>
-      </li>
-    );
-  }
-});
-
-export const NavBar = React.createClass({
-  getStoreState () {
-    return {
-      navLinks: navBarStore.getNavLinks(),
-      bottomNavLinks: navBarStore.getBottomNavLinks(),
-      footerNavLinks: navBarStore.getFooterNavLinks(),
-      activeLink: navBarStore.getActiveLink(),
-      version: navBarStore.getVersion(),
-      isMinimized: navBarStore.isMinimized(),
-      isNavBarVisible: navBarStore.isNavBarVisible()
-    };
-  },
-
-  getInitialState () {
-    return this.getStoreState();
-  },
-
-  createLinks (links) {
-    return _.map(links, function (link, i) {
-      return <NavLink key={i} link={link} active={this.state.activeLink} />;
-    }, this);
-  },
-
-  onChange () {
-    this.setState(this.getStoreState());
-  },
-
-  setMenuState () {
-    $('body').toggleClass('closeMenu', this.state.isMinimized);
-    FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.NAVBAR_SIZE_CHANGED, this.state.isMinimized);
-  },
-
-  componentDidMount () {
-    navBarStore.on('change', this.onChange, this);
-    this.setMenuState();
-  },
-
-  componentDidUpdate () {
-    this.setMenuState();
-  },
-
-  componentWillUnmount () {
-    navBarStore.off('change', this.onChange);
-  },
-
-  toggleMenu () {
-    Actions.toggleNavbarMenu();
-  },
-
-  render () {
-    //YUCK!! but we can only really fix this once we have removed all backbone
-    if (!this.state.isNavBarVisible) {
-      $('#primary-navbar').hide();
-      return null;
-    }
-
-    $('#primary-navbar').show();
-
-    const navLinks = this.createLinks(this.state.navLinks);
-    const bottomNavLinks = this.createLinks(this.state.bottomNavLinks);
-    const footerNavLinks = this.createLinks(this.state.footerNavLinks);
-
-    return (
-      <div className="navbar">
-        <Burger toggleMenu={this.toggleMenu}/>
-        <nav id="main_navigation">
-          <ul id="nav-links" className="nav">
-            {navLinks}
-            {bottomNavLinks}
-          </ul>
-        </nav>
-        <div id="primary-nav-right-shadow"/>
-
-        <div className="bottom-container">
-          <div className="brand">
-            <div className="icon">Apache Fauxton</div>
-          </div>
-
-          <Footer version={this.state.version}/>
-          <div id="footer-links">
-            <ul id="footer-nav-links" className="nav">
-              {footerNavLinks}
-            </ul>
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
-
-export default {
-  NavBar: NavBar,
-  Burger: Burger
-};
diff --git a/app/addons/fauxton/navigation/actiontypes.js b/app/addons/fauxton/navigation/components/Brand.js
similarity index 53%
copy from app/addons/fauxton/navigation/actiontypes.js
copy to app/addons/fauxton/navigation/components/Brand.js
index b5515bd..e8751b0 100644
--- a/app/addons/fauxton/navigation/actiontypes.js
+++ b/app/addons/fauxton/navigation/components/Brand.js
@@ -10,14 +10,27 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-export default {
-  ADD_NAVBAR_LINK: 'ADD_NAVBAR_LINK',
-  TOGGLE_NAVBAR_MENU: 'TOGGLE_NAVBAR_MENU',
-  UPDATE_NAVBAR_LINK: 'UPDATE_NAVBAR_LINK',
-  CLEAR_NAVBAR_LINK: 'CLEAR_NAVBAR_LINK',
-  REMOVE_NAVBAR_LINK: 'REMOVE_NAVBAR_LINK',
-  NAVBAR_SET_VERSION_INFO: 'NAVBAR_SET_VERSION_INFO',
-  NAVBAR_ACTIVE_LINK: 'NAVBAR_ACTIVE_LINK',
-  NAVBAR_HIDE: 'NAVBAR_HIDE',
-  NAVBAR_SHOW: 'NAVBAR_SHOW'
+import React from 'react';
+
+import classNames from 'classnames';
+
+const Brand = ({isMinimized}) => {
+
+  const burgerClasses = classNames(
+    'faux-navbar__brand-logo',
+    {'faux-navbar__brand-logo--wide':  !isMinimized},
+    {'faux-navbar__brand-logo--narrow': isMinimized}
+  );
+
+  return (
+    <div className="faux-navbar__brand">
+      <div className={burgerClasses}></div>
+    </div>
+  );
 };
+
+Brand.propTypes = {
+  isMinimized: React.PropTypes.bool.isRequired
+};
+
+export default Brand;
diff --git a/app/addons/fauxton/navigation/components/Burger.js b/app/addons/fauxton/navigation/components/Burger.js
new file mode 100644
index 0000000..fc9c357
--- /dev/null
+++ b/app/addons/fauxton/navigation/components/Burger.js
@@ -0,0 +1,41 @@
+// 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 React from 'react';
+
+import classNames from 'classnames';
+
+const Burger = ({toggleMenu, isMinimized}) => {
+
+  const burgerClasses = classNames(
+    'faux-navbar__burger',
+    {'faux-navbar--wide':  !isMinimized},
+    {'faux-navbar--narrow': isMinimized}
+  );
+
+  const icon = isMinimized ?
+    'icon-resize-horizontal' :
+    'icon-signin faux-navbar__burger__icon--flipped';
+
+  return (
+    <div className={burgerClasses} onClick={toggleMenu}>
+      <i className={"faux-navbar__burger__icon " + icon}></i>
+    </div>
+  );
+};
+
+Burger.propTypes = {
+  toggleMenu: React.PropTypes.func.isRequired,
+  isMinimized: React.PropTypes.bool.isRequired
+};
+
+export default Burger;
diff --git a/app/addons/fauxton/navigation/actiontypes.js b/app/addons/fauxton/navigation/components/Footer.js
similarity index 59%
copy from app/addons/fauxton/navigation/actiontypes.js
copy to app/addons/fauxton/navigation/components/Footer.js
index b5515bd..72ddd01 100644
--- a/app/addons/fauxton/navigation/actiontypes.js
+++ b/app/addons/fauxton/navigation/components/Footer.js
@@ -10,14 +10,24 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-export default {
-  ADD_NAVBAR_LINK: 'ADD_NAVBAR_LINK',
-  TOGGLE_NAVBAR_MENU: 'TOGGLE_NAVBAR_MENU',
-  UPDATE_NAVBAR_LINK: 'UPDATE_NAVBAR_LINK',
-  CLEAR_NAVBAR_LINK: 'CLEAR_NAVBAR_LINK',
-  REMOVE_NAVBAR_LINK: 'REMOVE_NAVBAR_LINK',
-  NAVBAR_SET_VERSION_INFO: 'NAVBAR_SET_VERSION_INFO',
-  NAVBAR_ACTIVE_LINK: 'NAVBAR_ACTIVE_LINK',
-  NAVBAR_HIDE: 'NAVBAR_HIDE',
-  NAVBAR_SHOW: 'NAVBAR_SHOW'
+import React from 'react';
+
+const Footer = ({version}) => {
+
+  if (!version) { return null; }
+
+  return (
+    <div className="faux-navbar__version-footer">
+      Fauxton on &nbsp;
+      <a href="http://couchdb.apache.org/">Apache CouchDB</a>
+      <div>v. {version}</div>
+    </div>
+  );
+
 };
+
+Footer.propTypes = {
+  version: React.PropTypes.string
+};
+
+export default Footer;
diff --git a/app/addons/fauxton/navigation/components/LoginButton.js b/app/addons/fauxton/navigation/components/LoginButton.js
new file mode 100644
index 0000000..6493901
--- /dev/null
+++ b/app/addons/fauxton/navigation/components/LoginButton.js
@@ -0,0 +1,42 @@
+// 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 React from 'react';
+
+import classNames from 'classnames';
+
+const LoginButton = ({isMinimized}) => {
+
+  const containerClasses = classNames(
+    'faux-navbar__login__textcontainer',
+    {'faux-navbar__login__textcontainer--narrow':  isMinimized},
+    {'faux-navbar__login__textcontainer--wide': !isMinimized}
+  );
+
+  return (
+    <a className="faux-login__link" href="#/login">
+
+      <div className={containerClasses}>
+        <span className="faux-navbar__logout__text">
+          Login
+        </span>
+      </div>
+
+    </a>
+  );
+};
+
+LoginButton.propTypes = {
+  isMinimized: React.PropTypes.bool.isRequired
+};
+
+export default LoginButton;
diff --git a/app/addons/fauxton/navigation/components/LogoutButton.js b/app/addons/fauxton/navigation/components/LogoutButton.js
new file mode 100644
index 0000000..037f172
--- /dev/null
+++ b/app/addons/fauxton/navigation/components/LogoutButton.js
@@ -0,0 +1,47 @@
+// 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 React from 'react';
+
+import classNames from 'classnames';
+import _ from 'lodash';
+
+const LogoutButton = ({username, isMinimized}) => {
+
+  const containerClasses = classNames(
+    'faux-navbar__logout__textcontainer',
+    {'faux-navbar__logout__textcontainer--narrow':  isMinimized},
+    {'faux-navbar__logout__textcontainer--wide': !isMinimized}
+  );
+
+  return (
+    <a className="faux-logout__link" href="#/logout">
+      <div className={containerClasses}>
+        <span className="faux-navbar__logout__text">
+          Log Out
+        </span>
+        &nbsp;
+        {isMinimized ?
+          null :
+          <span className="faux-navbar__logout__username">{_.escape(username)}</span>
+        }
+      </div>
+    </a>
+  );
+};
+
+LogoutButton.propTypes = {
+  username: React.PropTypes.string,
+  isMinimized: React.PropTypes.bool.isRequired
+};
+
+export default LogoutButton;
diff --git a/app/addons/fauxton/navigation/components/NavBar.js b/app/addons/fauxton/navigation/components/NavBar.js
new file mode 100644
index 0000000..3a83a11
--- /dev/null
+++ b/app/addons/fauxton/navigation/components/NavBar.js
@@ -0,0 +1,115 @@
+// 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 React, { Component } from 'react';
+
+import Footer from './Footer';
+import Burger from './Burger';
+import NavLink from './NavLink';
+import Brand from './Brand';
+import LogoutButton from './LogoutButton';
+import LoginButton from './LoginButton';
+
+import Actions from "../actions";
+
+import classNames from 'classnames';
+
+class NavBar extends Component {
+
+  createLinks (links) {
+    const { activeLink, isMinimized } = this.props;
+
+    return links.map((link, i) => {
+      return <NavLink
+        key={i}
+        link={link}
+        active={activeLink}
+        isMinimized={isMinimized} />;
+    });
+  }
+
+  toggleMenu () {
+    Actions.toggleNavbarMenu();
+  }
+
+  render () {
+    const {
+      isMinimized,
+      version,
+      isLoginSectionVisible,
+      isLoginVisibleInsteadOfLogout,
+      activeLink,
+      username,
+      isNavBarVisible
+    } = this.props;
+
+    if (!isNavBarVisible) {
+      return null;
+    }
+
+    const navLinks = this.createLinks(this.props.navLinks);
+    const bottomNavLinks = this.createLinks(this.props.bottomNavLinks);
+    const footerNavLinks = this.createLinks(this.props.footerNavLinks);
+
+    const navClasses = classNames(
+      'faux-navbar',
+      {'faux-navbar--wide':  !isMinimized},
+      {'faux-navbar--narrow': isMinimized}
+    );
+
+    const loginSection = isLoginVisibleInsteadOfLogout ?
+      <LoginButton active={activeLink} isMinimized={isMinimized} /> :
+      <LogoutButton username={username} isMinimized={isMinimized} />;
+
+    return (
+      <div className={navClasses}>
+        <nav>
+          <Burger isMinimized={isMinimized} toggleMenu={this.toggleMenu}/>
+          <div className="faux-navbar__linkcontainer">
+            <div className="faux-navbar__links">
+              {navLinks}
+              {bottomNavLinks}
+            </div>
+
+            <div className="faux-navbar__footer">
+              <Brand isMinimized={isMinimized} />
+
+              <div>
+                {footerNavLinks}
+              </div>
+
+              <Footer version={version}/>
+
+              {isLoginSectionVisible ? loginSection : null}
+            </div>
+          </div>
+        </nav>
+        <div id="primary-nav-right-shadow"/>
+      </div>
+    );
+  }
+}
+
+NavBar.propTypes = {
+  activeLink: React.PropTypes.string,
+  isMinimized: React.PropTypes.bool.isRequired,
+  version: React.PropTypes.string,
+  username: React.PropTypes.string,
+  navLinks: React.PropTypes.array,
+  bottomNavLinks: React.PropTypes.array,
+  footerNavLinks: React.PropTypes.array,
+  isNavBarVisible: React.PropTypes.bool,
+  isLoginSectionVisible: React.PropTypes.bool.isRequired,
+  isLoginVisibleInsteadOfLogout: React.PropTypes.bool.isRequired
+};
+
+export default NavBar;
diff --git a/app/addons/fauxton/navigation/components/NavLink.js b/app/addons/fauxton/navigation/components/NavLink.js
new file mode 100644
index 0000000..1cb6b7b
--- /dev/null
+++ b/app/addons/fauxton/navigation/components/NavLink.js
@@ -0,0 +1,51 @@
+// 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 React from 'react';
+import classNames from 'classnames';
+
+
+const NavLink = ({link, active, isMinimized}) => {
+
+  const linkClass = classNames(
+    'faux-navbar__link',
+    {'faux-navbar__link--active':  active === link.title},
+    {'faux-navbar__link--inactive': active !== link.title},
+    {'faux-navbar--wide':  !isMinimized},
+    {'faux-navbar--narrow': isMinimized}
+  );
+
+  return (
+    <a className={linkClass} href={link.href} target={link.target ? '_blank' : null} data-bypass={link.target ? 'true' : null}>
+      <div data-nav-name={link.title} className="faux-navbar__itemarea">
+
+        {!!link.icon ?
+          <i className={classNames(link.icon, 'fonticon faux-navbar__icon')}></i> :
+          null
+        }
+        {isMinimized ?
+          null :
+          <span className="faux-navbar__text">{link.title}</span>
+        }
+      </div>
+    </a>
+  );
+};
+
+NavLink.propTypes = {
+  link: React.PropTypes.object.isRequired,
+  active: React.PropTypes.string,
+  isMinimized: React.PropTypes.bool.isRequired,
+};
+
+
+export default NavLink;
diff --git a/app/addons/fauxton/navigation/container/NavBar.js b/app/addons/fauxton/navigation/container/NavBar.js
new file mode 100644
index 0000000..537f0da
--- /dev/null
+++ b/app/addons/fauxton/navigation/container/NavBar.js
@@ -0,0 +1,68 @@
+// 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 React from "react";
+
+import ReactDOM from "react-dom";
+import Stores from "../stores";
+
+import NavBar from '../components/NavBar';
+
+const navBarStore = Stores.navBarStore;
+
+const NavBarContainer = React.createClass({
+
+  getStoreState () {
+    return {
+      navLinks: navBarStore.getNavLinks(),
+      bottomNavLinks: navBarStore.getBottomNavLinks(),
+      footerNavLinks: navBarStore.getFooterNavLinks(),
+      activeLink: navBarStore.getActiveLink(),
+      version: navBarStore.getVersion(),
+      isMinimized: navBarStore.isMinimized(),
+      isNavBarVisible: navBarStore.isNavBarVisible(),
+
+      isLoginSectionVisible: navBarStore.getIsLoginSectionVisible(),
+      isLoginVisibleInsteadOfLogout: navBarStore.getIsLoginVisibleInsteadOfLogout()
+    };
+  },
+
+  getInitialState () {
+    return this.getStoreState();
+  },
+
+  onChange () {
+    this.setState(this.getStoreState());
+  },
+
+  componentDidMount () {
+    navBarStore.on('change', this.onChange, this);
+  },
+
+  componentWillUnmount () {
+    navBarStore.off('change', this.onChange);
+  },
+
+  render () {
+    const user = FauxtonAPI.session.user();
+
+    const username =  user ? user.name : '';
+    return (
+      <NavBar {...this.state} username={username} />
+    );
+  }
+
+});
+
+
+export default NavBarContainer;
diff --git a/app/addons/fauxton/navigation/stores.js b/app/addons/fauxton/navigation/stores.js
index 5e781a8..777f097 100644
--- a/app/addons/fauxton/navigation/stores.js
+++ b/app/addons/fauxton/navigation/stores.js
@@ -10,11 +10,10 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../../app";
 import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./actiontypes";
-const Stores = {};
 
+const Stores = {};
 
 Stores.NavBarStore = FauxtonAPI.Store.extend({
   initialize () {
@@ -22,12 +21,24 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
   },
 
   reset () {
+    this._isMinimized = true;
     this._activeLink = null;
     this._version = null;
     this._navLinks = [];
     this._footerNavLinks = [];
     this._bottomNavLinks = [];
     this._navBarVisible = true;
+
+    this._loginSectionVisible = false;
+    this._loginVisibleInsteadOfLogout = true;
+  },
+
+  getIsLoginSectionVisible () {
+    return this._loginSectionVisible;
+  },
+
+  getIsLoginVisibleInsteadOfLogout () {
+    return this._loginVisibleInsteadOfLogout;
   },
 
   isNavBarVisible () {
@@ -67,7 +78,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
     const links = this.getLinkSection(removeLink);
     let indexOf = 0;
 
-    const res = _.filter(links, function (link) {
+    const res = links.filter((link) => {
       if (link.id === removeLink.id) {
         return true;
       }
@@ -94,8 +105,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
   },
 
   toggleMenu () {
-    app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED,
-                              !this.isMinimized());
+    this._isMinimized = !this._isMinimized;
   },
 
   getLinkSection (link) {
@@ -143,8 +153,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
   },
 
   isMinimized () {
-    const isMinimized = app.utils.localStorageGet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED);
-    return (_.isUndefined(isMinimized)) ? false : isMinimized;
+    return this._isMinimized;
   },
 
   dispatch (action) {
@@ -185,6 +194,20 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({
         this.showNavBar();
       break;
 
+      case ActionTypes.NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION:
+        this._loginSectionVisible = action.visible;
+      break;
+
+      case ActionTypes.NAVBAR_SHOW_LOGIN_BUTTON:
+        this._loginSectionVisible = true;
+        this._loginVisibleInsteadOfLogout = true;
+      break;
+
+      case ActionTypes.NAVBAR_SHOW_LOGOUT_BUTTON:
+        this._loginSectionVisible = true;
+        this._loginVisibleInsteadOfLogout = false;
+      break;
+
       default:
       return;
       // do nothing
diff --git a/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx b/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx
deleted file mode 100644
index ba37364..0000000
--- a/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx
+++ /dev/null
@@ -1,61 +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 Views from "../components.react";
-import Auth from "../../../../core/auth";
-import BaseAuth from "../../../auth/base";
-import utils from "../../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
-import {mount} from 'enzyme';
-
-var assert = utils.assert;
-
-describe('NavBar', function () {
-
-  describe('burger', function () {
-    it('dispatch TOGGLE_NAVBAR_MENU on click', function () {
-      const toggleMenu = sinon.spy();
-      const burgerEl = mount(<Views.Burger toggleMenu={toggleMenu} />);
-      burgerEl.simulate('click');
-      assert.ok(toggleMenu.calledOnce);
-    });
-
-  });
-
-  it('logout link only ever appears once', function () {
-    FauxtonAPI.auth = new Auth();
-    sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-    sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
-    sinon.stub(FauxtonAPI.session, 'user').returns({ name: 'test-user' });
-    BaseAuth.initialize();
-
-    const el = mount(<Views.NavBar />);
-
-    FauxtonAPI.session.trigger('change');
-
-    // confirm the logout link is present
-    let matches = el.text().match(/Logout/);
-    assert.equal(matches.length, 1);
-
-    // now confirm there's still only a single logout link after publishing multiple
-    FauxtonAPI.session.trigger('change');
-    FauxtonAPI.session.trigger('change');
-    matches = el.text().match(/Logout/);
-    assert.equal(matches.length, 1);
-
-    FauxtonAPI.session.isLoggedIn.restore();
-    FauxtonAPI.session.user.restore();
-    FauxtonAPI.session.isAdminParty.restore();
-  });
-});
diff --git a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
index def0e23..daa4c59 100644
--- a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
+++ b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
@@ -24,7 +24,7 @@ module.exports = {
       .click('a[href="#/replication"]')
       .pause(1000)
       .waitForElementVisible('.replication__activity_header-btn', waitTime, false)
-      .assert.cssClassPresent('li[data-nav-name="Replication"]', 'active')
+      .assert.cssClassPresent('a[href="#/replication"]', 'faux-navbar__link--active')
     .end();
   }
 };
diff --git a/app/addons/fauxton/tests/nightwatch/notificationCenter.js b/app/addons/fauxton/tests/nightwatch/notificationCenter.js
index b81d860..0c192bd 100644
--- a/app/addons/fauxton/tests/nightwatch/notificationCenter.js
+++ b/app/addons/fauxton/tests/nightwatch/notificationCenter.js
@@ -29,6 +29,7 @@ module.exports = {
       .assert.cssClassNotPresent('.notification-center-panel', 'visible')
       .clickWhenVisible('#notification-center-btn', waitTime, false)
       .waitForElementPresent('.notification-center-panel.visible', waitTime, false)
+      .waitForElementPresent('.notification-list div.flex-layout', waitTime, false)
 
       .getText('.notification-center-panel', function (result) {
         var content = result.value;
diff --git a/app/app.js b/app/app.js
index 4551a07..cf9be21 100644
--- a/app/app.js
+++ b/app/app.js
@@ -72,6 +72,25 @@ FauxtonAPI.config({
       type: 'REMOVE_NAVBAR_LINK',
       link: link
     });
+  },
+
+  hideLogin: function () {
+    FauxtonAPI.dispatch({
+      type: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION',
+      visible: false
+    });
+  },
+
+  showLogout: function () {
+    FauxtonAPI.dispatch({
+      type: 'NAVBAR_SHOW_LOGOUT_BUTTON'
+    });
+  },
+
+  showLogin: function () {
+    FauxtonAPI.dispatch({
+      type: 'NAVBAR_SHOW_LOGIN_BUTTON'
+    });
   }
 });
 
diff --git a/app/constants.js b/app/constants.js
index a967db4..e937094 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -47,9 +47,5 @@ export default {
     MANGO_INDEX: '/_utils/docs/intro/api.html#documents',
     MANGO_SEARCH: '/_utils/docs/intro/api.html#documents',
     CHANGES: '/_utils/docs/api/database/changes.html?highlight=changes#post--db-_changes'
-  },
-
-  LOCAL_STORAGE: {
-    SIDEBAR_MINIMIZED: 'sidebar-minimized'
   }
 };
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index 679857b..31145f0 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -60,10 +60,6 @@ body {
 
 h2,h3,h4 {font-weight: 600;}
 
-a {
-  .transition(all .25s linear);
-}
-
 // remove blue borders from clicked elements
 button:focus, a:focus {
   outline: 0;
@@ -77,6 +73,7 @@ a,
 a:visited,
 a:active {
   color: @linkColor;
+  text-decoration: none;
 }
 
 a:hover {
@@ -557,7 +554,7 @@ footer.pagination-footer {
 
 // left navigationbar is closed
 @media (max-width: 875px) {
-  body:not(.closeMenu) {
+  #main:not(.closeMenu) {
     .one-pane {
       .faux-header__searchboxwrapper {
         display: none;
@@ -567,7 +564,7 @@ footer.pagination-footer {
 }
 
 @media (max-width: 1285px) {
-  body:not(.closeMenu) {
+  #main:not(.closeMenu) {
     .with-sidebar {
       .faux-header__searchboxwrapper {
         display: none;
@@ -584,7 +581,7 @@ body .control-toggle-include-docs span {
 }
 
 @media (max-width: 1177px) {
-  body.closeMenu {
+  #main.closeMenu {
     .with-sidebar {
       .two-panel-header {
         .control-toggle-include-docs span::before {
@@ -600,7 +597,7 @@ body .control-toggle-include-docs span {
 
 
 @media (max-width: 1120px) {
-  body.closeMenu {
+  #main.closeMenu {
     .with-sidebar {
       .two-panel-header {
         .control-toggle-include-docs span::before {
@@ -627,7 +624,7 @@ body .control-toggle-include-docs span {
 }
 
 @media (max-width: 1339px) {
-  body:not(.closeMenu) {
+  #main:not(.closeMenu) {
     .with-sidebar {
       .two-panel-header {
         .control-toggle-include-docs span::before {
@@ -642,7 +639,7 @@ body .control-toggle-include-docs span {
 }
 
 @media (max-width: 1090px) {
-  body:not(.closeMenu) {
+  #main:not(.closeMenu) {
     .with-sidebar {
       .two-panel-header {
         .control-toggle-include-docs span::before {
diff --git a/assets/less/templates.less b/assets/less/templates.less
index d7a7b49..f42de00 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -50,234 +50,6 @@
   }
 }
 
-/* Fixed side navigation */
-#primary-navbar {
-  z-index: 5;
-  height: 100%;
-  left: 0;
-  top: 0;
-  bottom: 0;
-  position: absolute;
-  width: @navWidth;
-  .closeMenu & {
-    width: @collapsedNavWidth;
-    overflow-x: hidden;
-  }
-  background-color: @brandDark2;
-  .version-footer {
-    color: #fff;
-    font-size: 10px;
-    text-align: center;
-    background-color: @brandDark2;
-  }
-  .closeMenu & {
-    .version-footer {
-      display: none;
-    }
-  }
-  #user-create-admin {
-    font-size: 12px
-  }
-  .navbar {
-    .closeMenu & {
-      width: @collapsedNavWidth;
-    }
-    .burger {
-      padding: 22px 0 22px 18px;
-      position: absolute;
-      z-index: 100;
-      top: 0;
-      background-color: @brandDark2;
-      width: @navWidth;
-      .closeMenu & {
-        width: @collapsedNavWidth;
-      }
-      div {
-        height: 4px;
-        width: 24px;
-        .border-radius(2);
-        background-color: @navIconColor;
-        margin: 2px 0px;
-      }
-      &:hover div {
-        background-color: @hoverHighlight;
-      }
-    }
-    .bottom-container {
-      position: fixed;
-      bottom: 10px;
-      .brand {
-        .box-sizing(content-box);
-        .hide-text;
-        margin: 20px 0 0px 0;
-        width: 199px;
-        height: 50px;
-        padding: 10px 10px 10px 10px;
-        float: none;
-        background: @brandDark2;
-        .icon {
-          .box-sizing(content-box);
-          background: url(../img/CouchDB-negative-logo.png) no-repeat 23px 0px;
-          background-size: 150px;
-          display: block;
-          height: 100%;
-          width: 100%;
-          margin-top: 10px;
-        }
-        .closeMenu & {
-          width: 43px;
-
-          .icon {
-            background: url(../img/couchdb-logo.png) no-repeat 3px 0;
-            background-size: 40px;
-
-          }
-        }
-      }
-
-      #footer-nav-links {
-        width: 100%;
-        margin: 0;
-        background: @brandDark2;
-
-        li {
-          width: 100%;
-          text-align: center;
-
-          a {
-            font-size: 10px;
-            color: @navIconColor;
-            padding: 0px;
-            text-shadow: none;
-            width: 100%;
-          }
-
-          &.active, &:hover {
-            a {
-              text-decoration: underline;
-              color: @hoverHighlight;
-            }
-          }
-        }
-      }
-    }
-    nav {
-      margin-top: 64px;
-      .nav {
-        margin: 0;
-        li {
-          padding: 0;
-          font-size: 16px;
-          letter-spacing: 1px;
-          line-height: 24px;
-          width: @navWidth;
-          .closeMenu & {
-            width: @collapsedNavWidth;
-          }
-          font-weight: normal;
-          font-family: helvetica;
-          .box-sizing(border-box);
-          background-color: @brandDark1;
-          border-bottom:  1px solid @brandDark2;
-          height: 48px;
-          min-height: 48px;
-          position: relative;
- 
-          &.active {
-            a {
-              .box-shadow(none);
-            }
-            background-color: @brandHighlight;
-            //pointer-events: none;
-          }
-          &:hover {
-            a {
-              .box-shadow(none);
-            }
-            background-color: @hoverHighlight;
-          }
-          &:hover .fonticon:before {
-            color: @white;
-          }
-          &.active .fonticon:before,
-          &:hover .fonticon:before,
-          {
-            text-shadow: @boxShadow;
-            color: @navIconActive;
-          }
-          a {
-            padding: 12px 25px 12px 60px;
-            background-color: transparent;
-            color: #fff;
-            text-shadow: @textShadowOff;
-            height: 48px;
-            display: flex;
-
-            .fonticon {
-              position: relative;
-
-              &:before {
-                position: absolute;
-                left: -41px;
-                font-size: 24px;
-                color: @navIconColor;
-                text-shadow: @boxShadowOff;
-              }
-            }
-            .closeMenu & {
-              color: transparent;
-              span {
-                display: none;
-              }
-            }
-          }
-        }
-      }
-      ul#footer-nav-links {
-        li {
-          background-color: @brandDark2;
-          border-top: 1px solid @linkColor;
-          border-bottom: none;
-          &.active, &:hover {
-            background-color: @hoverHighlight;
-            border-top: 1px solid @linkColor;
-            a {
-              color: white;
-            }
-          }
-          a {
-            color: @linkColor;
-          }
-        }
-
-      }
-      ul#bottom-nav-links {
-        margin-top: 0;
-        padding-bottom: 136px;
-        li {
-          background-color: @brandDark1;
-          &.active {
-            background-color:@brandHighlight;
-          }
-          &:hover {
-            background-color: @hoverHighlight;
-          }
-          a {
-            &.fonticon {
-              position: relative;
-              &:before {
-                top: -5px;
-                left: -40px;
-                font-size: 22px;
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
 .bottom-shadow-border {
   border-bottom: 1px solid #999;
   .box-shadow(0px 6px 0 0 rgba(0, 0, 0, 0.1));
diff --git a/assets/less/variables.less b/assets/less/variables.less
index a33f43f..47d9b76 100644
--- a/assets/less/variables.less
+++ b/assets/less/variables.less
@@ -115,3 +115,10 @@
 @infoAlertColor: #329898;
 @successAlertColor: #448c11;
 @errorAlertColor: #c45b55;
+
+/*
+  -- logo image paths --
+  These are set during webpack bundle through your settings.json file.
+*/
+@largeLogoPath: "";
+@smallLogoPath: "";
diff --git a/package.json b/package.json
index 1bad884..3d4d985 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
     "fetch-mock": "^5.9.3",
     "jest": "^18.1.0",
     "less": "^2.7.2",
-    "less-loader": "^2.2.3",
+    "less-loader": "^4.0.3",
     "mocha": "~3.1.2",
     "mocha-loader": "^1.1.0",
     "mocha-phantomjs": "git+https://github.com/garrensmith/mocha-phantomjs.git",
@@ -50,6 +50,7 @@
     "bluebird": "^3.4.6",
     "brace": "^0.7.0",
     "chai": "^3.5.0",
+    "classnames": "^2.2.5",
     "clean-css": "^4.0.5",
     "clipboard": "^1.5.16",
     "couchapp": "~0.11.0",
@@ -77,8 +78,8 @@
     "imports-loader": "^0.7.0",
     "jquery": "^2.2.0",
     "jsondiffpatch": "^0.1.41",
-    "less": "^2.3.1",
-    "less-loader": "^2.2.3",
+    "less": "^2.7.2",
+    "less-loader": "^4.0.3",
     "lodash": "^3.10.1",
     "mkdirp": "^0.5.1",
     "moment": "^2.17.1",
diff --git a/settings.json.default.json b/settings.json.default.json
index fde5dad..d474c1b 100644
--- a/settings.json.default.json
+++ b/settings.json.default.json
@@ -21,7 +21,9 @@
         "dest": "dist/debug/index.html",
         "variables": {
           "title": "Project Fauxton",
-          "generationLabel": "Fauxton Dev"
+          "generationLabel": "Fauxton Dev",
+          "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png",
+          "smallLogoPath": "../../../../../assets/img/couchdb-logo.png"
         },
         "app": {
           "root": "/",
@@ -34,7 +36,9 @@
         "dest": "dist/debug/index.html",
         "variables": {
           "title": "Project Fauxton",
-          "generationLabel": "Fauxton Release"
+          "generationLabel": "Fauxton Release",
+          "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png",
+          "smallLogoPath": "../../../../../assets/img/couchdb-logo.png"
         },
         "app": {
           "root": "/",
@@ -47,7 +51,9 @@
         "dest": "dist/debug/index.html",
         "variables": {
           "title": "Project Fauxton",
-          "generationLabel": "Fauxton Couchapp"
+          "generationLabel": "Fauxton Couchapp",
+          "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png",
+          "smallLogoPath": "../../../../../assets/img/couchdb-logo.png"
         },
         "app": {
           "root": "/",
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index 89d3dbd..54f09f4 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -74,7 +74,15 @@ module.exports = {
       use: [
         "style-loader",
         "css-loader",
-        "less-loader"
+        {
+          loader: "less-loader",
+          options: {
+            modifyVars: {
+              largeLogoPath: "\'" + settings.variables.largeLogoPath + "\'",
+              smallLogoPath: "\'" + settings.variables.smallLogoPath + "\'"
+            }
+          }
+        }
       ]
     },
     {
diff --git a/webpack.config.release.js b/webpack.config.release.js
index 99316c9..5220952 100644
--- a/webpack.config.release.js
+++ b/webpack.config.release.js
@@ -109,7 +109,15 @@ module.exports = {
         fallback: "style-loader",
         use: [
           "css-loader",
-          "less-loader"
+          {
+            loader: "less-loader",
+            options: {
+              modifyVars: {
+                largeLogoPath: "\'" + settings.variables.largeLogoPath + "\'",
+                smallLogoPath: "\'" + settings.variables.smallLogoPath + "\'"
+              }
+            }
+          }
         ],
         publicPath: '../../'
       }),

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <commits@couchdb.apache.org>'].

Mime
View raw message