Show dropdown on hover
This commit is contained in:
committed by
Stuart Taylor
parent
09b8ff88fd
commit
3d93e70a73
@ -9,7 +9,9 @@ import {
|
|||||||
updateNavHeight,
|
updateNavHeight,
|
||||||
updateAppLang,
|
updateAppLang,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge
|
loadCurrentChallenge,
|
||||||
|
openDropdown,
|
||||||
|
closeDropdown
|
||||||
} from './redux/actions';
|
} from './redux/actions';
|
||||||
|
|
||||||
import { submitChallenge } from './routes/challenges/redux/actions';
|
import { submitChallenge } from './routes/challenges/redux/actions';
|
||||||
@ -25,16 +27,20 @@ const mapDispatchToProps = {
|
|||||||
submitChallenge,
|
submitChallenge,
|
||||||
updateAppLang,
|
updateAppLang,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge
|
loadCurrentChallenge,
|
||||||
|
openDropdown,
|
||||||
|
closeDropdown
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
userSelector,
|
||||||
|
state => state.app.isNavDropdownOpen,
|
||||||
state => state.app.isSignInAttempted,
|
state => state.app.isSignInAttempted,
|
||||||
state => state.app.toast,
|
state => state.app.toast,
|
||||||
state => state.challengesApp.toast,
|
state => state.challengesApp.toast,
|
||||||
(
|
(
|
||||||
{ user: { username, points, picture } },
|
{ user: { username, points, picture } },
|
||||||
|
isNavDropdownOpen,
|
||||||
isSignInAttempted,
|
isSignInAttempted,
|
||||||
toast,
|
toast,
|
||||||
) => ({
|
) => ({
|
||||||
@ -42,6 +48,7 @@ const mapStateToProps = createSelector(
|
|||||||
points,
|
points,
|
||||||
picture,
|
picture,
|
||||||
toast,
|
toast,
|
||||||
|
isNavDropdownOpen,
|
||||||
showLoading: !isSignInAttempted,
|
showLoading: !isSignInAttempted,
|
||||||
isSignedIn: !!username
|
isSignedIn: !!username
|
||||||
})
|
})
|
||||||
@ -62,7 +69,10 @@ const propTypes = {
|
|||||||
params: PropTypes.object,
|
params: PropTypes.object,
|
||||||
updateAppLang: PropTypes.func.isRequired,
|
updateAppLang: PropTypes.func.isRequired,
|
||||||
trackEvent: PropTypes.func.isRequired,
|
trackEvent: PropTypes.func.isRequired,
|
||||||
loadCurrentChallenge: PropTypes.func.isRequired
|
loadCurrentChallenge: PropTypes.func.isRequired,
|
||||||
|
openDropdown: PropTypes.func.isRequired,
|
||||||
|
closeDropdown: PropTypes.func.isRequired,
|
||||||
|
isNavDropdownOpen: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
// export plain class for testing
|
// export plain class for testing
|
||||||
@ -102,7 +112,10 @@ export class FreeCodeCamp extends React.Component {
|
|||||||
picture,
|
picture,
|
||||||
updateNavHeight,
|
updateNavHeight,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge
|
loadCurrentChallenge,
|
||||||
|
openDropdown,
|
||||||
|
closeDropdown,
|
||||||
|
isNavDropdownOpen
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const navProps = {
|
const navProps = {
|
||||||
username,
|
username,
|
||||||
@ -110,7 +123,10 @@ export class FreeCodeCamp extends React.Component {
|
|||||||
picture,
|
picture,
|
||||||
updateNavHeight,
|
updateNavHeight,
|
||||||
trackEvent,
|
trackEvent,
|
||||||
loadCurrentChallenge
|
loadCurrentChallenge,
|
||||||
|
openDropdown,
|
||||||
|
closeDropdown,
|
||||||
|
isNavDropdownOpen
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,14 +31,17 @@ function handleNavLinkEvent(content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
points: PropTypes.number,
|
closeDropdown: PropTypes.func.isRequired,
|
||||||
|
isNavDropdownOpen: PropTypes.bool,
|
||||||
|
loadCurrentChallenge: PropTypes.func.isRequired,
|
||||||
|
openDropdown: PropTypes.func.isRequired,
|
||||||
picture: PropTypes.string,
|
picture: PropTypes.string,
|
||||||
signedIn: PropTypes.bool,
|
points: PropTypes.number,
|
||||||
username: PropTypes.string,
|
|
||||||
updateNavHeight: PropTypes.func,
|
|
||||||
showLoading: PropTypes.bool,
|
showLoading: PropTypes.bool,
|
||||||
|
signedIn: PropTypes.bool,
|
||||||
trackEvent: PropTypes.func.isRequired,
|
trackEvent: PropTypes.func.isRequired,
|
||||||
loadCurrentChallenge: PropTypes.func.isRequired
|
updateNavHeight: PropTypes.func,
|
||||||
|
username: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FCCNav extends React.Component {
|
export default class FCCNav extends React.Component {
|
||||||
@ -80,12 +83,23 @@ export default class FCCNav extends React.Component {
|
|||||||
|
|
||||||
renderLink(isNavItem, { isReact, isDropdown, content, link, links, target }) {
|
renderLink(isNavItem, { isReact, isDropdown, content, link, links, target }) {
|
||||||
const Component = isNavItem ? NavItem : MenuItem;
|
const Component = isNavItem ? NavItem : MenuItem;
|
||||||
|
const {
|
||||||
|
isNavDropdownOpen,
|
||||||
|
openDropdown,
|
||||||
|
closeDropdown
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (isDropdown) {
|
if (isDropdown) {
|
||||||
return (
|
return (
|
||||||
<NavDropdown
|
<NavDropdown
|
||||||
id={ `nav-${content}-dropdown` }
|
id={ `nav-${content}-dropdown` }
|
||||||
key={ content }
|
key={ content }
|
||||||
noCaret={ true }
|
noCaret={ true }
|
||||||
|
onClick={ openDropdown }
|
||||||
|
onClose={ closeDropdown }
|
||||||
|
onMouseEnter={ openDropdown }
|
||||||
|
onMouseLeave={ closeDropdown }
|
||||||
|
open={ isNavDropdownOpen }
|
||||||
title={ content }
|
title={ content }
|
||||||
>
|
>
|
||||||
{ links.map(this.renderLink.bind(this, false)) }
|
{ links.map(this.renderLink.bind(this, false)) }
|
||||||
@ -150,17 +164,6 @@ export default class FCCNav extends React.Component {
|
|||||||
picture,
|
picture,
|
||||||
showLoading
|
showLoading
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let navLinksCache;
|
|
||||||
|
|
||||||
if (this._navLinksCache) {
|
|
||||||
navLinksCache = this._navLinksCache;
|
|
||||||
} else {
|
|
||||||
// we cache the rendered static links on the instance
|
|
||||||
// these do not change for the lifetime of the app
|
|
||||||
navLinksCache = this._navLinksCache = navLinks.map(
|
|
||||||
this.renderLink.bind(this, true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar
|
<Navbar
|
||||||
@ -188,7 +191,11 @@ export default class FCCNav extends React.Component {
|
|||||||
navbar={ true }
|
navbar={ true }
|
||||||
pullRight={ true }
|
pullRight={ true }
|
||||||
>
|
>
|
||||||
{ navLinksCache }
|
{
|
||||||
|
navLinks.map(
|
||||||
|
this.renderLink.bind(this, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
{ this.renderSignIn(username, points, picture, showLoading) }
|
{ this.renderSignIn(username, points, picture, showLoading) }
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
@ -197,5 +204,5 @@ export default class FCCNav extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FCCNav.displayName = 'Nav';
|
FCCNav.displayName = 'FCCNav';
|
||||||
FCCNav.propTypes = propTypes;
|
FCCNav.propTypes = propTypes;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import types from './types';
|
import types from './types';
|
||||||
|
import noop from 'lodash/noop';
|
||||||
|
|
||||||
const throwIfUndefined = () => {
|
const throwIfUndefined = () => {
|
||||||
throw new TypeError('Argument must not be of type `undefined`');
|
throw new TypeError('Argument must not be of type `undefined`');
|
||||||
@ -146,3 +147,6 @@ export const toggleNightMode = createAction(
|
|||||||
export const updateTheme = createAction(types.updateTheme);
|
export const updateTheme = createAction(types.updateTheme);
|
||||||
// addThemeToBody(theme: /night|default/) => Action
|
// addThemeToBody(theme: /night|default/) => Action
|
||||||
export const addThemeToBody = createAction(types.addThemeToBody);
|
export const addThemeToBody = createAction(types.addThemeToBody);
|
||||||
|
|
||||||
|
export const openDropdown = createAction(types.openDropdown, noop);
|
||||||
|
export const closeDropdown = createAction(types.closeDropdown, noop);
|
||||||
|
@ -52,6 +52,14 @@ export default handleActions(
|
|||||||
[types.delayedRedirect]: (state, { payload }) => ({
|
[types.delayedRedirect]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
delayedRedirect: payload
|
delayedRedirect: payload
|
||||||
|
}),
|
||||||
|
[types.openDropdown]: state => ({
|
||||||
|
...state,
|
||||||
|
isNavDropdownOpen: true
|
||||||
|
}),
|
||||||
|
[types.closeDropdown]: state => ({
|
||||||
|
...state,
|
||||||
|
isNavDropdownOpen: false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initialState
|
initialState
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import createTypes from '../utils/create-types';
|
import { createTypes } from 'redux-create-types';
|
||||||
|
|
||||||
export default createTypes([
|
export default createTypes([
|
||||||
'analytics',
|
'analytics',
|
||||||
@ -33,5 +33,9 @@ export default createTypes([
|
|||||||
// night mode
|
// night mode
|
||||||
'toggleNightMode',
|
'toggleNightMode',
|
||||||
'updateTheme',
|
'updateTheme',
|
||||||
'addThemeToBody'
|
'addThemeToBody',
|
||||||
|
|
||||||
|
// nav
|
||||||
|
'openDropdown',
|
||||||
|
'closeDropdown'
|
||||||
], 'app');
|
], 'app');
|
||||||
|
@ -108,6 +108,7 @@
|
|||||||
"react-youtube": "^7.0.0",
|
"react-youtube": "^7.0.0",
|
||||||
"redux": "^3.0.5",
|
"redux": "^3.0.5",
|
||||||
"redux-actions": "^0.13.0",
|
"redux-actions": "^0.13.0",
|
||||||
|
"redux-create-types": "0.0.1",
|
||||||
"redux-epic": "^0.1.1",
|
"redux-epic": "^0.1.1",
|
||||||
"redux-form": "^5.2.3",
|
"redux-form": "^5.2.3",
|
||||||
"request": "^2.65.0",
|
"request": "^2.65.0",
|
||||||
|
Reference in New Issue
Block a user