Show dropdown on hover

This commit is contained in:
Berkeley Martinez
2017-01-02 23:04:07 -08:00
committed by Stuart Taylor
parent 09b8ff88fd
commit 3d93e70a73
6 changed files with 65 additions and 25 deletions

View File

@ -9,7 +9,9 @@ import {
updateNavHeight,
updateAppLang,
trackEvent,
loadCurrentChallenge
loadCurrentChallenge,
openDropdown,
closeDropdown
} from './redux/actions';
import { submitChallenge } from './routes/challenges/redux/actions';
@ -25,16 +27,20 @@ const mapDispatchToProps = {
submitChallenge,
updateAppLang,
trackEvent,
loadCurrentChallenge
loadCurrentChallenge,
openDropdown,
closeDropdown
};
const mapStateToProps = createSelector(
userSelector,
state => state.app.isNavDropdownOpen,
state => state.app.isSignInAttempted,
state => state.app.toast,
state => state.challengesApp.toast,
(
{ user: { username, points, picture } },
isNavDropdownOpen,
isSignInAttempted,
toast,
) => ({
@ -42,6 +48,7 @@ const mapStateToProps = createSelector(
points,
picture,
toast,
isNavDropdownOpen,
showLoading: !isSignInAttempted,
isSignedIn: !!username
})
@ -62,7 +69,10 @@ const propTypes = {
params: PropTypes.object,
updateAppLang: 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
@ -102,7 +112,10 @@ export class FreeCodeCamp extends React.Component {
picture,
updateNavHeight,
trackEvent,
loadCurrentChallenge
loadCurrentChallenge,
openDropdown,
closeDropdown,
isNavDropdownOpen
} = this.props;
const navProps = {
username,
@ -110,7 +123,10 @@ export class FreeCodeCamp extends React.Component {
picture,
updateNavHeight,
trackEvent,
loadCurrentChallenge
loadCurrentChallenge,
openDropdown,
closeDropdown,
isNavDropdownOpen
};
return (

View File

@ -31,14 +31,17 @@ function handleNavLinkEvent(content) {
}
const propTypes = {
points: PropTypes.number,
closeDropdown: PropTypes.func.isRequired,
isNavDropdownOpen: PropTypes.bool,
loadCurrentChallenge: PropTypes.func.isRequired,
openDropdown: PropTypes.func.isRequired,
picture: PropTypes.string,
signedIn: PropTypes.bool,
username: PropTypes.string,
updateNavHeight: PropTypes.func,
points: PropTypes.number,
showLoading: PropTypes.bool,
signedIn: PropTypes.bool,
trackEvent: PropTypes.func.isRequired,
loadCurrentChallenge: PropTypes.func.isRequired
updateNavHeight: PropTypes.func,
username: PropTypes.string
};
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 }) {
const Component = isNavItem ? NavItem : MenuItem;
const {
isNavDropdownOpen,
openDropdown,
closeDropdown
} = this.props;
if (isDropdown) {
return (
<NavDropdown
id={ `nav-${content}-dropdown` }
key={ content }
noCaret={ true }
onClick={ openDropdown }
onClose={ closeDropdown }
onMouseEnter={ openDropdown }
onMouseLeave={ closeDropdown }
open={ isNavDropdownOpen }
title={ content }
>
{ links.map(this.renderLink.bind(this, false)) }
@ -150,17 +164,6 @@ export default class FCCNav extends React.Component {
picture,
showLoading
} = 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 (
<Navbar
@ -188,7 +191,11 @@ export default class FCCNav extends React.Component {
navbar={ true }
pullRight={ true }
>
{ navLinksCache }
{
navLinks.map(
this.renderLink.bind(this, true)
)
}
{ this.renderSignIn(username, points, picture, showLoading) }
</Nav>
</Navbar.Collapse>
@ -197,5 +204,5 @@ export default class FCCNav extends React.Component {
}
}
FCCNav.displayName = 'Nav';
FCCNav.displayName = 'FCCNav';
FCCNav.propTypes = propTypes;

View File

@ -1,6 +1,7 @@
import { Observable } from 'rx';
import { createAction } from 'redux-actions';
import types from './types';
import noop from 'lodash/noop';
const throwIfUndefined = () => {
throw new TypeError('Argument must not be of type `undefined`');
@ -146,3 +147,6 @@ export const toggleNightMode = createAction(
export const updateTheme = createAction(types.updateTheme);
// addThemeToBody(theme: /night|default/) => Action
export const addThemeToBody = createAction(types.addThemeToBody);
export const openDropdown = createAction(types.openDropdown, noop);
export const closeDropdown = createAction(types.closeDropdown, noop);

View File

@ -52,6 +52,14 @@ export default handleActions(
[types.delayedRedirect]: (state, { payload }) => ({
...state,
delayedRedirect: payload
}),
[types.openDropdown]: state => ({
...state,
isNavDropdownOpen: true
}),
[types.closeDropdown]: state => ({
...state,
isNavDropdownOpen: false
})
},
initialState

View File

@ -1,4 +1,4 @@
import createTypes from '../utils/create-types';
import { createTypes } from 'redux-create-types';
export default createTypes([
'analytics',
@ -33,5 +33,9 @@ export default createTypes([
// night mode
'toggleNightMode',
'updateTheme',
'addThemeToBody'
'addThemeToBody',
// nav
'openDropdown',
'closeDropdown'
], 'app');

View File

@ -108,6 +108,7 @@
"react-youtube": "^7.0.0",
"redux": "^3.0.5",
"redux-actions": "^0.13.0",
"redux-create-types": "0.0.1",
"redux-epic": "^0.1.1",
"redux-form": "^5.2.3",
"request": "^2.65.0",