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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,21 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
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 { NavigationContext } from '../../contexts/GuideNavigationContext';
import SideNav from './components/guide/SideNav';
import Spacer from '../helpers/Spacer';
import 'prismjs/themes/prism.css';
import './guide.css';
// import { expandedState, displaySideNav } from '../../redux';
import { toggleExpandedState, toggleDisplaySideNav } from './redux';
const propTypes = {
children: PropTypes.any,
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 {
getContentRef = ref => (this.contentRef = ref);
@ -38,6 +58,12 @@ class GuideLayout extends React.Component {
};
render() {
let {
displaySideNav,
expandedState,
toggleExpandedState,
toggleDisplaySideNav
} = this.props;
return (
<StaticQuery
query={graphql`
@ -60,49 +86,40 @@ class GuideLayout extends React.Component {
const { edges } = data.allNavigationNode;
const pages = edges.map(edge => edge.node);
return (
<NavigationContext>
{({
toggleDisplaySideNav,
displaySideNav,
expandedState,
toggleExpandedState
}) => (
<Fragment>
<Spacer size={1} />
<Grid className='guide-container'>
<Row>
<Col
md={4}
smHidden={!displaySideNav}
xsHidden={!displaySideNav}
>
<SideNav
expandedState={expandedState}
onNavigate={this.handleNavigation}
pages={pages}
toggleDisplaySideNav={toggleDisplaySideNav}
toggleExpandedState={toggleExpandedState}
/>
</Col>
<Col
md={8}
smHidden={displaySideNav}
xsHidden={displaySideNav}
>
<main
className='content'
id='main'
ref={this.getContentRef}
tabIndex='-1'
>
{this.props.children}
</main>
</Col>
</Row>
</Grid>
</Fragment>
)}
</NavigationContext>
<Fragment>
<Spacer size={1} />
<Grid className='guide-container'>
<Row>
<Col
md={4}
smHidden={!displaySideNav}
xsHidden={!displaySideNav}
>
<SideNav
expandedState={expandedState}
onNavigate={this.handleNavigation}
pages={pages}
toggleDisplaySideNav={toggleDisplaySideNav}
toggleExpandedState={toggleExpandedState}
/>
</Col>
<Col
md={8}
smHidden={displaySideNav}
xsHidden={displaySideNav}
>
<main
className='content'
id='main'
ref={this.getContentRef}
tabIndex='-1'
>
{this.props.children}
</main>
</Col>
</Row>
</Grid>
</Fragment>
);
}}
/>
@ -113,4 +130,7 @@ class GuideLayout extends React.Component {
GuideLayout.displayName = 'GuideLayout';
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);
}
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) {
if (!parents) {
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,
ns as flashNameSpace
} 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 curriculumMap,
@ -21,6 +25,7 @@ export default combineReducers({
[challengeNameSpace]: challenge,
[curriculumMapNameSpace]: curriculumMap,
[flashNameSpace]: flash,
[guideNavNameSpace]: guideNav,
form: formReducer,
[settingsNameSpace]: settings
});