feat: add toggle nav (#36956)

* feat: re-add toggle menu

* Update client/src/components/Header/components/universalNav.css

Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* Update client/src/components/Header/components/universalNav.css

Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* Update client/src/components/Header/components/universalNav.css

Co-Authored-By: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* fix: fix lint error
This commit is contained in:
Ahmad Abdolsaheb
2019-10-04 18:00:17 +03:00
committed by mrugesh
parent 2066ed674b
commit f9a112b43e
7 changed files with 250 additions and 91 deletions

View File

@ -2,19 +2,17 @@
import React from 'react'; import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow'; import ShallowRenderer from 'react-test-renderer/shallow';
import TestRenderer from 'react-test-renderer'; import TestRenderer from 'react-test-renderer';
import { UniversalNav } from './components/UniversalNav';
import Header from './';
import NavLinks from './components/NavLinks'; import NavLinks from './components/NavLinks';
describe('<Header />', () => { describe('<UniversalNav />', () => {
it('renders to the DOM', () => { it('renders to the DOM', () => {
const shallow = new ShallowRenderer(); const shallow = new ShallowRenderer();
shallow.render(<Header />); shallow.render(<UniversalNav {...UniversalNavProps} />);
const result = shallow.getRenderOutput(); const result = shallow.getRenderOutput();
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
}); });
describe('<NavLinks />', () => { describe('<NavLinks />', () => {
const root = TestRenderer.create(<NavLinks />).root; const root = TestRenderer.create(<NavLinks />).root;
const aTags = root.findAllByType('a'); const aTags = root.findAllByType('a');
@ -25,17 +23,23 @@ describe('<NavLinks />', () => {
return acc; return acc;
}, []); }, []);
const expectedLinks = ['/', '/portfolio']; const expectedLinks = ['/learn', '/', '/portfolio'];
it('renders to the DOM', () => { it('renders to the DOM', () => {
expect(root).toBeTruthy(); expect(root).toBeTruthy();
}); });
it('has 3 a tags', () => { it('has 3 links', () => {
expect(aTags.length === 3).toBeTruthy(); expect(aTags.length === 3).toBeTruthy();
}); });
it('has link to portfolio', () => { it('has links to learn, main, and portfolio', () => {
// checks if all links in expected links exist in links // checks if all links in expected links exist in links
expect(expectedLinks.every(elem => links.indexOf(elem) > -1)).toBeTruthy(); expect(expectedLinks.every(elem => links.indexOf(elem) > -1)).toBeTruthy();
}); });
}); });
const UniversalNavProps = {
displayMenu: false,
menuButtonRef: {},
toggleDisplayMenu: function() {}
};

View File

@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
const MenuButton = React.forwardRef((props, ref) => {
return (
<button
aria-expanded={props.displayMenu}
className={
'toggle-button-nav' + (props.displayMenu ? ' reverse-toggle-color' : '')
}
onClick={props.onClick}
ref={ref}
>
Menu
</button>
);
});
MenuButton.displayName = 'MenuButton';
MenuButton.propTypes = {
className: PropTypes.string,
displayMenu: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired
};
export default MenuButton;

View File

@ -1,16 +1,25 @@
import React from 'react'; import React from 'react';
import { Link } from '../../helpers'; import { Link } from '../../helpers';
export function NavLinks() { import PropTypes from 'prop-types';
const propTypes = {
displayMenu: PropTypes.bool
};
function NavLinks({ displayMenu }) {
return ( return (
<div className='main-nav-group'> <div className='main-nav-group'>
<ul className={'nav-list display-flex'} role='menu'> <ul
<li className='nav-theme' role='menuitem'> className={'nav-list' + (displayMenu ? ' display-flex' : '')}
<Link to='/learn'>Projects</Link> role='menu'
</li> >
<li className='nav-theme' role='menuitem'> <li className='nav-theme' role='menuitem'>
<Link to='/'>Light</Link> <Link to='/'>Light</Link>
</li> </li>
<li className='nav-projects' role='menuitem'>
<Link to='/learn'>Projects</Link>
</li>
<li className='nav-portfolio' role='menuitem'> <li className='nav-portfolio' role='menuitem'>
<Link to='/portfolio'>Portfolio</Link> <Link to='/portfolio'>Portfolio</Link>
</li> </li>
@ -19,5 +28,6 @@ export function NavLinks() {
); );
} }
NavLinks.propTypes = propTypes;
NavLinks.displayName = 'NavLinks'; NavLinks.displayName = 'NavLinks';
export default NavLinks; export default NavLinks;

View File

@ -1,27 +1,48 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Link } from '../../helpers'; import { Link } from '../../helpers';
import NavLogo from './NavLogo'; import NavLogo from './NavLogo';
import SearchBar from '../../search/searchBar/SearchBar'; import SearchBar from '../../search/searchBar/SearchBar';
import MenuButton from './MenuButton';
import NavLinks from './NavLinks'; import NavLinks from './NavLinks';
import './universalNav.css'; import './universalNav.css';
export function UniversalNav() { export const UniversalNav = React.forwardRef(
return ( ({ displayMenu, toggleDisplayMenu }, ref) => (
<nav className={'universal-nav nav-padding'} id='universal-nav'> <nav
<div className={'universal-nav-left'}> className={
'universal-nav nav-padding' + (displayMenu ? ' expand-nav' : '')
}
id='universal-nav'
>
<div
className={'universal-nav-left' + (displayMenu ? ' display-flex' : '')}
>
<SearchBar /> <SearchBar />
</div> </div>
<div className='universal-nav-middle'> <div className='universal-nav-middle'>
<Link className='universal-nav-logo' to='/'> <Link id='universal-nav-logo' to='/'>
<NavLogo /> <NavLogo />
</Link> </Link>
</div> </div>
<div className='universal-nav-right main-nav'> <div className='universal-nav-right main-nav'>
<NavLinks /> <NavLinks displayMenu={displayMenu} />
</div> </div>
<MenuButton
displayMenu={displayMenu}
onClick={toggleDisplayMenu}
ref={ref}
/>
</nav> </nav>
); )
} );
UniversalNav.displayName = 'UniversalNav'; UniversalNav.displayName = 'UniversalNav';
export default UniversalNav; export default UniversalNav;
UniversalNav.propTypes = {
displayMenu: PropTypes.bool,
menuButtonRef: PropTypes.object,
toggleDisplayMenu: PropTypes.func
};

View File

@ -7,7 +7,7 @@
height: 40px; height: 40px;
font-size: 18px; font-size: 18px;
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
height: calc(2 * var(--header-height)); height: var(--header-height);
background: var(--theme-color); background: var(--theme-color);
position: absolute; position: absolute;
z-index: 1000; z-index: 1000;
@ -31,28 +31,25 @@
letter-spacing: 0.4px; letter-spacing: 0.4px;
white-space: nowrap; white-space: nowrap;
-ms-overflow-scrolling: touch; -ms-overflow-scrolling: touch;
margin-right: 0;
} }
.universal-nav-logo { #universal-nav-logo {
flex-shrink: 0;
display: block;
margin: 0px;
color: var(--gray-00); color: var(--gray-00);
font-size: 1.7rem; font-size: 1.7rem;
line-height: 1em; line-height: 1em;
font-weight: bold; font-weight: bold;
letter-spacing: -0.5px; letter-spacing: -0.5px;
display: flex;
flex-shrink: 0;
position: absolute;
left: 15px;
top: 0px;
} }
.universal-nav-logo:hover { #universal-nav-logo:hover {
text-decoration: none; text-decoration: none;
background-color: var(--theme-color); background-color: var(--theme-color);
} }
.universal-nav-logo img { #universal-nav-logo img {
display: block; display: block;
width: auto; width: auto;
height: 25px; height: 25px;
@ -67,7 +64,7 @@
} }
.nav-list { .nav-list {
height: var(--header-height); height: 38px;
} }
.nav-list li { .nav-list li {
@ -83,7 +80,6 @@
color: var(--gray-00); color: var(--gray-00);
opacity: 1; opacity: 1;
white-space: nowrap; white-space: nowrap;
height: var(--header-height);
} }
.nav-list li:hover { .nav-list li:hover {
@ -103,43 +99,115 @@
height: 38px; height: 38px;
} }
@media (max-width: 380px) { .toggle-button-nav {
.universal-nav { display: none;
padding: 0px 5px; padding: 2px 14px 2px;
} border: 1px solid var(--gray-00);
.universal-nav-logo { font-family: 'lato', sans-serif;
left: 5px; font-size: 18px;
} color: var(--gray-00);
outline: none;
background-color: var(--theme-color);
cursor: pointer;
margin-top: 4px;
height: auto;
}
@media (max-width: 265px) {
.nav-list li a { .nav-list li a {
padding: 8px 5px; width: 50vw;
} }
} }
@media (min-width: 700px) { @media (max-width: 799px) {
.universal-nav-logo { .site-header {
padding-right: 0;
padding-left: 0;
}
.universal-nav-middle {
margin-right: 0;
}
.universal-nav-left {
display: none;
}
.nav-list {
display: none;
position: absolute;
background-color: var(--theme-color);
top: calc(var(--header-height) * 2);
right: 0px;
flex-wrap: wrap;
justify-content: flex-end;
width: 100vw;
height: auto;
}
.display-flex {
display: flex;
}
.toggle-button-nav {
display: flex;
}
.expand-nav {
min-height: calc(3 * var(--header-height));
}
.reverse-toggle-color {
background-color: var(--gray-00);
color: var(--theme-color);
}
.universal-nav-left form {
display: none;
}
.fcc_searchBar .ais-SearchBox-form {
display: flex;
position: absolute;
top: var(--header-height);
left: 15px;
}
#universal-nav-logo {
display: flex;
position: absolute;
left: 17px;
top: 0px;
}
}
@media (min-width: 800px) {
.universal-nav-middle {
flex: 1 0 30%;
margin-right: 0px;
}
.universal-nav-left {
display: flex;
flex: 1 0 35%;
margin-left: 0px;
}
#universal-nav-logo {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
position: static; }
left: auto; .universal-nav-right {
top: auto; flex: 1 0 35%;
margin-left: auto;
} }
.main-nav-group { .main-nav-group {
margin-left: auto; margin-left: auto;
} }
}
@media (min-width: 1500px) {
.universal-nav-middle { .universal-nav-middle {
flex: 1 0 33%; flex: 1 0 33%;
margin-right: 0;
margin-left: 0;
} }
.universal-nav-left { .universal-nav-left {
flex: 1 0 33%; flex: 1 0 33%;
margin-left: 0px;
} }
.universal-nav-right { .universal-nav-right {
flex: 1 0 33%; flex: 1 0 33%;
margin-left: auto;
}
.universal-nav {
height: var(--header-height);
} }
} }

View File

@ -5,17 +5,56 @@ import UniversalNav from './components/UniversalNav';
import './header.css'; import './header.css';
export function Header() { export class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
displayMenu: false
};
this.menuButtonRef = React.createRef();
this.handleClickOutside = this.handleClickOutside.bind(this);
this.toggleDisplayMenu = this.toggleDisplayMenu.bind(this);
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside);
}
handleClickOutside(event) {
if (
this.state.displayMenu &&
this.menuButtonRef.current &&
!this.menuButtonRef.current.contains(event.target) &&
event.target.id !== 'fcc_instantsearch'
) {
this.toggleDisplayMenu();
}
}
toggleDisplayMenu() {
this.setState(({ displayMenu }) => ({ displayMenu: !displayMenu }));
}
render() {
const { displayMenu } = this.state;
return ( return (
<> <>
<Helmet> <Helmet>
<style>{':root{--header-height: 38px}'}</style> <style>{':root{--header-height: 38px}'}</style>
</Helmet> </Helmet>
<header> <header>
<UniversalNav /> <UniversalNav
displayMenu={displayMenu}
ref={this.menuButtonRef}
toggleDisplayMenu={this.toggleDisplayMenu}
/>
</header> </header>
</> </>
); );
}
} }
Header.displayName = 'Header'; Header.displayName = 'Header';

View File

@ -29,6 +29,12 @@
color: var(--gray-00); color: var(--gray-00);
} }
.fcc_searchBar .ais-Hits {
top: 71px;
width: calc(100vw - 30px);
left: 15px;
}
.fcc_searchBar .ais-SearchBox-form { .fcc_searchBar .ais-SearchBox-form {
margin-bottom: 0; margin-bottom: 0;
display: flex; display: flex;
@ -37,12 +43,6 @@
right: 5px; right: 5px;
} }
.fcc_searchBar .ais-Hits {
top: 71px;
width: calc(100vw - 10px);
left: 5px;
}
/* hits */ /* hits */
.fcc_searchBar .ais-Highlight-highlighted { .fcc_searchBar .ais-Highlight-highlighted {
background-color: transparent; background-color: transparent;
@ -103,23 +103,18 @@
font-weight: bold; font-weight: bold;
} }
@media (min-width: 380px) { .ais-SearchBox-input {
.fcc_searchBar .ais-Hits {
width: calc(100vw - 30px); width: calc(100vw - 30px);
left: 15px; }
}
.ais-SearchBox-input { .fcc_searchBar .ais-SearchBox-form {
width: calc(100vw - 30px);
}
.fcc_searchBar .ais-SearchBox-form {
display: flex; display: flex;
position: absolute; position: absolute;
top: var(--header-height); top: var(--header-height);
right: 15px; right: 15px;
}
} }
@media (min-width: 700px) { @media (min-width: 800px) {
.ais-SearchBox-input { .ais-SearchBox-input {
width: 100%; width: 100%;
margin-top: 6px; margin-top: 6px;
@ -130,7 +125,8 @@
} }
.fcc_searchBar .ais-Hits { .fcc_searchBar .ais-Hits {
top: auto; top: auto;
width: calc(80vw - 20px); width: calc(100% - 20px);
max-width: 500px;
left: 10px; left: 10px;
} }
.fcc_searchBar .ais-SearchBox-form { .fcc_searchBar .ais-SearchBox-form {
@ -143,8 +139,3 @@
left: 0.85rem; left: 0.85rem;
} }
} }
@media (min-width: 1100px) {
.fcc_searchBar .ais-Hits {
width: calc(100% - 20px);
}
}