#app
View Compiled
$responsive-size: 576px;

@import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");

:root {
  --animation-time: 300ms;
  --app-font-family: Montserrat, sans-serif;
  --app-background-image:
    linear-gradient(to top left, #79a8bb, #b36dd4, #f06699);
  --cpc-menu-background-color: rgba(0, 0, 0, 0.5);
  --cpc-menu-max-width: 320px;
  --cpc-menu-padding: 0.4rem;
  --cpc-menu-border-radius: 0.7rem;
  --cpc-menu-box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
  --cpc-menu-links-color: #ffffff;
  --cpc-menu-links-padding: 0.9rem 0.7rem;
  --cpc-menu-links-text-shadow: 0 -1px #000000;
  --cpc-menu-links-border-radius: 0.7rem;
  --cpc-menu-links-transition: background-color 300ms ease-in-out;
  --cpc-menu-links-hover-background-color: rgba(0, 0, 0, 0.5);
  --cpc-icon-margin-right: 0.5rem;
  --cpc-caret-margin-left: 0.5rem;
  --cpc-caret-transform: rotate(0deg) scale(1);
  --cpc-caret-transforming: rotate(90deg) scale(1.2);
  --cpc-caret-transformed: rotate(180deg) scale(1);
  --cpc-sub-menu-background-color: rgba(0, 0, 0, 0.5);
  --cpc-sub-menu-padding: 0.4rem;
  --cpc-sub-menu-border-radius: 0.7rem;
  --cpc-sub-menu-box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.5);
}

html, body, #app {
  font-size: 16px;
  width: 100%;
  height: 100%;
}

@mixin flex-center() {
  display: flex;
  align-items: center;
  justify-content: center;
}

#app {
  @include flex-center();
  font-family: var(--app-font-family);
  background-image: var(--app-background-image);
  padding: 2rem;
  overflow: auto;
  box-sizing: border-box;
}

.cpc-menu {
  width: 100%;
  max-width: var(--cpc-menu-max-width);
  background-color: var(--cpc-menu-background-color);
  padding: var(--cpc-menu-padding);
  margin: auto;
  box-sizing: border-box;
  border-radius: var(--cpc-menu-border-radius);
  box-shadow: var(--cpc-menu-box-shadow);
}

.cpc-main, .cpc-sub {
  padding: 0;
  margin: 0;
  list-style-type: none;
  & > li {
    width: 100%;
    & > a {
      color: var(--cpc-menu-links-color);
      font-weight: bold;
      text-align: center;
      text-decoration: none;
      width: 100%;
      padding: var(--cpc-menu-links-padding);
      display: flex;
      justify-content: space-between;
      box-sizing: border-box;
      text-shadow: var(--cpc-menu-links-text-shadow);
      border-radius: var(--cpc-menu-links-border-radius);
      transition: var(--cpc-menu-links-transition);
      &:hover {
        background-color: var(--cpc-menu-links-hover-background-color);
      }
    }
  }
}

.cpc-main {
  @include flex-center();
  flex-direction: column;
}

.cpc-sub {
  height: 0;
  overflow: hidden;
  visibility: hidden;
}

.cpc-caret {
  margin-left: var(--cpc-caret-margin-left);
  .cpc-active {
    animation: caret-is-active var(--animation-time) linear forwards;
  }
  .cpc-inactive {
    animation: caret-is-active var(--animation-time) linear forwards;
  }
}

.cpc-icon {
  margin-right: var(--cpc-icon-margin-right);
}

.cpc-hidden {
  visibility: hidden;
}

@keyframes caret-is-inactive {
	0%   { transform: var(--cpc-caret-transform); }
	50%  { transform: var(--cpc-caret-transforming); }
	100% { transform: var(--cpc-caret-transformed); }
}

@keyframes caret-is-active {
	0%   { transform: var(--cpc-caret-transformed); }
	50%  { transform: var(--cpc-caret-transforming); }
	100% { transform: var(--cpc-caret-transform); }
}

@media (min-width: $responsive-size) {
  .cpc-menu {
    width: auto;
    max-width: none;
  }
  
  .cpc-main {
    flex-direction: row;
    position: relative;
    & > li > a {
      position: relative;
    }
  }
  
  .cpc-sub {
    background-color: var(--cpc-sub-menu-background-color);
    padding: var(--cpc-sub-menu-padding);
    position: absolute;
    top: 120%;
    border-radius: var(--cpc-sub-menu-border-radius);
    box-shadow: var(--cpc-sub-menu-box-shadow);
  }
  
  .cpc-hidden {
    display: none;
  }
}
View Compiled
// TODO: Activate caret active animation

class CpcNavigation extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      jsxData: [],
      submenu: []
    };
  }

  componentDidMount() {
    this.setState({jsxData: this.createMenuJSX()});
  }

  menuClickEvent(i) {
    let submenu = this.state.submenu;
    let tmpmenu = submenu[i];
    let sub     = tmpmenu.sub.current;
    let caret   = tmpmenu.caret.current;

    if (tmpmenu.active === false) {
      tmpmenu.active = true;
      
      TweenLite.to(caret, 1, {
        transform: 'rotate(180deg)',
        ease: Elastic.easeOut.config(1, 0.3)
      });
      
      TweenLite.to(sub, 1, {
        height: sub.scrollHeight,
        visibility: 'visible',
        ease: Elastic.easeOut.config(1, 0.3)
      });
    } else {
      tmpmenu.active = false;
      
      TweenLite.to(caret, 1, {
        transform: 'rotate(0deg)',
        ease: Elastic.easeOut.config(1, 0.3)
      });
      
      TweenLite.to(sub, 0.5, {
        height: 0,
        ease: Bounce.easeOut
      }).eventCallback('onComplete', () => {
        TweenLite.to(sub, 0, {
          visibility: 'hidden'
        })
      });
    }
    
    submenu[i] = tmpmenu;
    
    this.setState({submenu: submenu});
  }

  createMenuJSX(menu = this.props.menu) {
    let link = [];

    for (let i in menu) {
      let m  = menu[i];
      let ic = <i className="cpc-icon cpc-hidden fas fa-caret-down"></i>;

      if (typeof m.icon !== 'undefined') {
        ic = <i className={'cpc-icon ' + m.icon}></i>;
      }

      if (typeof m.menu === 'undefined') {
        link.push(
          <li>
            <a href={m.link}>
              {ic}
              <span>{i}</span>
              <i className="cpc-caret cpc-hidden fas fa-caret-down"></i>
            </a>
          </li>
        );
      } else if (typeof m.menu === 'object') {
        let tmpSubmenu = this.state.submenu;
        let tmpLength  = tmpSubmenu.length;

        tmpSubmenu.push({
          'id': m.link,
          'active': false,
          'caret': React.createRef(),
          'sub': React.createRef()
        });

        link.push(
          <li>
            <a
              href={m.link}
              onClick={this.menuClickEvent.bind(this, tmpLength)}
            >
              {ic}
              <span>{i}</span>
              <i
                className="cpc-caret fas fa-caret-down"
                ref={tmpSubmenu[tmpLength].caret}
              ></i>
            </a>
            <ul className="cpc-sub" ref={tmpSubmenu[tmpLength].sub}>
              {this.createMenuJSX(m.menu)}
            </ul>
          </li>
        );

        this.setState({submenu: tmpSubmenu});
      }
    }

    return link;
  }

  render() {
    return (
      <nav className="cpc-menu">
        <ul className="cpc-main">
          {this.state.jsxData}
        </ul>
      </nav>
    );
  }
}

// Navigation menu builder
const menu = {
  'Home': {
    'link': '#home',
    'icon': 'fas fa-home'
  },
  'About': {
    'link': '#about',
    'icon': 'fas fa-info-circle'
  },
  'Clients': {
    'link': '#clients',
    'icon': 'fas fa-user-tie',
    'menu': {
      'Burger King': {
        'link': '#burger_king',
        'icon': 'fas fa-check'
      },
      'Southwest Airlines': {
        'link': '#southwest_airlines',
        'icon': 'fas fa-check'
      },
      'Levi Strauss': {
        'link': '#levi_strauss',
        'icon': 'fas fa-check'
      }
    }
  },
  'Services': {
    'link': '#services',
    'icon': 'fas fa-cogs',
    'menu': {
      'Print Design': {
        'link': '#print_design',
        'icon': 'fas fa-check'
      },
      'Web Design': {
        'link': '#web_design',
        'icon': 'fas fa-check'
      },
      'Mobile App Development': {
        'link': '#mobile_app_development',
        'icon': 'fas fa-check'
      }
    }
  },
  'Contact': {
    'link': '#contact',
    'icon': 'fas fa-phone-square'
  }
};

ReactDOM.render(
  <CpcNavigation menu={menu} />,
  document.querySelector('#app')
);
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js