Created using react-transition-group package.
App.js:
import { useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import { ReactComponent as ArrowIcon } from './icons/arrow.svg';
import { ReactComponent as BellIcon } from './icons/bell.svg';
import { ReactComponent as BoltIcon } from './icons/bolt.svg';
import { ReactComponent as CaretIcon } from './icons/caret.svg';
import { ReactComponent as ChevronIcon } from './icons/chevron.svg';
import { ReactComponent as CogIcon } from './icons/cog.svg';
import { ReactComponent as MessengerIcon } from './icons/messenger.svg';
import { ReactComponent as PlusIcon } from './icons/plus.svg';
function App() {
return (
<Navbar>
<NavItem icon={<PlusIcon />} />
<NavItem icon={<BellIcon />} />
<NavItem icon={<MessengerIcon />} />
<NavItem icon={<CaretIcon />}>
<DropdownMenu></DropdownMenu>
</NavItem>
</Navbar>
);
}
function Navbar({ children }) {
return (
<nav className="navbar">
<ul className="navbar-nav">{children}</ul>
</nav>
);
}
function NavItem({ icon, children }) {
const [open, setOpen] = useState(false);
return (
<li className="nav-item">
<a href="!#" className="icon-button" onClick={() => setOpen(!open)}>
{icon}
</a>
{open && children}
</li>
);
}
function DropdownMenu() {
const [activeMenu, setActiveMenu] = useState('main');
const [menuHeight, setMenuHeight] = useState(null);
const dropdownRef = useRef(null);
useEffect(() => {
setMenuHeight(dropdownRef.current?.firstChild.offsetHeight);
}, []);
function calcHeight(el) {
const height = el.offsetHeight;
setMenuHeight(height);
}
function DropdownItem({ leftIcon, rightIcon, goToMenu, children }) {
return (
<a
href="!#"
className="menu-item"
onClick={() => goToMenu && setActiveMenu(goToMenu)}
>
{leftIcon && <span className="icon-button">{leftIcon}</span>}
{children}
{rightIcon && (
<span className="icon-button icon-right">{rightIcon}</span>
)}
</a>
);
}
return (
<div className="dropdown" style={{ height: menuHeight }} ref={dropdownRef}>
<CSSTransition
in={activeMenu === 'main'}
timeout={500}
classNames="menu-primary"
unmountOnExit
onEnter={calcHeight}
>
<div className="menu">
<DropdownItem>My Profile</DropdownItem>
<DropdownItem
leftIcon={<CogIcon />}
rightIcon={<ChevronIcon />}
goToMenu="settings"
>
Settings
</DropdownItem>
<DropdownItem
leftIcon="🦧"
rightIcon={<ChevronIcon />}
goToMenu="animals"
>
Animals
</DropdownItem>
</div>
</CSSTransition>
<CSSTransition
in={activeMenu === 'settings'}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}
>
<div className="menu">
<DropdownItem goToMenu="main" leftIcon={<ArrowIcon />}>
<h2>My Tutorial</h2>
</DropdownItem>
<DropdownItem leftIcon={<BoltIcon />}>HTML</DropdownItem>
<DropdownItem leftIcon={<BoltIcon />}>CSS</DropdownItem>
<DropdownItem leftIcon={<BoltIcon />}>JavaScript</DropdownItem>
<DropdownItem leftIcon={<BoltIcon />}>Awesome!</DropdownItem>
</div>
</CSSTransition>
<CSSTransition
in={activeMenu === 'animals'}
timeout={500}
classNames="menu-secondary"
unmountOnExit
onEnter={calcHeight}
>
<div className="menu">
<DropdownItem goToMenu="main" leftIcon={<ArrowIcon />}>
<h2>Animals</h2>
</DropdownItem>
<DropdownItem leftIcon="🦘">Kangaroo</DropdownItem>
<DropdownItem leftIcon="🐸">Frog</DropdownItem>
<DropdownItem leftIcon="🦋">Horse?</DropdownItem>
<DropdownItem leftIcon="🦔">Hedgehog</DropdownItem>
</div>
</CSSTransition>
</div>
);
}
export default App;index.css:
body {
margin : 0;
background : #151616;
font-family : roboto;
-webkit-font-smoothing : antialiased;
-moz-osx-font-smoothing: grayscale;
}
:root {
--bg : #242526;
--bg-accent : #484a4d;
--text-color : #dadce1;
--nav-size : 60px;
--border : 1px solid #474a4d;
--border-radius: 8px;
--speed : 500ms;
}
ul {
list-style: none;
margin : 0;
padding : 0;
}
a {
color : var(--text-color);
text-decoration: none;
;
}
/* Top Navigation Bar */
/* <nav> */
.navbar {
height : var(--nav-size);
background-color: var(--bg);
padding : 0 1rem;
border-bottom : var(--border);
}
/* <ul> */
.navbar-nav {
max-width : 100%;
height : 100%;
display : flex;
justify-content: flex-end;
}
/* <li> */
.nav-item {
width : calc(var(--nav-size) * 0.8);
display : flex;
align-items : center;
justify-content: center;
}
/* Icon Button */
.icon-button {
--button-size : calc(var(--nav-size) * 0.5);
width : var(--button-size);
height : var(--button-size);
background-color: #484a4d;
border-radius : 50%;
padding : 5px;
margin : 2px;
display : flex;
align-items : center;
justify-content : center;
transition : filter 300ms;
}
.icon-button:hover {
filter: brightness(1.2);
}
.icon-button svg {
fill : var(--text-color);
width : 20px;
height: 20px;
}
/* Dropdown Menu */
.dropdown {
position : absolute;
top : 58px;
width : 300px;
transform : translateX(-45%);
background-color: var(--bg);
border : var(--border);
border-radius : var(--border-radius);
padding : 1rem;
overflow : hidden;
transition : height var(--speed) ease;
}
.menu {
width: 100%;
}
.menu-item {
height : 50px;
display : flex;
align-items : center;
border-radius: var(--border-radius);
transition : background var(--speed);
padding : 0.5rem;
}
.menu-item .icon-button {
margin-right: 0.5rem;
}
.menu-item .icon-button:hover {
filter: none;
}
.menu-item:hover {
background-color: #525357;
}
.icon-right {
margin-left: auto;
}
/* CSSTransition classes */
.menu-primary-enter {
position : absolute;
transform: translateX(-110%);
}
.menu-primary-enter-active {
transform : translateX(0%);
transition: all var(--speed) ease;
}
.menu-primary-exit {
position: absolute;
}
.menu-primary-exit-active {
transform : translateX(-110%);
transition: all var(--speed) ease;
}
.menu-secondary-enter {
transform: translateX(110%);
}
.menu-secondary-enter-active {
transform : translateX(0%);
transition: all var(--speed) ease;
}
.menu-secondary-exit {
position: absolute;
}
.menu-secondary-exit-active {
transform : translateX(110%);
transition: all var(--speed) ease;
}