Просмотр исходного кода

Merge pull request #337 from smiclea/CORWEB-183

Add collapsable navigation menu CORWEB-183
Dorin Paslaru 7 лет назад
Родитель
Сommit
144ff98235
26 измененных файлов с 607 добавлено и 217 удалено
  1. 1 1
      package.json
  2. 1 1
      server/main.js
  3. 7 3
      src/components/atoms/Logo/Logo.jsx
  4. 93 0
      src/components/molecules/NavigationMini/NavigationMini.jsx
  5. 13 13
      src/components/molecules/NavigationMini/images/menu.js
  6. 6 0
      src/components/molecules/NavigationMini/package.json
  7. 3 3
      src/components/molecules/NavigationMini/story.jsx
  8. 0 123
      src/components/molecules/SideMenu/SideMenu.jsx
  9. BIN
      src/components/molecules/SideMenu/images/star-bg.jpg
  10. 0 6
      src/components/molecules/SideMenu/package.json
  11. 0 36
      src/components/molecules/SideMenu/test.jsx
  12. 3 3
      src/components/organisms/DetailsPageHeader/DetailsPageHeader.jsx
  13. 2 2
      src/components/organisms/LoginForm/test.jsx
  14. 1 0
      src/components/organisms/MainList/MainList.jsx
  15. 346 15
      src/components/organisms/Navigation/Navigation.jsx
  16. 20 0
      src/components/organisms/Navigation/images/cbs-logo-small.svg
  17. 17 0
      src/components/organisms/Navigation/images/endpoint-menu.svg
  18. 25 0
      src/components/organisms/Navigation/images/logo-small.svg
  19. 19 0
      src/components/organisms/Navigation/images/planning-menu.svg
  20. 20 0
      src/components/organisms/Navigation/images/project-menu.svg
  21. 10 0
      src/components/organisms/Navigation/images/replica-menu.svg
  22. 17 0
      src/components/organisms/Navigation/images/user-menu.svg
  23. 0 8
      src/components/organisms/Navigation/test.jsx
  24. 1 1
      src/components/styleUtils/StyleProps.js
  25. 0 1
      src/components/templates/MainTemplate/MainTemplate.jsx
  26. 2 1
      src/config.js

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "coriolis-web",
-  "version": "1.4.3",
+  "version": "1.4.4",
   "license": "AGPL-3.0",
   "scripts": {
     "start": "npm run env:dev && node server.js --dev",

+ 1 - 1
server/main.js

@@ -42,5 +42,5 @@ app.use((req, res) => {
 })
 
 app.listen(PORT, () => {
-  console.log(`Express server is up on port ${PORT}`) // eslint-disable-line no-console
+  console.log(`Express server is up on port ${PORT}`)
 })

+ 7 - 3
src/components/atoms/Logo/Logo.jsx

@@ -41,7 +41,9 @@ const smallblackProps = css`
   background: url('${coriolisSmallBlackImage}') center no-repeat;
 `
 
-const Wrapper = styled.a``
+const Wrapper = styled.a`
+  transition: all ${StyleProps.animations.swift};
+`
 const Coriolis = styled.div`
   ${props => props.small ? smallProps : props.smallblack ? smallblackProps : largeProps}
   ${props => !props.large && !props.small && !props.smallblack ? StyleProps.media.handheld`
@@ -53,12 +55,14 @@ const Coriolis = styled.div`
 
 type Props = {
   small?: boolean,
-  smallblack?:boolean,
+  smallblack?: boolean,
   large?: boolean,
+  customRef?: (ref: HTMLElement) => void,
 }
+
 const Logo = (props: Props) => {
   return (
-    <Wrapper {...props}>
+    <Wrapper {...props} innerRef={ref => { if (props.customRef) props.customRef(ref) }}>
       <Coriolis large={props.large} small={props.small} smallblack={props.smallblack} />
     </Wrapper>
   )

+ 93 - 0
src/components/molecules/NavigationMini/NavigationMini.jsx

@@ -0,0 +1,93 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { observer } from 'mobx-react'
+import styled, { css } from 'styled-components'
+
+import Navigation from '../../organisms/Navigation'
+
+import StyleProps from '../../styleUtils/StyleProps'
+import menuImage from './images/menu'
+
+const Wrapper = styled.div`
+  margin-right: 38px;
+  margin-left: 32px;
+`
+const Close = css`
+  transform: rotate(0deg) translateX(0) translateY(0);
+`
+const Stub = styled.div`
+  width: 20px;
+`
+const MenuImage = styled.div`
+  cursor: pointer;
+  width: 20px;
+  height: 20px;
+  ${props => props.open ? css`position: fixed;` : ''}
+  top: 23px;
+  z-index: 99;
+  #top {
+    ${props => props.open ? css`transform: rotate(45deg) translateX(0.5px) translateY(-4.5px);` : Close}
+    transition: all ${StyleProps.animations.swift};
+  }
+  #bottom {
+    ${props => props.open ? css`transform: rotate(-45deg) translateX(-6.5px) translateY(1.5px);` : Close}
+    transition: all ${StyleProps.animations.swift};
+  }
+`
+const NavigationStyled = styled(Navigation)`
+  position: fixed;
+  left: ${props => props.open ? 0 : -80}px;
+  top: 0;
+  padding-top: 24px;
+  transition: left ${StyleProps.animations.swift};
+`
+
+type State = {
+  open: boolean,
+}
+@observer
+class NavigationMini extends React.Component<{}, State> {
+  state = {
+    open: false,
+  }
+
+  handleMenuToggleClick() {
+    this.setState({ open: !this.state.open })
+  }
+
+  render() {
+    return (
+      <Wrapper>
+        <MenuImage
+          open={this.state.open}
+          onClick={() => { this.handleMenuToggleClick() }}
+          dangerouslySetInnerHTML={{ __html: menuImage() }}
+          data-test-id="sideMenu-toggle"
+        />
+        {this.state.open ? <Stub /> : null}
+        <NavigationStyled
+          open={this.state.open}
+          collapsed
+          hideLogos
+        />
+      </Wrapper>
+    )
+  }
+}
+
+export default NavigationMini

+ 13 - 13
src/components/molecules/SideMenu/images/hamburger.js → src/components/molecules/NavigationMini/images/menu.js

@@ -12,21 +12,21 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-const hamburger = () => {
-  return `
+const menu = () => `
 <?xml version="1.0" encoding="UTF-8"?>
-<svg width="30px" height="30px" viewBox="-7 -8 30 30" version="1.1" 
-xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Nav/Menu/Header" transform="translate(-22.000000, -24.000000)" fill="#FFFFFF">
-            <g id="Group" transform="translate(22.000000, 24.000000)">
-                <rect id="top-layer" x="0" y="0" width="20" height="2" rx="1"></rect>
-                <rect id="middle-layer" x="0" y="7" width="20" height="2" rx="1"></rect>
-                <rect id="bottom-layer" x="0" y="14" width="20" height="2" rx="1"></rect>
+<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Group 2</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0 5)">
+        <g id="Icon/Hamburger-Closed" fill="#FFFFFF">
+            <g id="Group-2">
+                <rect id="top" x="0" y="0" width="20" height="2" rx="1"></rect>
+                <rect id="bottom" x="0" y="8" width="20" height="2" rx="1"></rect>
             </g>
         </g>
     </g>
-</svg>`
-}
+</svg>
+`
 
-export default hamburger
+export default menu

+ 6 - 0
src/components/molecules/NavigationMini/package.json

@@ -0,0 +1,6 @@
+{
+  "name": "NavigationMini",
+  "version": "0.0.0",
+  "private": true,
+  "main": "./NavigationMini.jsx"
+}

+ 3 - 3
src/components/molecules/SideMenu/story.jsx → src/components/molecules/NavigationMini/story.jsx

@@ -14,9 +14,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { storiesOf } from '@storybook/react'
-import SideMenu from '.'
+import NavigationMini from '.'
 
-storiesOf('SideMenu', module)
+storiesOf('NavigationMini', module)
   .add('default', () => (
-    <SideMenu />
+    <NavigationMini />
   ))

+ 0 - 123
src/components/molecules/SideMenu/SideMenu.jsx

@@ -1,123 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-// @flow
-
-import React from 'react'
-import { observer } from 'mobx-react'
-import styled, { css } from 'styled-components'
-
-import StyleProps from '../../styleUtils/StyleProps'
-import Palette from '../../styleUtils/Palette'
-
-import { navigationMenu } from '../../../config'
-import hamburgerImage from './images/hamburger'
-import backgroundImage from './images/star-bg.jpg'
-
-const Wrapper = styled.div`
-  margin-right: 20px;
-`
-const OpenTopLayer = css`
-  transform: rotate(45deg) translateX(3px);
-  width: 19px;
-`
-const OpenMiddleLayer = css`
-  transform: rotate(-45deg) translateY(4.5px) translateX(-10.5px);
-  width: 19px;
-`
-const Close = css`
-  transform: rotate(0) translateY(0) translateX(0);
-  opacity: 1;
-`
-const OpenBottomLayer = css`
-  opacity: 0;
-`
-
-const Hamburger = styled.div`
-  cursor: pointer;
-  #top-layer, #middle-layer, #bottom-layer {
-    transition: all .4s cubic-bezier(0, 1.4, 1, 1);
-  }
-  #top-layer {
-    ${props => props.open ? OpenTopLayer : Close};
-  }
-  #middle-layer {
-    ${props => props.open ? OpenMiddleLayer : Close};
-  }
-  #bottom-layer {
-    ${props => props.open ? OpenBottomLayer : Close};
-  }
-`
-const Menu = styled.div`
-  position: fixed;
-  background: url('${backgroundImage}');
-  top: 64px;
-  left: ${props => props.open ? 0 : '-224px'};
-  bottom: 0;
-  width: 184px;
-  padding-left: 40px;
-  padding-top: 60px;
-  transition: all ${StyleProps.animations.swift};
-  display: flex;
-  flex-direction: column;
-  z-index: 1;
-`
-const MenuItem = styled.a`
-  font-size: 18px;
-  color: white;
-  margin-bottom: 24px;
-  cursor: pointer;
-  text-decoration: none;
-
-  &:hover {
-    color: ${Palette.primary};
-  }
-`
-
-type Props = {}
-type State = {
-  open: boolean,
-}
-@observer
-class SideMenu extends React.Component<Props, State> {
-  state = {
-    open: false,
-  }
-
-  handleHamburgerClick() {
-    this.setState({ open: !this.state.open })
-  }
-
-  render() {
-    return (
-      <Wrapper>
-        <Hamburger
-          open={this.state.open}
-          onClick={() => { this.handleHamburgerClick() }}
-          dangerouslySetInnerHTML={{ __html: hamburgerImage() }}
-          data-test-id="sideMenu-toggle"
-        />
-        <Menu open={this.state.open} data-test-id="sideMenu-menu">
-          {navigationMenu.filter(i => i.disabled ? !i.disabled : true).map(item => {
-            return (
-              <MenuItem key={item.value} href={`/#/${item.value}`} data-test-id={`sideMenu-item-${item.value}`}>{item.label}</MenuItem>
-            )
-          })}
-        </Menu>
-      </Wrapper>
-    )
-  }
-}
-
-export default SideMenu

BIN
src/components/molecules/SideMenu/images/star-bg.jpg


+ 0 - 6
src/components/molecules/SideMenu/package.json

@@ -1,6 +0,0 @@
-{
-  "name": "SideMenu",
-  "version": "0.0.0",
-  "private": true,
-  "main":"./SideMenu.jsx"
-}

+ 0 - 36
src/components/molecules/SideMenu/test.jsx

@@ -1,36 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-// @flow
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import TW from '../../../utils/TestWrapper'
-import SideMenu from '.'
-
-const wrap = props => new TW(shallow(<SideMenu {...props} />), 'sideMenu')
-
-describe('SideMenu Component', () => {
-  it('opens menu on click', () => {
-    let wrapper = wrap()
-    expect(wrapper.find('menu').prop('open')).toBe(false)
-    wrapper.find('toggle').simulate('click')
-    expect(wrapper.find('menu').prop('open')).toBe(true)
-  })
-
-  it('renders at least one item in the list', () => {
-    let wrapper = wrap()
-    expect(wrapper.find('item-', true).length).toBeGreaterThan(0)
-  })
-})

+ 3 - 3
src/components/organisms/DetailsPageHeader/DetailsPageHeader.jsx

@@ -18,7 +18,7 @@ import React from 'react'
 import styled from 'styled-components'
 import { observer } from 'mobx-react'
 
-import SideMenu from '../../molecules/SideMenu'
+import NavigationMini from '../../molecules/NavigationMini'
 import NotificationDropdown from '../../molecules/NotificationDropdown'
 import UserDropdown from '../../molecules/UserDropdown'
 import AboutModal from '../../organisms/AboutModal'
@@ -35,7 +35,7 @@ const Wrapper = styled.div`
   height: 64px;
   background: url('${backgroundImage}');
   align-items: center;
-  padding: 0 22px;
+  padding-right: 22px;
   justify-content: space-between;
 `
 const Logo = styled.a`
@@ -114,7 +114,7 @@ class DetailsPageHeader extends React.Component<Props, State> {
     return (
       <Wrapper>
         <Menu>
-          <SideMenu />
+          <NavigationMini />
           <Logo href="/#/replicas" />
         </Menu>
         <User>

+ 2 - 2
src/components/organisms/LoginForm/test.jsx

@@ -28,12 +28,12 @@ const wrap = props => new TW(shallow(
 describe('LoginForm Component', () => {
   it('renders incorrect credentials', () => {
     let wrapper = wrap({ loginFailedResponse: { status: 401 } })
-    expect(wrapper.findText('errorText')).toBe('The username or password did not match. Please try again.')
+    expect(wrapper.find('errorText').prop('dangerouslySetInnerHTML').__html).toBe('Incorrect credentials.<br />Please try again.') // eslint-disable-line
   })
 
   it('renders server error', () => {
     let wrapper = wrap({ loginFailedResponse: {} })
-    expect(wrapper.findText('errorText')).toBe('Request failed, there might be a problem with the connection to the server.')
+    expect(wrapper.find('errorText').prop('dangerouslySetInnerHTML').__html).toBe('Request failed, there might be a problem with the connection to the server.') // eslint-disable-line
   })
 
   it('submits correct info', () => {

+ 1 - 0
src/components/organisms/MainList/MainList.jsx

@@ -30,6 +30,7 @@ const Wrapper = styled.div`
   display: flex;
   flex-direction: column;
   flex-grow: 1;
+  min-width: 785px;
 `
 const Separator = styled.div`
   height: 1px;

+ 346 - 15
src/components/organisms/Navigation/Navigation.jsx

@@ -15,45 +15,166 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 
 import React from 'react'
+import { Link } from 'react-router-dom'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
+import autobind from 'autobind-decorator'
 
 import Logo from '../../atoms/Logo'
 import userStore from '../../../stores/UserStore'
 
 import { navigationMenu } from '../../../config'
+
 import backgroundImage from './images/star-bg.jpg'
 import cbsImage from './images/cbs-logo.svg'
+import cbsImageSmall from './images/cbs-logo-small.svg'
+import tinyLogo from './images/logo-small.svg'
+
+import replicaImage from './images/replica-menu.svg'
+import endpointImage from './images/endpoint-menu.svg'
+import planningImage from './images/planning-menu.svg'
+import projectImage from './images/project-menu.svg'
+import userImage from './images/user-menu.svg'
+
+const MENU_MAX_WIDTH_TOGGLE = 1350
+
+const isCollapsed = (props: any) => props.collapsed || (window.outerWidth < MENU_MAX_WIDTH_TOGGLE)
+
+const ANIMATION = '200ms'
 
 const Wrapper = styled.div`
   background-image: url('${backgroundImage}');
   display: flex;
   flex-direction: column;
+  align-items: center;
   height: 100%;
+  width: ${props => isCollapsed(props) ? '80px' : '320px'};
+  transition: width ${ANIMATION};
+`
+
+const LogoWrapper = styled.div`
+  position: relative;
+  height: 48px;
+  margin-top: 48px;
   width: 100%;
+  display: flex;
+  justify-content: center;
 `
 
 const LogoStyled = styled(Logo)`
-  margin: 40px auto 0 30px;
+  position: absolute;
+  top: 0;
+  left: ${props => isCollapsed(props) ? '-9999px' : 'auto'};
   cursor: pointer;
+  display: flex;
 `
 
-const Menu = styled.div`
+const TinyLogo = styled.a`
+  position: absolute;
+  top: 0;
+  opacity: ${props => isCollapsed(props) ? 1 : 0};
+  background: url('${tinyLogo}') center no-repeat;
+  display: flex;
+  width: 48px;
+  height: 48px;
+  transition: opacity ${ANIMATION};
+`
+const MenuWrapper = styled.div`
+  position: relative;
   display: flex;
   flex-direction: column;
-  margin-top:32px;
+  align-items: center;
   flex-grow: 1;
+  margin-top: 32px;
+  width: 100%;
+`
+const Menu = styled.div`
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  top: 0;
+  left: ${props => isCollapsed(props) ? '-9999px' : 'auto'};
+  opacity: ${props => isCollapsed(props) ? 0 : 1};
+  transition: opacity ${ANIMATION};
 `
 
-const MenuItem = styled.a`
+const MenuItem = styled(Link)`
   font-size: 18px;
   color: ${props => props.selected ? '#007AFF' : 'white'};
   cursor: pointer;
   margin-top: 26px;
-  margin-left: 96px;
-  display: inline-block;
   text-decoration: none;
+  width: 145px;
+  margin-left: 32px;
 `
+const SmallMenu = styled.div`
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  opacity: ${props => isCollapsed(props) ? 1 : 0};
+  left: ${props => isCollapsed(props) ? 'auto' : '-9999px'};
+  top: 0;
+  transition: opacity ${ANIMATION};
+`
+const SmallMenuBackground = styled.div`
+  border-radius: 50%;
+  opacity: 0.15;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  transition: background-color ${ANIMATION};
+`
+const MenuTooltip = styled.div`
+  position: absolute;
+  font-size: 12px;
+  color: #202234;
+  background: #D8DBE2;
+  border-radius: 8px;
+  padding: 1px 6px;
+  white-space: nowrap;
+  transition: opacity ${ANIMATION};
+  opacity: 0;
+  left: -9999px;
+`
+const SmallMenuItem = styled(Link)`
+  position: relative;
+  cursor: pointer;
+  width: 38px;
+  height: 38px;
+  margin-top: 16px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  ${SmallMenuBackground} {
+    background: ${props => props.selected ? 'white' : 'inherit'}
+  }
+  &:hover {
+    ${SmallMenuBackground} {
+      background: white;
+    }
+    ${MenuTooltip} {
+      opacity: 1;
+      left: 42px;
+    }
+  }
+`
+const SmallMenuItemBullet = styled.div`
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  position: absolute;
+  left: -12px;
+  background: ${props => props.bullet === 'replica' ? '#E62565' : '#0044CA'};
+`
+
+const MenuImage = styled.div`
+  width: 24px;
+  height: 24px;
+  background: url('${props => props.image}') center no-repeat;
+`
+
 const Footer = styled.div`
   flex-shrink: 1;
   display: flex;
@@ -63,28 +184,153 @@ const Footer = styled.div`
   margin-bottom: 32px;
 `
 
+const CbsLogoWrapper = styled.div`
+  position: relative;
+  display: flex;
+  width: 100%;
+  height: 34px;
+  justify-content: center;
+`
 const CbsLogo = styled.a`
+  position: absolute;
+  top: 0;
+  left: ${props => isCollapsed(props) ? '-9999px' : 'auto'};
+  opacity: ${props => isCollapsed(props) ? 0 : 1};
   width: 128px;
-  height: 32px;
+  height: 34px;
   background: url('${cbsImage}') center no-repeat;
   cursor: pointer;
   display: flex;
+  transition: opacity ${ANIMATION};
 `
+const CbsLogoSmall = styled.a`
+  position: absolute;
+  top: 0;
+  left: ${props => isCollapsed(props) ? 'auto' : '-9999px'};
+  opacity: ${props => isCollapsed(props) ? 1 : 0};
+  width: 48px;
+  height: 34px;
+  background: url('${cbsImageSmall}') center no-repeat;
+  cursor: pointer;
+  display: flex;
+  transition: opacity ${ANIMATION};
+`
+
+type Props = {
+  currentPage?: string,
+  className?: string,
+  collapsed?: boolean,
+  hideLogos?: boolean,
+}
 
 @observer
-class Navigation extends React.Component<{ currentPage: string }> {
+class Navigation extends React.Component<Props> {
+  wrapper: ?HTMLElement
+  coriolisLogo: ?HTMLElement
+  coriolisLogoSmall: ?HTMLElement
+  cbsLogo: ?HTMLElement
+  cbsLogoSmall: ?HTMLElement
+  menu: ?HTMLElement
+  smallMenu: ?HTMLElement
+
+  resizeTimeout: ?TimeoutID
+
+  isCollapsed: boolean = false
+
+  componentDidMount() {
+    if (this.props.collapsed) {
+      return
+    }
+    window.addEventListener('resize', this.handleWindowResize)
+  }
+
+  componentWillUnmount() {
+    if (this.props.collapsed) {
+      return
+    }
+    window.removeEventListener('resize', this.handleWindowResize)
+  }
+
+  @autobind
+  handleCollapsedTransitionEnd() {
+    if (!this.coriolisLogo || !this.cbsLogo || !this.menu || !this.isCollapsed) {
+      return
+    }
+
+    this.coriolisLogo.style.left = '-9999px'
+    this.cbsLogo.style.left = '-9999px'
+    this.menu.style.left = '-9999px'
+    this.cbsLogo.removeEventListener('transitionend', this.handleCollapsedTransitionEnd)
+  }
+
+  @autobind
+  handleExpandedTransitionEnd() {
+    if (!this.smallMenu || this.isCollapsed || !this.cbsLogoSmall) {
+      return
+    }
+
+    this.smallMenu.style.left = '-9999px'
+    this.cbsLogoSmall.style.left = '-9999px'
+    this.smallMenu.removeEventListener('transitionend', this.handleExpandedTransitionEnd)
+  }
+
+  @autobind
+  handleWindowResize() {
+    if (this.resizeTimeout) {
+      return
+    }
+
+    this.resizeTimeout = setTimeout(() => {
+      this.resizeTimeout = null
+      this.toggleMenu(window.outerWidth < MENU_MAX_WIDTH_TOGGLE)
+    }, 100)
+  }
+
+  toggleMenu(toCollapsed: boolean) {
+    if (
+      !this.wrapper ||
+      !this.coriolisLogo ||
+      !this.coriolisLogoSmall ||
+      !this.cbsLogo ||
+      !this.cbsLogoSmall ||
+      !this.menu ||
+      !this.smallMenu
+    ) {
+      return
+    }
+
+    if (toCollapsed) {
+      this.smallMenu.style.left = 'auto'
+      this.cbsLogoSmall.style.left = 'auto'
+      this.cbsLogo.addEventListener('transitionend', this.handleCollapsedTransitionEnd)
+    } else {
+      this.coriolisLogo.style.left = 'auto'
+      this.cbsLogo.style.left = 'auto'
+      this.menu.style.left = 'auto'
+      this.smallMenu.addEventListener('transitionend', this.handleExpandedTransitionEnd)
+    }
+    this.isCollapsed = toCollapsed
+
+    this.wrapper.style.width = toCollapsed ? '80px' : '320px'
+    this.coriolisLogoSmall.style.opacity = toCollapsed ? '1' : '0'
+    this.coriolisLogo.style.opacity = toCollapsed ? '0' : '1'
+    this.cbsLogo.style.opacity = toCollapsed ? '0' : '1'
+    this.cbsLogoSmall.style.opacity = toCollapsed ? '1' : '0'
+    this.menu.style.opacity = toCollapsed ? '0' : '1'
+    this.smallMenu.style.opacity = toCollapsed ? '1' : '0'
+  }
+
   renderMenu() {
     const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
     return (
-      <Menu>
+      <Menu innerRef={ref => { this.menu = ref }} collapsed={this.props.collapsed}>
         {
-          // $FlowIgnore
           navigationMenu.filter(i => i.disabled ? !i.disabled : i.requiresAdmin ? isAdmin : true).map(item => {
             return (
               <MenuItem
                 key={item.value}
                 selected={this.props.currentPage === item.value}
-                href={`/#/${item.value}`}
+                to={`/${item.value}`}
                 data-test-id={`navigation-item-${item.value}`}
               >{item.label}</MenuItem>
             )
@@ -93,13 +339,98 @@ class Navigation extends React.Component<{ currentPage: string }> {
     )
   }
 
+  renderSmallMenu() {
+    const isAdmin = userStore.loggedUser ? userStore.loggedUser.isAdmin : false
+    return (
+      <SmallMenu innerRef={ref => { this.smallMenu = ref }} collapsed={this.props.collapsed}>
+        {
+          navigationMenu.filter(i => i.disabled ? !i.disabled : i.requiresAdmin ? isAdmin : true).map(item => {
+            let menuImage
+            let bullet
+            switch (item.value) {
+              case 'replicas':
+                bullet = 'replica'
+                menuImage = replicaImage
+                break
+              case 'migrations':
+                bullet = 'migration'
+                menuImage = replicaImage
+                break
+              case 'endpoints':
+                menuImage = endpointImage
+                break
+              case 'planning':
+                menuImage = planningImage
+                break
+              case 'projects':
+                menuImage = projectImage
+                break
+              case 'users':
+                menuImage = userImage
+                break
+              default:
+            }
+
+            return (
+              <SmallMenuItem
+                key={item.value}
+                selected={this.props.currentPage === item.value}
+                to={`/${item.value}`}
+              >
+                <SmallMenuBackground />
+                {bullet ? <SmallMenuItemBullet bullet={bullet} /> : null}
+                <MenuImage image={menuImage} />
+                <MenuTooltip>{item.label}</MenuTooltip>
+              </SmallMenuItem>
+            )
+          })}
+      </SmallMenu>
+    )
+  }
+
   render() {
     return (
-      <Wrapper>
-        <LogoStyled small href={navigationMenu[0].value} />
-        {this.renderMenu()}
+      <Wrapper
+        innerRef={ref => { this.wrapper = ref }}
+        className={this.props.className}
+        collapsed={this.props.collapsed}
+      >
+        {this.props.hideLogos ? null : (
+          <LogoWrapper>
+            <LogoStyled
+              small
+              collapsed={this.props.collapsed}
+              href={navigationMenu[0].value}
+              customRef={ref => { this.coriolisLogo = ref }}
+            />
+            <TinyLogo
+              collapsed={this.props.collapsed}
+              innerRef={ref => { this.coriolisLogoSmall = ref }}
+              href={navigationMenu[0].value}
+            />
+          </LogoWrapper>
+        )}
+        <MenuWrapper>
+          {this.renderMenu()}
+          {this.renderSmallMenu()}
+        </MenuWrapper>
         <Footer>
-          <CbsLogo href="https://cloudbase.it" target="_blank" />
+          {this.props.hideLogos ? null : (
+            <CbsLogoWrapper>
+              <CbsLogo
+                innerRef={ref => { this.cbsLogo = ref }}
+                href="https://cloudbase.it"
+                target="_blank"
+                collapsed={this.props.collapsed}
+              />
+              <CbsLogoSmall
+                innerRef={ref => { this.cbsLogoSmall = ref }}
+                href="https://cloudbase.it"
+                target="_blank"
+                collapsed={this.props.collapsed}
+              />
+            </CbsLogoWrapper>
+          )}
         </Footer>
       </Wrapper>
     )

+ 20 - 0
src/components/organisms/Navigation/images/cbs-logo-small.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="45px" height="34px" viewBox="0 0 45 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Group 13</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-17.000000, -927.000000)" stroke="#FFFFFF" stroke-width="1.5">
+            <g id="Menu-Copy">
+                <g id="Group-13" transform="translate(18.000000, 928.000000)">
+                    <path d="M13.7724235,12.3408941 C14.8830118,12.3098353 15.9926588,12.3013647 17.1041882,12.3164235 C17.0298353,10.3013647 17.0326588,8.28818824 17.1145412,6.27312941 C16.8001882,6.19218824 16.4736,6.14230588 16.1413647,6.13289412 C15.2105412,6.1056 14.2806588,6.10371765 13.3498353,6.12818824 C11.4336,6.18089412 9.75077647,7.49289412 9.12207059,9.24254118 C9.28677647,9.24818824 9.45148235,9.25195294 9.61618824,9.25854118 C11.4891294,9.34042353 13.1352471,10.6307765 13.7724235,12.3408941 Z" id="Stroke-1"></path>
+                    <path d="M9.61618824,9.25816471 C9.45148235,9.25157647 9.28677647,9.24781176 9.12207059,9.24216471 C7.62183529,9.18945882 6.12254118,9.1904 4.62136471,9.25157647 C2.27595294,9.34945882 0.279717647,11.3400471 0.177129412,13.6854588 C0.103717647,15.3513412 0.101835294,17.0144 0.1696,18.6802824 C0.266541176,21.0266353 2.25807059,23.0228706 4.60442353,23.1254588 C5.18795294,23.1518118 5.77148235,23.1649882 6.35595294,23.1734588 C6.32489412,21.0482824 6.3776,18.9249882 6.51312941,16.7969882 C6.61101176,15.2694588 7.55312941,13.8755765 8.85289412,13.1019294 C9.45430588,12.7442824 10.1310118,12.5193412 10.8359529,12.4741647 C11.8157176,12.4120471 12.7936,12.3687529 13.7724235,12.3405176 C13.1352471,10.6304 11.4891294,9.34098824 9.61618824,9.25816471 Z" id="Stroke-3"></path>
+                    <path d="M42.4590118,15.7893647 C42.4006588,13.3648941 40.3667765,11.3310118 37.9423059,11.2726588 C37.1771294,11.2547765 36.4100706,11.2547765 35.6439529,11.2726588 L35.6345412,11.2726588 C35.6110118,12.2590118 35.5714824,13.2453647 35.5103059,14.2326588 C35.5027765,14.3540706 35.4896,14.4754824 35.4717176,14.5950118 C36.2378353,15.3912471 36.7347765,16.4472471 36.7837176,17.5879529 C36.8552471,19.2651294 36.8561882,20.9404235 36.7856,22.6176 C37.1714824,22.6176 37.5564235,22.6138353 37.9423059,22.6034824 C40.3667765,22.5460706 42.4006588,20.5121882 42.4590118,18.0877176 C42.4778353,17.3216 42.4778353,16.5545412 42.4590118,15.7893647 Z" id="Stroke-5"></path>
+                    <path d="M36.7835294,17.5881412 C36.7345882,16.4474353 36.2376471,15.3914353 35.4715294,14.5952 C35.1590588,16.6940235 33.3021176,18.4464941 31.1684706,18.5820235 C31.1656471,18.5820235 31.1628235,18.5829647 31.16,18.5829647 C29.3294118,18.6949647 27.5025882,18.7410824 25.6738824,18.7250824 C25.7821176,21.3848471 25.7585882,24.0417882 25.6051765,26.7043765 C26.1209412,26.9396706 26.6818824,27.0808471 27.2654118,27.1062588 C28.9595294,27.1787294 30.6498824,27.1787294 32.344,27.1062588 C34.6894118,27.0055529 36.6828235,25.0130824 36.7835294,22.6667294 C36.7844706,22.6507294 36.7844706,22.6337882 36.7854118,22.6177882 C36.856,20.9406118 36.8550588,19.2653176 36.7835294,17.5881412 Z" id="Stroke-7"></path>
+                    <path d="M35.5104941,4.61844706 C35.3721412,2.35962353 33.4192,0.406682353 31.1603765,0.268329412 C27.9528471,0.0706823529 24.7537882,0.0706823529 21.5462588,0.268329412 C19.2883765,0.406682353 17.3344941,2.35962353 17.1961412,4.61844706 C17.1622588,5.16997647 17.1368471,5.72150588 17.1142588,6.27303529 C17.0323765,8.28809412 17.0295529,10.3012706 17.1039059,12.3163294 C18.2784941,12.3304471 19.4530824,12.3728 20.6286118,12.4396235 C20.8366118,12.4499765 21.0455529,12.4603294 21.2535529,12.4744471 C22.5382588,12.5563294 23.7279059,13.2358588 24.5344941,14.2240941 C25.1293176,14.9525647 25.5161412,15.8495059 25.5763765,16.7963294 C25.6168471,17.4400941 25.6479059,18.0829176 25.6742588,18.7248 C27.5020235,18.7408 29.3297882,18.6946824 31.1603765,18.5826824 C31.1632,18.5826824 31.1660235,18.5817412 31.1688471,18.5817412 C33.3015529,18.4462118 35.1594353,16.6937412 35.4719059,14.5958588 C35.4897882,14.4753882 35.5029647,14.3539765 35.5104941,14.2325647 C35.5707294,13.2452706 35.6102588,12.2589176 35.6347294,11.2725647 C35.6864941,9.05515294 35.6479059,6.83868235 35.5104941,4.61844706 Z" id="Stroke-9"></path>
+                    <path d="M25.6740706,18.7250824 C25.6486588,18.0822588 25.6166588,17.4403765 25.5761882,16.7966118 C25.5159529,15.8488471 25.1291294,14.9528471 24.5343059,14.2243765 C23.7286588,13.2361412 22.5380706,12.5556706 21.2533647,12.4737882 C21.0453647,12.4606118 20.8364235,12.4502588 20.6293647,12.4389647 C19.4538353,12.3730824 18.2792471,12.3307294 17.1046588,12.3156706 C15.9931294,12.3015529 14.8825412,12.3100235 13.7719529,12.3410824 C12.7931294,12.3683765 11.8152471,12.4116706 10.8354824,12.4737882 C10.1305412,12.5189647 9.45477647,12.7448471 8.85336471,13.1015529 C7.55265882,13.8761412 6.61054118,15.2690824 6.5136,16.7966118 C6.37712941,18.9246118 6.32442353,21.0488471 6.35642353,23.1740235 C6.37524706,24.5189647 6.42701176,25.8657882 6.5136,27.2144941 C6.65571765,29.4479059 8.60207059,31.3942588 10.8354824,31.5373176 C14.3121882,31.7594353 17.7766588,31.7594353 21.2533647,31.5373176 C23.4867765,31.3942588 25.4331294,29.4479059 25.5761882,27.2144941 C25.5865412,27.0441412 25.5950118,26.8737882 25.6053647,26.7043765 C25.7587765,24.0417882 25.7813647,21.3848471 25.6740706,18.7250824 Z" id="Stroke-11"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 17 - 0
src/components/organisms/Navigation/images/endpoint-menu.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="17px" viewBox="0 0 24 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Icon/Replica/24 Copy 2</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-28.000000, -231.000000)" stroke="#FFFFFF" stroke-width="1.5">
+            <g id="Menu-Copy">
+                <g id="Group" transform="translate(9.000000, 125.000000)">
+                    <g id="Icon/Endpoint/24" transform="translate(19.000000, 103.000000)">
+                        <path d="M18.6513931,18.5912346 L18.8017804,18.5818287 C21.2949988,18.4258905 23.25,16.3523663 23.25,13.8412346 C23.25,11.4570913 21.4828085,9.45048902 19.1430634,9.13430625 L18.5194921,9.05003958 L18.4941125,8.42131231 C18.3987542,6.05900659 16.4519703,4.18479731 14.0793989,4.18479731 C12.4945123,4.18479731 11.0565134,5.025556 10.2675063,6.36736626 L9.97683561,6.86168987 L9.4236026,6.71076441 C9.04711403,6.60805595 8.65613083,6.55543869 8.25744591,6.55543869 C5.81724385,6.55543869 3.83906922,8.53361332 3.83906922,10.9738154 C3.83906922,11.1086301 3.8450851,11.2426093 3.85703626,11.3755132 L3.91579018,12.0288915 L3.27604486,12.1740846 C1.80932898,12.5069624 0.75,13.8175316 0.75,15.3423971 C0.75,17.1366805 2.20455412,18.5912346 3.99883756,18.5912346 C3.99900167,18.5912346 3.99900167,18.5912346 3.99918949,18.5912346 C3.99960068,18.5912346 3.99960068,18.5912346 4,18.5912346 L18.6513931,18.5912346 Z" id="Combined-Shape"></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 25 - 0
src/components/organisms/Navigation/images/logo-small.svg

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="48px" height="43px" viewBox="0 0 48 43" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Coriolis-Symbol-Small</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-16.000000, -43.000000)" stroke-width="0.7">
+            <g id="Menu-Copy">
+                <g id="ID/Coriolis/Symbol-Small" transform="translate(16.000000, 40.000000)">
+                    <path d="M33.327222,44.8988076 C33.2458771,44.8988076 33.1655006,44.8978486 33.0851241,44.8968895 C27.2641222,44.7866005 20.0931825,40.7596119 14.371925,34.3877824 C5.55278284,24.5653444 2.6805334,12.1352899 7.96891985,6.68029798 C9.66360513,4.93293595 12.0584375,4.03719712 14.9151927,4.09090309 C20.7361945,4.20119213 27.9061659,8.22818071 33.6274234,14.6000102 C42.4475339,24.4224482 45.3197834,36.8515437 40.0304285,42.3074947 C38.3851312,44.0059459 36.0706753,44.8988076 33.327222,44.8988076" id="Fill-3" stroke="#0044CA"></path>
+                    <path d="M34.1288833,41.4486056 C28.8046664,41.4486056 22.1198592,38.6300013 16.2852999,33.8530472 C6.66917147,25.9822458 2.30269381,15.2458473 6.5510277,9.91936611 C7.85738795,8.28229312 9.88810511,7.27722428 12.4243227,7.00965348 C17.9489967,6.42847818 25.3387929,9.31133783 31.7156515,14.5313661 C41.3308116,22.4021676 45.6972892,33.1395251 41.4489553,38.4660063 C40.1435635,40.1021203 38.1128463,41.1081481 35.5756604,41.3747599 C35.1069588,41.4236707 34.6237314,41.4486056 34.1288833,41.4486056" id="Fill-6" stroke="#2F81B7"></path>
+                    <path d="M34.352737,38.3136399 C29.6831528,38.3136399 23.8485935,36.6765669 18.2067441,33.4733895 C11.0726033,29.4233841 5.83650998,23.8063153 4.54370721,18.8154964 C4.00237631,16.7276768 4.20477015,14.8287873 5.1315209,13.3250201 C8.32721314,8.13472193 19.391087,8.71685626 29.7925811,14.6235537 C36.9267219,18.6735592 42.1628152,24.290628 43.4565864,29.2814469 C43.9979173,31.3692664 43.794555,33.268156 42.8678043,34.7719231 C41.405533,37.1474532 38.2940908,38.3136399 34.352737,38.3136399" id="Fill-8" stroke="#159588"></path>
+                    <path d="M33.3978422,35.8918122 C29.4487413,35.8918122 24.8237031,35.0929359 20.1366878,33.4424364 C12.2597906,30.6698658 5.90907858,26.0540296 3.95873792,21.6856245 C3.21404479,20.0159444 3.13076311,18.4728568 3.71179807,17.0985594 C5.84710152,12.0492394 16.6814666,11.1429512 27.8634843,15.0788314 C35.7403814,17.8523611 42.0910935,22.4672382 44.0414341,26.8366023 C44.7861272,28.5053235 44.8694089,30.048411 44.2874056,31.4236674 C43.0478643,34.3563969 38.8721598,35.8918122 33.3978422,35.8918122" id="Fill-10" stroke="#8CC152"></path>
+                    <path d="M30.4657242,33.8110604 C27.841383,33.8110604 25.0078692,33.580892 22.0697691,33.0975383 C13.2699947,31.6503543 5.8734198,28.2208446 3.2258372,24.3616871 C2.34266407,23.075621 2.02987359,21.8039404 2.29714967,20.5821296 C3.3865902,15.5865156 13.7687164,13.2407156 25.9307464,15.2412629 C34.7305207,16.689406 42.1270957,20.1179566 44.7746783,23.9771141 C45.656883,25.2641392 45.9696735,26.5358198 45.7033658,27.7566716 C44.8773278,31.5458194 38.7047998,33.8110604 30.4657242,33.8110604" id="Fill-12" stroke="#CDDA49"></path>
+                    <path d="M23.9995181,32.8063174 C11.102592,32.8063174 1,29.0008659 1,24.1423939 C1,19.2829628 11.102592,15.4775113 23.9995181,15.4775113 C36.897408,15.4775113 47,19.2829628 47,24.1423939 C47,29.0008659 36.897408,32.8063174 23.9995181,32.8063174" id="Fill-25" stroke="#FFDC00"></path>
+                    <path d="M2.29662145,27.7566716 C2.03031646,26.5358198 2.34310377,25.2641392 3.22626797,23.9771141 C5.87285539,20.1179566 13.2693555,16.689406 22.0700092,15.2412629 C34.2328844,13.2407156 44.6139372,15.5865156 45.7033667,20.5821296 C45.9696717,21.8029814 45.6568844,23.074662 44.7746886,24.3616871 C42.1271328,28.2208446 34.7306327,31.6503543 25.9309474,33.0975383 C22.992877,33.580892 20.1593919,33.8110604 17.5350773,33.8110604 C9.29511663,33.8110604 3.1236195,31.5458194 2.29662145,27.7566716 Z" id="Fill-23" stroke="#FDC02F"></path>
+                    <path d="M14.6022438,35.8918122 C9.1279262,35.8918122 4.95222167,34.3563969 3.71171205,31.4227084 C3.1306771,30.048411 3.21395877,28.5053235 3.95865191,26.8366023 C5.90899256,22.4681972 12.2587362,17.8523611 20.1366018,15.0788314 C31.317651,11.1429512 42.1520161,12.0492394 44.2882879,17.0985594 C44.8693229,18.4728568 44.7860412,20.0159444 44.0413481,21.6856245 C42.0910074,26.0540296 35.7402954,30.6698658 27.8633982,33.4424364 C23.1773513,35.0919769 18.5513447,35.8918122 14.6022438,35.8918122" id="Fill-21" stroke="#FD9727"></path>
+                    <path d="M13.6464413,38.3133131 C9.70508758,38.3133131 6.59461381,37.1471263 5.13137412,34.7715963 C4.20559176,33.2678292 4.00222953,31.3689396 4.54356042,29.2811201 C5.83733158,24.2903012 11.0724565,18.6741913 18.2065973,14.6232269 C28.6080914,8.71748845 39.6719653,8.13439508 42.8686259,13.3246933 C43.7944082,14.8284604 43.9977705,16.72735 43.4564396,18.8161285 C42.1626684,23.8059884 36.9275435,29.4230573 29.7934027,33.4730627 C24.1515533,36.6762401 18.3160256,38.3133131 13.6464413,38.3133131" id="Fill-19" stroke="#FC5830"></path>
+                    <path d="M13.9458532,41.5121472 C13.4510051,41.5121472 12.9677777,41.4881713 12.4990761,41.4383015 C9.96189017,41.1716897 7.93117301,40.1656618 6.62578115,38.5285888 L6.62578115,38.5295479 C2.37744726,33.2030667 6.74392492,22.4657092 16.359085,14.5949077 C22.7359436,9.37487941 30.1247714,6.49489687 35.6504138,7.07319506 C38.1866314,7.34076587 40.2173485,8.3458347 41.5237088,9.9829077 C45.7720427,15.3093889 41.405565,26.0457874 31.7894365,33.9165888 C25.9548772,38.6925839 19.2700701,41.5121472 13.9458532,41.5121472" id="Fill-17" stroke="#FF2D55"></path>
+                    <path d="M14.672797,44.8987313 C11.9293437,44.8987313 9.61488776,44.0058695 7.96959045,42.3074183 C2.6802356,36.8524264 5.55248505,24.4223718 14.3716272,14.5999339 C20.0938531,8.22810435 27.2638244,4.20111577 33.0848263,4.09082672 C35.9328659,4.03807979 38.3364138,4.93285958 40.0310991,6.68022162 C45.3194856,12.1361726 42.4472361,24.5652681 33.628094,34.387706 C27.9058681,40.7595356 20.7358967,44.7865241 14.9148949,44.8968132 C14.8345184,44.8977722 14.7531735,44.8987313 14.672797,44.8987313" id="Fill-14" stroke="#F91661"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 19 - 0
src/components/organisms/Navigation/images/planning-menu.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="23px" height="20px" viewBox="0 0 23 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Icon/Replica/24 Copy 6</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-28.000000, -278.000000)" stroke="#FFFFFF" stroke-width="1.5">
+            <g id="Menu-Copy">
+                <g id="Group" transform="translate(9.000000, 125.000000)">
+                    <g id="Icon/Planning/24" transform="translate(19.000000, 151.000000)">
+                        <polyline id="Shape" points="13 3 22 12 13 21"></polyline>
+                        <polyline id="Shape-Copy" points="1 3 10 12 1 21"></polyline>
+                        <polyline id="Shape-Copy-2" points="7 3 16 12 7 21"></polyline>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/components/organisms/Navigation/images/project-menu.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="25px" viewBox="0 0 20 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Icon/Replica/24 Copy 4</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-30.000000, -324.000000)" stroke="#FFFFFF" stroke-width="1.5">
+            <g id="Menu-Copy">
+                <g id="Group" transform="translate(9.000000, 125.000000)">
+                    <g id="Icon/Project/24" transform="translate(19.000000, 199.000000)">
+                        <path d="M12,4.25806452 L12,5.79536419e-13" id="Line-Copy"></path>
+                        <path d="M3.48387097,23.8499432 L10.1821162,10.9008175 M13.785757,10.8387097 L20.516129,23.8499432" id="Triangle-Copy"></path>
+                        <path d="M2.70967742,17.0322581 L21.2903226,17.0322581" id="Line-Copy-2"></path>
+                        <circle id="Oval-Copy" cx="12" cy="7.74193548" r="3.09677419"></circle>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 10 - 0
src/components/organisms/Navigation/images/replica-menu.svg


+ 17 - 0
src/components/organisms/Navigation/images/user-menu.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
+    <title>Icon/Replica/24 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/Collapsed" transform="translate(-28.000000, -370.000000)" stroke="#FFFFFF" stroke-width="1.5">
+            <g id="Menu-Copy">
+                <g id="Group" transform="translate(9.000000, 125.000000)">
+                    <g id="Icon/Users/24" transform="translate(19.000000, 245.000000)">
+                        <path d="M23,24 L23,23.4651163 C23,19.7744186 19.64555,18.3837209 17.39,17.4744186 C16.455,17.1 15.3,16.6180698 15.3,16.244186 L15.3,16.244186 L15.3,13.8372093 C16.675,12.8209302 17.5,11.2162791 17.5,9.55813953 L17.5,6.34883721 C17.5,3.40697674 15.025,1 12,1 C8.975,1 6.5,3.40697674 6.5,6.34883721 L6.5,9.55813953 C6.5,11.2162791 7.325,12.8744186 8.7,13.8372093 L8.7,16.244186 C8.7,16.5651163 7.545,17.0465116 6.61,17.4744186 C4.355,18.3837209 1,19.7744186 1,23.4651163 L1,24" id="Combined-Shape"></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 0 - 8
src/components/organisms/Navigation/test.jsx

@@ -23,14 +23,6 @@ import Navigation from '.'
 const wrap = props => new TW(shallow(<Navigation {...props} />), 'navigation')
 
 describe('Navigation Component', () => {
-  it('renders all items', () => {
-    let wrapper = wrap()
-    navigationMenu.filter(item => !item.disabled && !item.requiresAdmin).forEach(item => {
-      expect(wrapper.findText(`item-${item.value}`)).toBe(item.label)
-      expect(wrapper.find(`item-${item.value}`).prop('href')).toBe(`/#/${item.value}`)
-    })
-  })
-
   it('selects the current page', () => {
     let wrapper = wrap({ currentPage: 'endpoints' })
     expect(wrapper.find('item-endpoints').prop('selected')).toBe(true)

+ 1 - 1
src/components/styleUtils/StyleProps.js

@@ -46,7 +46,7 @@ const StyleProps = {
 
   media: {
     handheld: (...args: any) => css`
-      @media (max-height: 760px) { 
+      @media (max-height: 760px) {
         ${css(...args)}
       }
     `,

+ 0 - 1
src/components/templates/MainTemplate/MainTemplate.jsx

@@ -23,7 +23,6 @@ const Wrapper = styled.div`
 `
 const Navigation = styled.div`
   display: flex;
-  min-width: 320px;
 `
 const Content = styled.div`
   padding: 0 64px 0 64px;

+ 2 - 1
src/config.js

@@ -37,7 +37,8 @@ export const useSecret = true
 // when creating a new openstack endpoint
 export const showOpenstackCurrentUserSwitch = false
 
-export const navigationMenu = [
+
+export const navigationMenu: any[] = [
   { label: 'Replicas', value: 'replicas' },
   { label: 'Migrations', value: 'migrations' },
   { label: 'Cloud Endpoints', value: 'endpoints' },

Некоторые файлы не были показаны из-за большого количества измененных файлов