fix: custom menu for guide
This commit is contained in:
committed by
Stuart Taylor
parent
44a2ce8796
commit
357b824033
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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);
|
||||||
|
@ -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';
|
||||||
|
32
client/src/components/layouts/redux/index.js
Normal file
32
client/src/components/layouts/redux/index.js
Normal 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
|
||||||
|
);
|
@ -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
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user