feat(Nav): Split Nav Bar in to Components
This commit is contained in:
@ -2,53 +2,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
|
||||
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 { BinButtons, NavLogo, NavLinks } from './components';
|
||||
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';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
dropdownSelector,
|
||||
signInLoadingSelector,
|
||||
panesSelector,
|
||||
(
|
||||
isSignedIn,
|
||||
isDropdownOpen,
|
||||
showLoading,
|
||||
panes,
|
||||
) => {
|
||||
panes => {
|
||||
return {
|
||||
panes: panes.map(({ name, type }) => {
|
||||
return {
|
||||
@ -56,20 +25,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 +40,10 @@ function mapDispatchToProps(dispatch) {
|
||||
clickOnLogo: e => {
|
||||
e.preventDefault();
|
||||
return clickOnLogo();
|
||||
},
|
||||
closeDropdown: () => closeDropdown(),
|
||||
openDropdown: () => openDropdown()
|
||||
}
|
||||
), dispatch);
|
||||
}
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
dispatchers.dispatch = dispatch;
|
||||
return () => dispatchers;
|
||||
}
|
||||
@ -105,153 +66,42 @@ 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
|
||||
shouldShowMapButton: PropTypes.bool
|
||||
};
|
||||
|
||||
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 (
|
||||
<NavDropdown
|
||||
id={ `nav-${content}-dropdown` }
|
||||
key={ content }
|
||||
noCaret={ true }
|
||||
onClick={ openDropdown }
|
||||
onMouseEnter={ openDropdown }
|
||||
onMouseLeave={ closeDropdown }
|
||||
onToggle={ isDropdownOpen ? closeDropdown : openDropdown }
|
||||
open={ isDropdownOpen }
|
||||
title={ content }
|
||||
>
|
||||
{ links.map(this.renderLink.bind(this, false)) }
|
||||
</NavDropdown>
|
||||
);
|
||||
}
|
||||
if (isReact) {
|
||||
return (
|
||||
<Link
|
||||
key={ content }
|
||||
onClick={ this.props[`handle${content}Click`] }
|
||||
to={ link }
|
||||
>
|
||||
<Component
|
||||
target={ target || null }
|
||||
>
|
||||
{ content }
|
||||
</Component>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Component
|
||||
href={ link }
|
||||
key={ content }
|
||||
onClick={ this.props[`handle${content}Click`] }
|
||||
target={ target || null }
|
||||
>
|
||||
{ content }
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
panes,
|
||||
isSignedIn,
|
||||
clickOnLogo,
|
||||
clickOnMap,
|
||||
showLoading
|
||||
} = this.props;
|
||||
|
||||
const shouldShowMapButton = panes.length === 0;
|
||||
return (
|
||||
<Navbar
|
||||
className='nav-height'
|
||||
id='navbar'
|
||||
staticTop={ true }
|
||||
>
|
||||
<div className='nav-component-wrapper'>
|
||||
<Navbar.Header>
|
||||
<Navbar.Toggle children={ 'Menu' } />
|
||||
<NavbarBrand>
|
||||
<a
|
||||
href='/challenges/current-challenge'
|
||||
onClick={ clickOnLogo }
|
||||
>
|
||||
<img
|
||||
alt='learn to code javascript at freeCodeCamp logo'
|
||||
className='img-responsive nav-logo logo'
|
||||
src={ fCClogo }
|
||||
/>
|
||||
<img
|
||||
alt='learn to code javascript at freeCodeCamp logo'
|
||||
className='img-responsive logo-glyph'
|
||||
src={ fCCglyph }
|
||||
/>
|
||||
</a>
|
||||
</NavbarBrand>
|
||||
<FCCSearchBar
|
||||
dropdown={ true }
|
||||
placeholder=' Search 8,000+ lessons, articles, and videos'
|
||||
/>
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
<Nav
|
||||
navbar={ true }
|
||||
pullRight={ true }
|
||||
>
|
||||
{
|
||||
panes.map(({ content, actionCreator }) => (
|
||||
<BinButton
|
||||
content={ content }
|
||||
handleClick={ actionCreator }
|
||||
key={ content }
|
||||
/>
|
||||
))
|
||||
}
|
||||
{ shouldShowMapButton ?
|
||||
<NoPropsPassThrough>
|
||||
<li>
|
||||
<Link
|
||||
onClick={ clickOnMap }
|
||||
to={ onRouteCurrentChallenge() }
|
||||
>
|
||||
Map
|
||||
</Link>
|
||||
</li>
|
||||
</NoPropsPassThrough> :
|
||||
null
|
||||
}
|
||||
{
|
||||
navLinks.map(
|
||||
this.renderLink.bind(this, true)
|
||||
)
|
||||
}
|
||||
<SignUp
|
||||
showLoading={ showLoading }
|
||||
showSignUp={ !isSignedIn }
|
||||
/>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</div>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
function FCCNav(props) {
|
||||
const {
|
||||
panes,
|
||||
clickOnLogo,
|
||||
clickOnMap,
|
||||
shouldShowMapButton
|
||||
} = props;
|
||||
return (
|
||||
<Navbar
|
||||
className='nav-height'
|
||||
id='navbar'
|
||||
staticTop={ true }
|
||||
>
|
||||
<div className='nav-component-wrapper'>
|
||||
<Navbar.Header>
|
||||
<Navbar.Toggle children={ 'Menu' } />
|
||||
<NavLogo clickOnLogo={ clickOnLogo } />
|
||||
<FCCSearchBar
|
||||
dropdown={ true }
|
||||
placeholder=' Search 8,000+ lessons, articles, and videos'
|
||||
/>
|
||||
</Navbar.Header>
|
||||
<BinButtons panes={ panes } />
|
||||
<Navbar.Collapse>
|
||||
<NavLinks
|
||||
clickOnMap={ clickOnMap }
|
||||
shouldShowMapButton={ shouldShowMapButton }
|
||||
/>
|
||||
</Navbar.Collapse>
|
||||
</div>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
FCCNav.displayName = 'FCCNav';
|
||||
|
36
common/app/Nav/components/BinButtons.jsx
Normal file
36
common/app/Nav/components/BinButtons.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Nav } 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 (
|
||||
<div id='bin-buttons'>
|
||||
<Nav>
|
||||
{
|
||||
panes.map(({ content, actionCreator }) => (
|
||||
<BinButton
|
||||
content={ content }
|
||||
handleClick={ actionCreator }
|
||||
key={ content }
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BinButtons.displayName = 'BinButtons';
|
||||
BinButtons.propTypes = propTypes;
|
||||
|
||||
export default BinButtons;
|
167
common/app/Nav/components/NavLinks.jsx
Normal file
167
common/app/Nav/components/NavLinks.jsx
Normal file
@ -0,0 +1,167 @@
|
||||
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 = {
|
||||
clickOnMap: PropTypes.func.isRequired,
|
||||
closeDropdown: PropTypes.func.isRequired,
|
||||
isDropdownOpen: 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 (
|
||||
<NavDropdown
|
||||
id={ `nav-${content}-dropdown` }
|
||||
key={ content }
|
||||
noCaret={ true }
|
||||
onClick={ openDropdown }
|
||||
onMouseEnter={ openDropdown }
|
||||
onMouseLeave={ closeDropdown }
|
||||
onToggle={ isDropdownOpen ? closeDropdown : openDropdown }
|
||||
open={ isDropdownOpen }
|
||||
title={ content }
|
||||
>
|
||||
{ links.map(this.renderLink.bind(this, false)) }
|
||||
</NavDropdown>
|
||||
);
|
||||
}
|
||||
if (isReact) {
|
||||
return (
|
||||
<Link
|
||||
key={ content }
|
||||
onClick={ this.props[`handle${content}Click`] }
|
||||
to={ link }
|
||||
>
|
||||
<Component
|
||||
target={ target || null }
|
||||
>
|
||||
{ content }
|
||||
</Component>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Component
|
||||
href={ link }
|
||||
key={ content }
|
||||
onClick={ this.props[`handle${content}Click`] }
|
||||
target={ target || null }
|
||||
>
|
||||
{ content }
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
shouldShowMapButton,
|
||||
clickOnMap,
|
||||
showLoading,
|
||||
isSignedIn,
|
||||
navLinks
|
||||
} = this.props;
|
||||
return (
|
||||
<Nav id='nav-links' navbar={ true } pullRight={ true }>
|
||||
{
|
||||
shouldShowMapButton ?
|
||||
<NoPropsPassThrough>
|
||||
<li>
|
||||
<Link
|
||||
onClick={ clickOnMap }
|
||||
to={ onRouteCurrentChallenge() }
|
||||
>
|
||||
Map
|
||||
</Link>
|
||||
</li>
|
||||
</NoPropsPassThrough> :
|
||||
null
|
||||
}
|
||||
{
|
||||
navLinks.map(
|
||||
this.renderLink.bind(this, true)
|
||||
)
|
||||
}
|
||||
<SignUp
|
||||
showLoading={ showLoading }
|
||||
showSignUp={ !isSignedIn }
|
||||
/>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavLinks.displayName = 'NavLinks';
|
||||
NavLinks.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NavLinks);
|
39
common/app/Nav/components/NavLogo.jsx
Normal file
39
common/app/Nav/components/NavLogo.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavbarBrand } from 'react-bootstrap';
|
||||
|
||||
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 (
|
||||
<NavbarBrand>
|
||||
<a
|
||||
href='/challenges/current-challenge'
|
||||
onClick={ clickOnLogo }
|
||||
>
|
||||
<img
|
||||
alt='learn to code javascript at freeCodeCamp logo'
|
||||
className='img-responsive nav-logo logo'
|
||||
src={ fCClogo }
|
||||
/>
|
||||
<img
|
||||
alt='learn to code javascript at freeCodeCamp logo'
|
||||
className='img-responsive logo-glyph'
|
||||
src={ fCCglyph }
|
||||
/>
|
||||
</a>
|
||||
</NavbarBrand>
|
||||
);
|
||||
}
|
||||
|
||||
NavLogo.displayName = 'NavLogo';
|
||||
NavLogo.propTypes = propTypes;
|
||||
|
||||
export default NavLogo;
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { 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 = {
|
||||
showLoading: PropTypes.bool,
|
3
common/app/Nav/components/index.js
Normal file
3
common/app/Nav/components/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as BinButtons } from './BinButtons.jsx';
|
||||
export { default as NavLogo } from './NavLogo.jsx';
|
||||
export { default as NavLinks } from './NavLinks.jsx';
|
Reference in New Issue
Block a user