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, 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 (

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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');

View File

@ -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",