diff --git a/client/gatsby-browser.js b/client/gatsby-browser.js
index b66417a193..9c69ef5b9f 100644
--- a/client/gatsby-browser.js
+++ b/client/gatsby-browser.js
@@ -10,6 +10,7 @@ import {
DefaultLayout,
GuideLayout
} from './src/components/layouts';
+import GuideNavMenu from './src/components/layouts/components/guide/NavMenu';
const store = createStore();
@@ -41,7 +42,7 @@ export const wrapPageElement = ({ element, props }) => {
}
if (/^\/guide(\/.*)*/.test(pathname)) {
return (
-
+ }>
{element}
);
diff --git a/client/gatsby-ssr.js b/client/gatsby-ssr.js
index d369a5c515..312207d60e 100644
--- a/client/gatsby-ssr.js
+++ b/client/gatsby-ssr.js
@@ -5,12 +5,9 @@ import { Provider } from 'react-redux';
import headComponents from './src/head';
import { createStore } from './src/redux/createStore';
+import { wrapPageElement } from './gatsby-browser';
-import {
- CertificationLayout,
- DefaultLayout,
- GuideLayout
-} from './src/components/layouts';
+export { wrapPageElement };
const store = createStore();
@@ -22,39 +19,6 @@ wrapRootElement.propTypes = {
element: PropTypes.any
};
-export const wrapPageElement = ({ element, props }) => {
- const {
- location: { pathname }
- } = props;
- if (pathname === '/') {
- return (
-
- {element}
-
- );
- }
- if (/^\/certification(\/.*)*/.test(pathname)) {
- return {element};
- }
- if (/^\/guide(\/.*)*/.test(pathname)) {
- return (
-
- {element}
-
- );
- }
- if (/^\/learn(\/.*)*/.test(pathname)) {
- return {element};
- }
- return {element};
-};
-
-wrapPageElement.propTypes = {
- element: PropTypes.any,
- location: PropTypes.objectOf({ pathname: PropTypes.string }),
- props: PropTypes.any
-};
-
export const onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => {
setHeadComponents([...headComponents]);
setPostBodyComponents(
diff --git a/client/src/components/Header/components/MenuButton.js b/client/src/components/Header/components/MenuButton.js
new file mode 100644
index 0000000000..d9d1830b28
--- /dev/null
+++ b/client/src/components/Header/components/MenuButton.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import './menuButton.css';
+
+const MenuButton = React.forwardRef((props, ref) => (
+
+));
+
+MenuButton.displayName = 'MenuButton';
+MenuButton.propTypes = {
+ className: PropTypes.string,
+ displayMenu: PropTypes.bool.isRequired,
+ onClick: PropTypes.func.isRequired
+};
+
+export default MenuButton;
diff --git a/client/src/components/Header/components/MenuLinks.js b/client/src/components/Header/components/MenuLinks.js
new file mode 100644
index 0000000000..602cca4e46
--- /dev/null
+++ b/client/src/components/Header/components/MenuLinks.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Link } from '../../helpers';
+import UserState from '../components/UserState';
+
+import './menuLinks.css';
+
+function MenuLinks(props) {
+ return (
+
+ -
+
+ Learn
+
+
+ -
+
+ Forum
+
+
+ -
+
+ News
+
+
+ -
+
+
+
+ );
+}
+
+MenuLinks.displayName = 'MenuLinks';
+MenuLinks.propTypes = {
+ className: PropTypes.string,
+ disableSettings: PropTypes.bool
+};
+
+export default MenuLinks;
diff --git a/client/src/components/Header/components/NavMenu.js b/client/src/components/Header/components/NavMenu.js
new file mode 100644
index 0000000000..9f0377fa23
--- /dev/null
+++ b/client/src/components/Header/components/NavMenu.js
@@ -0,0 +1,65 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import MenuButton from './MenuButton';
+import MenuLinks from './MenuLinks';
+
+class NavigationMenu extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ displayMenu: false
+ };
+ this.menuButtonRef = React.createRef();
+
+ this.handleClickOutside = this.handleClickOutside.bind(this);
+ this.toggleDisplayMenu = this.toggleDisplayMenu.bind(this);
+ }
+
+ componentDidMount() {
+ document.addEventListener('click', this.handleClickOutside);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.handleClickOutside);
+ }
+
+ handleClickOutside(event) {
+ if (
+ this.state.displayMenu &&
+ this.menuButtonRef.current &&
+ !this.menuButtonRef.current.contains(event.target)
+ ) {
+ this.toggleDisplayMenu();
+ }
+ }
+
+ toggleDisplayMenu() {
+ this.setState(({ displayMenu }) => ({ displayMenu: !displayMenu }));
+ }
+
+ render() {
+ const { disableSettings } = this.props;
+ const { displayMenu } = this.state;
+ return (
+ <>
+
+
+ >
+ );
+ }
+}
+
+NavigationMenu.displayName = 'NavigationMenu';
+NavigationMenu.propTypes = {
+ disableSettings: PropTypes.bool
+};
+
+export default NavigationMenu;
diff --git a/client/src/components/Header/components/menuButton.css b/client/src/components/Header/components/menuButton.css
new file mode 100644
index 0000000000..7bfcf062f6
--- /dev/null
+++ b/client/src/components/Header/components/menuButton.css
@@ -0,0 +1,26 @@
+
+#top-nav .menu-button {
+ color: white;
+ background-color: transparent;
+ margin: 0 20px 0 12px;
+ padding: 2px 14px;
+ border: 1px solid #fff;
+ border-radius: 3px;
+}
+
+#top-nav .menu-button-open {
+ background: white;
+ color: #006400;
+}
+
+@media (min-width: 735px) {
+ #top-nav .top-menu-button {
+ display: none;
+ }
+}
+
+@media (max-width: 420px) {
+ #top-nav .menu-button {
+ margin: 0 10px 0 4px;
+ }
+}
diff --git a/client/src/components/Header/components/menuLinks.css b/client/src/components/Header/components/menuLinks.css
new file mode 100644
index 0000000000..d2b9f5df14
--- /dev/null
+++ b/client/src/components/Header/components/menuLinks.css
@@ -0,0 +1,62 @@
+#top-right-nav {
+ display: flex;
+ margin: 0;
+ list-style: none;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #006400;
+}
+
+#top-right-nav li {
+ margin: 0;
+}
+
+.top-right-nav-link {
+ max-height: var(--header-height);
+ color: #fff;
+ font-size: 18px;
+ padding: 8px 15px;
+}
+
+.top-right-nav-link:hover,
+.top-right-nav-link:focus,
+.top-right-nav-link:active {
+ background-color: #fff;
+ color: #006400;
+ text-decoration: none;
+}
+
+.user-state-spinner {
+ height: var(--header-height);
+ padding: 0 12px;
+}
+
+.user-state-spinner > div {
+ animation-duration: 1.5s !important;
+}
+
+#top-right-nav .signup-btn,
+#top-right-nav .settings-link {
+ margin: 0 15px;
+}
+
+#top-right-nav .user-avatar {
+ max-height: calc(var(--header-height) - 4px);
+}
+
+@media (max-width: 734px) {
+ #top-right-nav {
+ position: absolute;
+ top: var(--header-height);
+ flex-direction: column;
+ width: 100vw;
+ height: min-content;
+ min-height: 160px;
+ padding: 10px 0;
+ display: none;
+ }
+
+ #top-right-nav.top-nav-expanded {
+ display: flex;
+ }
+}
diff --git a/client/src/components/Header/header.css b/client/src/components/Header/header.css
index b7e05691f5..1abf2f5cec 100644
--- a/client/src/components/Header/header.css
+++ b/client/src/components/Header/header.css
@@ -26,49 +26,6 @@ header {
align-items: center;
}
-#top-nav .nav-logo {
- max-height: 25px;
- min-width: 35px;
- margin: 0 5px;
-}
-
-#top-right-nav {
- display: flex;
- margin: 0;
- list-style: none;
- justify-content: space-between;
- align-items: center;
- background-color: #006400;
-}
-
-#top-right-nav li {
- margin: 0;
-}
-
-.top-right-nav-link {
- max-height: var(--header-height);
- color: #fff;
- font-size: 18px;
- padding: 8px 15px;
-}
-
-.top-right-nav-link:hover,
-.top-right-nav-link:focus,
-.top-right-nav-link:active {
- background-color: #fff;
- color: #006400;
- text-decoration: none;
-}
-
-.user-state-spinner {
- height: var(--header-height);
- padding: 0 12px;
-}
-
-.user-state-spinner > div {
- animation-duration: 1.5s !important;
-}
-
/* Search bar */
.fcc_searchBar {
flex-grow: 1;
@@ -127,6 +84,12 @@ header {
/* Navbar logo */
+#top-nav .nav-logo {
+ max-height: 25px;
+ min-width: 35px;
+ margin: 0 5px;
+}
+
.logoContainer {
margin-right: 10px;
}
@@ -138,44 +101,11 @@ header {
}
}
-#top-nav .menu-button {
- color: white;
- background-color: transparent;
- margin: 0 20px 0 12px;
- padding: 2px 14px;
- border: 1px solid #fff;
- border-radius: 3px;
-}
-
-#top-nav .menu-button-open {
- background: white;
- color: #006400;
-}
-
-#top-right-nav .signup-btn,
-#top-right-nav .settings-link {
- margin: 0 15px;
-}
-
-#top-right-nav .user-avatar {
- max-height: calc(var(--header-height) - 4px);
-}
-
@media (max-width: 734px) {
#top-nav {
padding: 0;
}
- #top-right-nav {
- position: absolute;
- top: var(--header-height);
- flex-direction: column;
- width: 100vw;
- height: min-content;
- min-height: 160px;
- padding: 10px 0;
- }
-
#top-nav .nav-logo {
margin: 0 0 0 10px;
}
@@ -185,14 +115,4 @@ header {
#top-nav .nav-logo {
margin: 0 0 0 5px;
}
-
- #top-nav .menu-button {
- margin: 0 10px 0 4px;
- }
-
- .ais-Hits {
- background-color: #fff;
- left: 0.5em;
- top: 2.4em;
- }
}
diff --git a/client/src/components/Header/index.js b/client/src/components/Header/index.js
index 1caf207ed1..44012ed169 100644
--- a/client/src/components/Header/index.js
+++ b/client/src/components/Header/index.js
@@ -1,145 +1,37 @@
-import React, { Component } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { createSelector } from 'reselect';
-import Media from 'react-responsive';
import FCCSearch from 'react-freecodecamp-search';
+import NavigationMenu from './components/NavMenu';
import NavLogo from './components/NavLogo';
-import UserState from './components/UserState';
import { Link } from '../helpers';
import './header.css';
-import {
- toggleDisplayMenu,
- displayMenuSelector
-} from '../layouts/components/guide/redux';
-
-const mapStateToProps = createSelector(
- displayMenuSelector,
- displayMenu => ({
- displayMenu
- })
-);
-
-const mapDispatchToProps = dispatch =>
- bindActionCreators({ toggleDisplayMenu }, dispatch);
-
const propTypes = {
- disableMenuButtonBehavior: PropTypes.bool,
disableSettings: PropTypes.bool,
- displayMenu: PropTypes.bool,
- mediaBreakpoint: PropTypes.string.isRequired,
- toggleDisplayMenu: PropTypes.func.isRequired
+ navigationMenu: PropTypes.element
};
-class Header extends Component {
- constructor(props) {
- super(props);
- this.menuButtonRef = React.createRef();
- }
-
- componentDidMount() {
- document.addEventListener('click', this.handleClickOutside);
- }
-
- componentWillUnmount() {
- document.removeEventListener('click', this.handleClickOutside);
- }
-
- handleClickOutside = event => {
- if (
- !this.props.disableMenuButtonBehavior &&
- this.props.displayMenu &&
- this.menuButtonRef.current &&
- !this.menuButtonRef.current.contains(event.target)
- ) {
- this.props.toggleDisplayMenu();
- }
- };
-
- handleMediaChange = matches => {
- if (!matches && this.props.displayMenu) {
- this.props.toggleDisplayMenu();
- }
- };
-
- render() {
- const {
- disableMenuButtonBehavior,
- disableSettings,
- displayMenu,
- mediaBreakpoint,
- toggleDisplayMenu
- } = this.props;
- return (
-
-
-
- );
- }
+function Header(props) {
+ const { disableSettings, navigationMenu } = props;
+ return (
+
+
+
+ );
}
Header.propTypes = propTypes;
-Header.defaultProps = {
- mediaBreakpoint: '734px'
-};
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(Header);
+export default Header;
diff --git a/client/src/components/layouts/Default.js b/client/src/components/layouts/Default.js
index a1127b3506..86894edcce 100644
--- a/client/src/components/layouts/Default.js
+++ b/client/src/components/layouts/Default.js
@@ -56,8 +56,6 @@ const metaKeywords = [
const propTypes = {
children: PropTypes.node.isRequired,
- disableMenuButtonBehavior: PropTypes.bool,
- disableSettings: PropTypes.bool,
fetchUser: PropTypes.func.isRequired,
flashMessages: PropTypes.arrayOf(
PropTypes.shape({
@@ -70,7 +68,7 @@ const propTypes = {
isOnline: PropTypes.bool.isRequired,
isSignedIn: PropTypes.bool,
landingPage: PropTypes.bool,
- mediaBreakpoint: PropTypes.string,
+ navigationMenu: PropTypes.element.isRequired,
onlineStatusChange: PropTypes.func.isRequired,
removeFlashMessage: PropTypes.func.isRequired,
showFooter: PropTypes.bool
@@ -136,14 +134,12 @@ class DefaultLayout extends Component {
render() {
const {
children,
- disableSettings,
hasMessages,
flashMessages = [],
removeFlashMessage,
landingPage,
showFooter = true,
- mediaBreakpoint,
- disableMenuButtonBehavior,
+ navigationMenu,
isOnline,
isSignedIn
} = this.props;
@@ -162,11 +158,7 @@ class DefaultLayout extends Component {
>
-
+
{hasMessages ? (
diff --git a/client/src/components/layouts/components/guide/NavMenu.js b/client/src/components/layouts/components/guide/NavMenu.js
new file mode 100644
index 0000000000..b8d4c6a9c2
--- /dev/null
+++ b/client/src/components/layouts/components/guide/NavMenu.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { createSelector } from 'reselect';
+
+import MenuButton from '../../../../components/Header/components/MenuButton';
+import MenuLinks from '../../../../components/Header/components/MenuLinks';
+
+import { toggleDisplayMenu, displayMenuSelector } from './redux';
+
+const mapStateToProps = createSelector(
+ displayMenuSelector,
+ displayMenu => ({
+ displayMenu
+ })
+);
+
+const mapDispatchToProps = dispatch =>
+ bindActionCreators({ toggleDisplayMenu }, dispatch);
+
+function GuideNavigationMenu(props) {
+ const { displayMenu, toggleDisplayMenu } = props;
+ return (
+ <>
+
+
+ >
+ );
+}
+
+GuideNavigationMenu.displayName = 'GuideNavigationMenu';
+GuideNavigationMenu.propTypes = {
+ displayMenu: PropTypes.bool.isRequired,
+ toggleDisplayMenu: PropTypes.func.isRequired
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(GuideNavigationMenu);
diff --git a/client/src/components/layouts/guide.css b/client/src/components/layouts/guide.css
index e1df01d0fb..cbe292c5af 100644
--- a/client/src/components/layouts/guide.css
+++ b/client/src/components/layouts/guide.css
@@ -124,6 +124,15 @@
.content {
height: auto;
}
+ #top-right-nav.guide-top-nav {
+ display: none;
+ }
+}
+
+@media (min-width: 993px) {
+ #top-nav .guide-menu-button {
+ display: none;
+ }
}
.content,