fix: custom menu for guide

This commit is contained in:
Ahmad Abdolsaheb
2019-02-20 15:41:33 +03:00
committed by Stuart Taylor
parent 44a2ce8796
commit 357b824033
8 changed files with 184 additions and 66 deletions

View File

@ -4,7 +4,7 @@ import { Provider } from 'react-redux';
import { createStore } from './src/redux/createStore'; import { createStore } from './src/redux/createStore';
import AppMountNotifier from './src/components/AppMountNotifier'; import AppMountNotifier from './src/components/AppMountNotifier';
import GuideNavContextProvider from './src/contexts/GuideNavigationContext';
import { import {
CertificationLayout, CertificationLayout,
DefaultLayout, DefaultLayout,
@ -16,9 +16,7 @@ const store = createStore();
export const wrapRootElement = ({ element }) => { export const wrapRootElement = ({ element }) => {
return ( return (
<Provider store={store}> <Provider store={store}>
<GuideNavContextProvider> <AppMountNotifier render={() => element} />
<AppMountNotifier render={() => element} />
</GuideNavContextProvider>
</Provider> </Provider>
); );
}; };
@ -43,7 +41,7 @@ export const wrapPageElement = ({ element, props }) => {
} }
if (/^\/guide(\/.*)*/.test(pathname)) { if (/^\/guide(\/.*)*/.test(pathname)) {
return ( return (
<DefaultLayout> <DefaultLayout showMobileSidenav={true}>
<GuideLayout>{element}</GuideLayout> <GuideLayout>{element}</GuideLayout>
</DefaultLayout> </DefaultLayout>
); );

View File

@ -6,7 +6,6 @@ import { Provider } from 'react-redux';
import headComponents from './src/head'; import headComponents from './src/head';
import { createStore } from './src/redux/createStore'; import { createStore } from './src/redux/createStore';
import GuideNavContextProvider from './src/contexts/GuideNavigationContext';
import { import {
CertificationLayout, CertificationLayout,
DefaultLayout, DefaultLayout,
@ -16,11 +15,7 @@ import {
const store = createStore(); const store = createStore();
export const wrapRootElement = ({ element }) => { export const wrapRootElement = ({ element }) => {
return ( return <Provider store={store}>{element}</Provider>;
<Provider store={store}>
<GuideNavContextProvider>{element}</GuideNavContextProvider>
</Provider>
);
}; };
wrapRootElement.propTypes = { wrapRootElement.propTypes = {
@ -43,7 +38,7 @@ export const wrapPageElement = ({ element, props }) => {
} }
if (/^\/guide(\/.*)*/.test(pathname)) { if (/^\/guide(\/.*)*/.test(pathname)) {
return ( return (
<DefaultLayout> <DefaultLayout showMobileSidenav={true}>
<GuideLayout>{element}</GuideLayout> <GuideLayout>{element}</GuideLayout>
</DefaultLayout> </DefaultLayout>
); );

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Media from 'react-media'; import Media from 'react-media';
import FCCSearch from 'react-freecodecamp-search'; import FCCSearch from 'react-freecodecamp-search';
@ -9,6 +11,17 @@ import { Link } from '../helpers';
import './header.css'; import './header.css';
import { toggleDisplaySideNav } from '../layouts/redux';
const mapDispatchToProps = dispatch =>
bindActionCreators({ toggleDisplaySideNav }, dispatch);
const propTypes = {
disableSettings: PropTypes.bool,
showMobileSidenav: PropTypes.bool,
toggleDisplaySideNav: PropTypes.func
};
class Header extends Component { class Header extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -27,6 +40,9 @@ class Header extends Component {
} }
toggleClass = () => { toggleClass = () => {
if (this.props.showMobileSidenav) {
this.props.toggleDisplaySideNav();
}
this.setState({ this.setState({
isMenuOpened: !this.state.isMenuOpened isMenuOpened: !this.state.isMenuOpened
}); });
@ -35,7 +51,8 @@ class Header extends Component {
handleClickOutside = event => { handleClickOutside = event => {
if ( if (
this.state.isMenuOpened && this.state.isMenuOpened &&
!this.menuButtonRef.current.contains(event.target) !this.menuButtonRef.current.contains(event.target) &&
!this.props.showMobileSidenav
) { ) {
this.toggleClass(); this.toggleClass();
} }
@ -48,7 +65,27 @@ class Header extends Component {
}; };
render() { render() {
const { disableSettings } = this.props; const { disableSettings, showMobileSidenav } = this.props;
if (this.state.isMenuOpened && showMobileSidenav) {
return (
<header>
<nav id='top-nav'>
<Link className='home-link' to='/'>
<NavLogo />
</Link>
{disableSettings ? null : <FCCSearch />}
<span
className='menu-button'
onClick={this.toggleClass}
ref={this.menuButtonRef}
>
Menu
</span>
</nav>
</header>
);
}
return ( return (
<header className={this.state.isMenuOpened ? 'opened' : null}> <header className={this.state.isMenuOpened ? 'opened' : null}>
<nav id='top-nav'> <nav id='top-nav'>
@ -88,7 +125,9 @@ class Header extends Component {
} }
} }
Header.propTypes = { Header.propTypes = propTypes;
disableSettings: PropTypes.bool
}; export default connect(
export default Header; null,
mapDispatchToProps
)(Header);

View File

@ -71,7 +71,8 @@ const propTypes = {
landingPage: PropTypes.bool, landingPage: PropTypes.bool,
onlineStatusChange: PropTypes.func.isRequired, onlineStatusChange: PropTypes.func.isRequired,
removeFlashMessage: PropTypes.func.isRequired, removeFlashMessage: PropTypes.func.isRequired,
showFooter: PropTypes.bool showFooter: PropTypes.bool,
showMobileSidenav: PropTypes.bool
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@ -140,6 +141,7 @@ class DefaultLayout extends Component {
removeFlashMessage, removeFlashMessage,
landingPage, landingPage,
showFooter = true, showFooter = true,
showMobileSidenav = false,
isOnline, isOnline,
isSignedIn isSignedIn
} = this.props; } = this.props;
@ -158,7 +160,10 @@ class DefaultLayout extends Component {
> >
<style>{fontawesome.dom.css()}</style> <style>{fontawesome.dom.css()}</style>
</Helmet> </Helmet>
<Header disableSettings={disableSettings} /> <Header
disableSettings={disableSettings}
showMobileSidenav={showMobileSidenav}
/>
<div className={`default-layout ${landingPage ? 'landing-page' : ''}`}> <div className={`default-layout ${landingPage ? 'landing-page' : ''}`}>
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} /> <OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
{hasMessages ? ( {hasMessages ? (

View File

@ -1,15 +1,21 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StaticQuery, graphql } from 'gatsby'; import { StaticQuery, graphql } from 'gatsby';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// import { createSelector } from 'reselect';
import { Grid, Col, Row } from '@freecodecamp/react-bootstrap'; import { Grid, Col, Row } from '@freecodecamp/react-bootstrap';
import { NavigationContext } from '../../contexts/GuideNavigationContext';
import SideNav from './components/guide/SideNav'; import SideNav from './components/guide/SideNav';
import Spacer from '../helpers/Spacer'; import Spacer from '../helpers/Spacer';
import 'prismjs/themes/prism.css'; import 'prismjs/themes/prism.css';
import './guide.css'; import './guide.css';
// import { expandedState, displaySideNav } from '../../redux';
import { toggleExpandedState, toggleDisplaySideNav } from './redux';
const propTypes = { const propTypes = {
children: PropTypes.any, children: PropTypes.any,
data: PropTypes.shape({ data: PropTypes.shape({
@ -26,9 +32,23 @@ const propTypes = {
) )
}) })
}), }),
location: PropTypes.object displaySideNav: PropTypes.bool,
expandedState: PropTypes.object,
location: PropTypes.object,
toggleDisplaySideNav: PropTypes.func,
toggleExpandedState: PropTypes.func
}; };
const mapStateToProps = state => {
return {
expandedState: state.guideNav.expandedState,
displaySideNav: state.guideNav.displaySideNav
};
};
const mapDispatchToProps = dispatch =>
bindActionCreators({ toggleExpandedState, toggleDisplaySideNav }, dispatch);
class GuideLayout extends React.Component { class GuideLayout extends React.Component {
getContentRef = ref => (this.contentRef = ref); getContentRef = ref => (this.contentRef = ref);
@ -38,6 +58,12 @@ class GuideLayout extends React.Component {
}; };
render() { render() {
let {
displaySideNav,
expandedState,
toggleExpandedState,
toggleDisplaySideNav
} = this.props;
return ( return (
<StaticQuery <StaticQuery
query={graphql` query={graphql`
@ -60,49 +86,40 @@ class GuideLayout extends React.Component {
const { edges } = data.allNavigationNode; const { edges } = data.allNavigationNode;
const pages = edges.map(edge => edge.node); const pages = edges.map(edge => edge.node);
return ( return (
<NavigationContext> <Fragment>
{({ <Spacer size={1} />
toggleDisplaySideNav, <Grid className='guide-container'>
displaySideNav, <Row>
expandedState, <Col
toggleExpandedState md={4}
}) => ( smHidden={!displaySideNav}
<Fragment> xsHidden={!displaySideNav}
<Spacer size={1} /> >
<Grid className='guide-container'> <SideNav
<Row> expandedState={expandedState}
<Col onNavigate={this.handleNavigation}
md={4} pages={pages}
smHidden={!displaySideNav} toggleDisplaySideNav={toggleDisplaySideNav}
xsHidden={!displaySideNav} toggleExpandedState={toggleExpandedState}
> />
<SideNav </Col>
expandedState={expandedState} <Col
onNavigate={this.handleNavigation} md={8}
pages={pages} smHidden={displaySideNav}
toggleDisplaySideNav={toggleDisplaySideNav} xsHidden={displaySideNav}
toggleExpandedState={toggleExpandedState} >
/> <main
</Col> className='content'
<Col id='main'
md={8} ref={this.getContentRef}
smHidden={displaySideNav} tabIndex='-1'
xsHidden={displaySideNav} >
> {this.props.children}
<main </main>
className='content' </Col>
id='main' </Row>
ref={this.getContentRef} </Grid>
tabIndex='-1' </Fragment>
>
{this.props.children}
</main>
</Col>
</Row>
</Grid>
</Fragment>
)}
</NavigationContext>
); );
}} }}
/> />
@ -113,4 +130,7 @@ class GuideLayout extends React.Component {
GuideLayout.displayName = 'GuideLayout'; GuideLayout.displayName = 'GuideLayout';
GuideLayout.propTypes = propTypes; GuideLayout.propTypes = propTypes;
export default GuideLayout; export default connect(
mapStateToProps,
mapDispatchToProps
)(GuideLayout);

View File

@ -27,6 +27,30 @@ class SideNav extends Component {
this.renderParent = this.renderParent.bind(this); this.renderParent = this.renderParent.bind(this);
} }
componentDidMount() {
if (typeof window !== 'undefined') {
const pathMap = window.location.pathname
.slice(1)
.split('/')
.slice(0, -1)
.reduce((map, current, i, pathArray) => {
const path =
i !== 0 ? map[pathArray[i - 1]] + `/${current}` : `/${current}`;
return {
...map,
[current]: path
};
}, {});
return Object.keys(pathMap)
.map(key => pathMap[key])
.forEach(path => {
this.props.toggleExpandedState(path);
});
}
return null;
}
renderPanels(parents, pages) { renderPanels(parents, pages) {
if (!parents) { if (!parents) {
return 'No Parents Here'; return 'No Parents Here';

View File

@ -0,0 +1,32 @@
import { createAction, handleActions } from 'redux-actions';
import { createTypes } from '../../../utils/createTypes';
export const ns = 'guideNav';
const initialState = {
displaySideNav: false,
expandedState: {}
};
const types = createTypes(['toggleExpandedState', 'toggleDisplaySideNav'], ns);
export const toggleExpandedState = createAction(types.toggleExpandedState);
export const toggleDisplaySideNav = createAction(types.toggleDisplaySideNav);
export const reducer = handleActions(
{
[types.toggleExpandedState]: (state, { payload }) => ({
...state,
expandedState: {
...state.expandedState,
[payload]: !state.expandedState[payload]
}
}),
[types.toggleDisplaySideNav]: state => ({
...state,
displaySideNav: !state.displaySideNav
})
},
initialState
);

View File

@ -6,6 +6,10 @@ import {
reducer as flash, reducer as flash,
ns as flashNameSpace ns as flashNameSpace
} from '../components/Flash/redux'; } from '../components/Flash/redux';
import {
reducer as guideNav,
ns as guideNavNameSpace
} from '../components/layouts/redux';
import { reducer as settings, ns as settingsNameSpace } from './settings'; import { reducer as settings, ns as settingsNameSpace } from './settings';
import { import {
reducer as curriculumMap, reducer as curriculumMap,
@ -21,6 +25,7 @@ export default combineReducers({
[challengeNameSpace]: challenge, [challengeNameSpace]: challenge,
[curriculumMapNameSpace]: curriculumMap, [curriculumMapNameSpace]: curriculumMap,
[flashNameSpace]: flash, [flashNameSpace]: flash,
[guideNavNameSpace]: guideNav,
form: formReducer, form: formReducer,
[settingsNameSpace]: settings [settingsNameSpace]: settings
}); });