diff --git a/client/less/lib/bootstrap/variables.less b/client/less/lib/bootstrap/variables.less index 019b8e7a58..15fcda30c3 100755 --- a/client/less/lib/bootstrap/variables.less +++ b/client/less/lib/bootstrap/variables.less @@ -328,7 +328,7 @@ @grid-gutter-width: 30px; // Navbar collapse //** Point at which the navbar becomes uncollapsed. -@grid-float-breakpoint: @screen-sm-min; +@grid-float-breakpoint: 955px; //** Point at which the navbar begins collapsing. @grid-float-breakpoint-max: (@grid-float-breakpoint - 1); diff --git a/common/app/Nav/LargeNav.jsx b/common/app/Nav/LargeNav.jsx new file mode 100644 index 0000000000..e9d4052a6c --- /dev/null +++ b/common/app/Nav/LargeNav.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import Media from 'react-media'; +import { Col, Navbar, Row } from 'react-bootstrap'; +import FCCSearchBar from 'react-freecodecamp-search'; +import { NavLogo, BinButtons, NavLinks } from './components'; + +import propTypes from './navPropTypes'; + +function LargeNav({ clickOnLogo, clickOnMap, shouldShowMapButton, panes }) { + return ( + ( + + + + + + + + + + + + + + + + + ) + } + /> + ); +} + +LargeNav.displayName = 'LargeNav'; +LargeNav.propTypes = propTypes; + +export default LargeNav; diff --git a/common/app/Nav/MediumNav.jsx b/common/app/Nav/MediumNav.jsx new file mode 100644 index 0000000000..bb5004121c --- /dev/null +++ b/common/app/Nav/MediumNav.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import Media from 'react-media'; +import { Navbar, Row } from 'react-bootstrap'; +import FCCSearchBar from 'react-freecodecamp-search'; +import { NavLogo, BinButtons, NavLinks } from './components'; + +import propTypes from './navPropTypes'; + +function MediumNav({ clickOnLogo, clickOnMap, shouldShowMapButton, panes }) { + return ( + + { + matches => matches && typeof window !== 'undefined' && ( +
+ + +
+ + + +
+
+ +
+
+
+ + + + + +
+ ) + } +
+ ); +} + +MediumNav.displayName = 'MediumNav'; +MediumNav.propTypes = propTypes; + +export default MediumNav; diff --git a/common/app/Nav/Nav.jsx b/common/app/Nav/Nav.jsx index 463519a77e..9fd70567ca 100644 --- a/common/app/Nav/Nav.jsx +++ b/common/app/Nav/Nav.jsx @@ -1,54 +1,22 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import capitalize from 'lodash/capitalize'; import { createSelector } from 'reselect'; -import FCCSearchBar from 'react-freecodecamp-search'; -import { - MenuItem, - Nav, - NavDropdown, - NavItem, - Navbar, - NavbarBrand -} from 'react-bootstrap'; +import { Navbar } from 'react-bootstrap'; -import NoPropsPassThrough from '../utils/No-Props-Passthrough.jsx'; -import { Link } from '../Router'; -import navLinks from './links.json'; -import SignUp from './Sign-Up.jsx'; -import BinButton from './Bin-Button.jsx'; +import LargeNav from './LargeNav.jsx'; +import MediumNav from './MediumNav.jsx'; +import SmallNav from './SmallNav.jsx'; import { clickOnLogo, - clickOnMap, - openDropdown, - closeDropdown, - createNavLinkActionCreator, - - dropdownSelector + clickOnMap } from './redux'; -import { isSignedInSelector, signInLoadingSelector } from '../redux'; import { panesSelector } from '../Panes/redux'; -import { onRouteCurrentChallenge } from '../routes/Challenges/redux'; - - -const fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg'; -// TODO @freecodecamp-team: place this glyph in S3 like above, PR in /assets -const fCCglyph = 'https://raw.githubusercontent.com/freeCodeCamp/assets/' + - '3b9cafc312802199ebba8b31fb1ed9b466a3efbb/assets/logos/FFCFire.png'; +import propTypes from './navPropTypes'; const mapStateToProps = createSelector( - isSignedInSelector, - dropdownSelector, - signInLoadingSelector, panesSelector, - ( - isSignedIn, - isDropdownOpen, - showLoading, - panes, - ) => { + panes => { return { panes: panes.map(({ name, type }) => { return { @@ -56,20 +24,13 @@ const mapStateToProps = createSelector( action: type }; }, {}), - isDropdownOpen, - isSignedIn, - showLoading + shouldShowMapButton: panes.length === 0 }; } ); function mapDispatchToProps(dispatch) { - const dispatchers = bindActionCreators(navLinks.reduce( - (mdtp, { content }) => { - const handler = `handle${capitalize(content)}Click`; - mdtp[handler] = createNavLinkActionCreator(content); - return mdtp; - }, + const dispatchers = bindActionCreators( { clickOnMap: e => { e.preventDefault(); @@ -78,11 +39,10 @@ function mapDispatchToProps(dispatch) { clickOnLogo: e => { e.preventDefault(); return clickOnLogo(); - }, - closeDropdown: () => closeDropdown(), - openDropdown: () => openDropdown() - } - ), dispatch); + } + }, + dispatch + ); dispatchers.dispatch = dispatch; return () => dispatchers; } @@ -102,156 +62,39 @@ function mergeProps(stateProps, dispatchProps, ownProps) { }; } -const propTypes = { - clickOnLogo: PropTypes.func.isRequired, - clickOnMap: PropTypes.func.isRequired, - closeDropdown: PropTypes.func.isRequired, - isDropdownOpen: PropTypes.bool, - isSignedIn: PropTypes.bool, - openDropdown: PropTypes.func.isRequired, - panes: PropTypes.array, - showLoading: PropTypes.bool, - signedIn: PropTypes.bool -}; +const allNavs = [ + LargeNav, + MediumNav, + SmallNav +]; -export class FCCNav extends React.Component { - renderLink(isNavItem, { isReact, isDropdown, content, link, links, target }) { - const Component = isNavItem ? NavItem : MenuItem; - const { - isDropdownOpen, - openDropdown, - closeDropdown - } = this.props; - - if (isDropdown) { - // adding a noop to NavDropdown to disable false warning - // about controlled component - return ( - - { links.map(this.renderLink.bind(this, false)) } - - ); +function FCCNav(props) { + const { + panes, + clickOnLogo, + clickOnMap, + shouldShowMapButton + } = props; + const withNavProps = Component => ( + + ); + return ( + + { + allNavs.map(withNavProps) } - if (isReact) { - return ( - - - { content } - - - ); - } - return ( - - { content } - - ); - } - - render() { - const { - panes, - isSignedIn, - clickOnLogo, - clickOnMap, - showLoading - } = this.props; - - const shouldShowMapButton = panes.length === 0; - return ( - -
- - - - - learn to code javascript at freeCodeCamp logo - learn to code javascript at freeCodeCamp logo - - - - - - - -
-
- ); - } +
+ ); } FCCNav.displayName = 'FCCNav'; diff --git a/common/app/Nav/SmallNav.jsx b/common/app/Nav/SmallNav.jsx new file mode 100644 index 0000000000..f4ff42afce --- /dev/null +++ b/common/app/Nav/SmallNav.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import Media from 'react-media'; +import { Navbar, Row } from 'react-bootstrap'; +import FCCSearchBar from 'react-freecodecamp-search'; +import { NavLogo, BinButtons, NavLinks } from './components'; + +import propTypes from './navPropTypes'; + +function SmallNav({ clickOnLogo, clickOnMap, shouldShowMapButton, panes }) { + return ( + + { + matches => matches && typeof window !== 'undefined' && ( +
+ + +
+ + +
+
+ +
+
+
+ + + + + + + +
+ ) + } +
+ ); +} + +SmallNav.displayName = 'SmallNav'; +SmallNav.propTypes = propTypes; + +export default SmallNav; diff --git a/common/app/Nav/Bin-Button.jsx b/common/app/Nav/components/Bin-Button.jsx similarity index 79% rename from common/app/Nav/Bin-Button.jsx rename to common/app/Nav/components/Bin-Button.jsx index 21360d4cdf..d0ec1f35f5 100644 --- a/common/app/Nav/Bin-Button.jsx +++ b/common/app/Nav/components/Bin-Button.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { NavItem } from 'react-bootstrap'; +import { Button } from 'react-bootstrap'; const propTypes = { content: PropTypes.string, @@ -9,11 +9,12 @@ const propTypes = { export default function BinButton({ content, handleClick }) { return ( - { content } - + ); } BinButton.displayName = 'BinButton'; diff --git a/common/app/Nav/components/BinButtons.jsx b/common/app/Nav/components/BinButtons.jsx new file mode 100644 index 0000000000..f6d429f4e1 --- /dev/null +++ b/common/app/Nav/components/BinButtons.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ButtonGroup } from 'react-bootstrap'; +import BinButton from './Bin-Button.jsx'; + +const propTypes = { + panes: PropTypes.arrayOf( + PropTypes.shape({ + actionCreator: PropTypes.func.isRequired, + content: PropTypes.string.isRequired + }) + ) +}; + +function BinButtons({ panes }) { + return ( + + { + panes.map(({ content, actionCreator }) => ( + + )) + } + + ); +} + +BinButtons.displayName = 'BinButtons'; +BinButtons.propTypes = propTypes; + +export default BinButtons; diff --git a/common/app/Nav/components/NavLinks.jsx b/common/app/Nav/components/NavLinks.jsx new file mode 100644 index 0000000000..02a33cdc9f --- /dev/null +++ b/common/app/Nav/components/NavLinks.jsx @@ -0,0 +1,171 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { capitalize } from 'lodash'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { MenuItem, NavDropdown, NavItem, Nav } from 'react-bootstrap'; + +import navLinks from '../links.json'; +import SignUp from './Sign-Up.jsx'; +import NoPropsPassThrough from '../../utils/No-Props-Passthrough.jsx'; +import { Link } from '../../Router'; + +import { onRouteCurrentChallenge } from '../../routes/Challenges/redux'; +import { + openDropdown, + closeDropdown, + dropdownSelector, + createNavLinkActionCreator +} from '../redux'; +import { isSignedInSelector, signInLoadingSelector } from '../../redux'; + +const mapStateToProps = createSelector( + isSignedInSelector, + dropdownSelector, + signInLoadingSelector, + (isSignedIn, isDropdownOpen, showLoading) => ({ + isDropdownOpen, + isSignedIn, + navLinks, + showLoading + }) +); + +function mapDispatchToProps(dispatch) { + return bindActionCreators( + { + ...navLinks.reduce( + (mdtp, { content }) => { + const handler = `handle${capitalize(content)}Click`; + mdtp[handler] = createNavLinkActionCreator(content); + return mdtp; + }), + closeDropdown, + openDropdown + }, + dispatch + ); +} + +const navLinkPropType = PropTypes.shape({ + content: PropTypes.string, + link: PropTypes.string, + isDropdown: PropTypes.bool, + target: PropTypes.string, + links: PropTypes.array +}); + +const propTypes = { + children: PropTypes.any, + clickOnMap: PropTypes.func.isRequired, + closeDropdown: PropTypes.func.isRequired, + isDropdownOpen: PropTypes.bool, + isInNav: PropTypes.bool, + isSignedIn: PropTypes.bool, + navLinks: PropTypes.arrayOf(navLinkPropType), + openDropdown: PropTypes.func.isRequired, + shouldShowMapButton: PropTypes.bool, + showLoading: PropTypes.bool +}; + +class NavLinks extends PureComponent { + + renderLink(isNavItem, { isReact, isDropdown, content, link, links, target }) { + const Component = isNavItem ? NavItem : MenuItem; + const { + isDropdownOpen, + openDropdown, + closeDropdown + } = this.props; + + if (isDropdown) { + // adding a noop to NavDropdown to disable false warning + // about controlled component + return ( + + { links.map(this.renderLink.bind(this, false)) } + + ); + } + if (isReact) { + return ( + + + { content } + + + ); + } + return ( + + { content } + + ); + } + + render() { + const { + shouldShowMapButton, + clickOnMap, + showLoading, + isSignedIn, + navLinks, + isInNav = true, + children + } = this.props; + return ( + + ); + } +} + +NavLinks.displayName = 'NavLinks'; +NavLinks.propTypes = propTypes; + +export default connect(mapStateToProps, mapDispatchToProps)(NavLinks); diff --git a/common/app/Nav/components/NavLogo.jsx b/common/app/Nav/components/NavLogo.jsx new file mode 100644 index 0000000000..3ead758437 --- /dev/null +++ b/common/app/Nav/components/NavLogo.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { NavbarBrand } from 'react-bootstrap'; +import Media from 'react-media'; + +const fCClogo = 'https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg'; +// TODO @freecodecamp-team: place this glyph in S3 like above, PR in /assets +const fCCglyph = 'https://raw.githubusercontent.com/freeCodeCamp/assets/' + +'3b9cafc312802199ebba8b31fb1ed9b466a3efbb/assets/logos/FFCFire.png'; + +const propTypes = { + clickOnLogo: PropTypes.func.isRequired +}; + +function NavLogo({ clickOnLogo }) { + return ( + + + + { + matches => matches ? ( + learn to code javascript at freeCodeCamp logo + ) : ( + learn to code javascript at freeCodeCamp logo + ) + } + + + + ); +} + +NavLogo.displayName = 'NavLogo'; +NavLogo.propTypes = propTypes; + +export default NavLogo; diff --git a/common/app/Nav/Sign-Up.jsx b/common/app/Nav/components/Sign-Up.jsx similarity index 56% rename from common/app/Nav/Sign-Up.jsx rename to common/app/Nav/components/Sign-Up.jsx index b91c9e895a..6fc2bb30e3 100644 --- a/common/app/Nav/Sign-Up.jsx +++ b/common/app/Nav/components/Sign-Up.jsx @@ -1,21 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { NavItem } from 'react-bootstrap'; +import { MenuItem, NavItem } from 'react-bootstrap'; -import { Link } from '../Router'; -import { onRouteSettings } from '../routes/Settings/redux'; +import { Link } from '../../Router'; +import { onRouteSettings } from '../../routes/Settings/redux'; const propTypes = { + isInDropDown: PropTypes.bool, showLoading: PropTypes.bool, showSignUp: PropTypes.bool }; -export default function SignUpButton({ showLoading, showSignUp }) { +function SignUpButton({ isInDropDown, showLoading, showSignUp }) { if (showLoading) { return null; } if (showSignUp) { - return ( + return isInDropDown ? ( + + Sign Up + + ) : (