Repository: uber-web/baseui
Pull request #525 (2018-11-20 00:56:13)

feat(header-navigation): add implementation
add header navigation implementation

Header navigation is highly customizable component. All examples provide different ways customers can address their design and concerns. They mainly show flexibility of Base UI rather than component itself.

gergelyke: <img width="380" alt="screen shot 2018-11-20 at 9 45 55 am" src="https://user-images.githubusercontent.com/2174968/48792441-1faf2400-eca9-11e8-966d-a76545419507.png">

it seems like the hamburger icon is not in the middle 
gergelyke: also, for the menu should we use `point` cursor instead of the text?
blackswanny: > it seems like the hamburger icon is not in the middle
@gergelyke 
fixed 
blackswanny: > also, for the menu should we use point cursor instead of the text?
@gergelyke 
Menu is used with it's own default styles, however I added cursor for better customization
blackswanny: > are you planning to add tests in a separate PR?


yes, next commit
nadiia: Tab links in the example are not keyboard navigatable. Could you fix it please?

blackswanny: > Tab links in the example are not keyboard navigatable. Could you fix it please?

@nadiia fixed
gergelyke: Can you update the cursor to use the `pointer` style instead of `text`?
gergelyke: also, can you please add the warning that says that this component is in a beta state, and may change without notice in the near future?
blackswanny: > Can you update the cursor to use the pointer style instead of text?

updated for the whole header navigation 
blackswanny: > also, can you please add the warning that says that this component is in a beta state, and may change without notice in the near future?


added in `componentDidMount` method for client-side warnings
gergelyke: can you please use the theme variables for the hamburger icon too?

<img width="207" alt="screen shot 2018-11-29 at 9 41 46 am" src="https://user-images.githubusercontent.com/2174968/49240824-069a2780-f3bb-11e8-8efb-caa2ef7bdda4.png">

blackswanny: > can you please use the theme variables for the hamburger icon too?

fixed. @gergelyke 
chasestarr: Deploy preview for *baseui* ready!

Built with commit 79438de18d4672bc3489d789dcb25a9f41aaa9e9

https://deploy-preview-525--baseui.netlify.com
blackswanny: > Before merging please make sure that the Styled component wrapped in HOC behaves as a styled component in cases of restyling

added example for that

Patch:
diff --git a/rfcs/header-navigation-component.md b/rfcs/header-navigation-component.md
index 687fc16e0..8ee2048d5 100644
--- a/rfcs/header-navigation-component.md
+++ b/rfcs/header-navigation-component.md
@@ -6,7 +6,7 @@
 
 ```javascript
 import * as React from 'react';
-import {HeaderNavigation, NavigationItem, NavigationList} from 'baseui/header-navigation';
+import {HeaderNavigation, StyledNavigationItem as NavigationItem, StyledNavigationList as NavigationList} from 'baseui/header-navigation';
 import {Button, KIND} from 'baseui/button';
 
 export default () => <HeaderNavigation>
@@ -25,7 +25,7 @@ export default () => <HeaderNavigation>
 
 ```javascript
 import * as React from 'react';
-import {HeaderNavigation, NavigationItem, NavigationList} from 'baseui/header-navigation';
+import {HeaderNavigation, StyledNavigationItem as NavigationItem, StyledNavigationList as NavigationList} from 'baseui/header-navigation';
 import {Button, KIND} from 'baseui/button';
 import {StatefulMenu as Menu} from 'baseui/menu';
 const ITEMS = [{label: 'menu item 1'}, {label: 'menu item 2'}];
@@ -54,8 +54,8 @@ export default (props) => <HeaderNavigation
 ## Exports
 
 * `HeaderNavigation`
-* `NavigationItem`
-* `NavigationList`
+* `StyledNavigationItem`
+* `StyledNavigationList`
 * `StyledRoot`
 * `ALIGN`
 
@@ -64,14 +64,14 @@ export default (props) => <HeaderNavigation
 * `children: React$Node` - Required.
   All the children of header navigation. It accepts any components, but it is useful to use `NavigationList` and `NavigationItem` to align and follow style guides.
   
-## `NavigationList` API
+## `StyledNavigationList` API
 
 * `align: ALIGN.right | ALIGN.left | ALIGN.center` - Optional.
   Alignment of elements inside of navigation list
 * `children: React$Node` - Required.
   All the children of NavigationList.  
   
-## `NavigationItem` API
+## `StyledNavigationItem` API
 
 * `children: React$Node` - Required.
   All the children of NavigationItem.
diff --git a/src/header-navigation/constants.js b/src/header-navigation/constants.js
new file mode 100644
index 000000000..7741aaafb
--- /dev/null
+++ b/src/header-navigation/constants.js
@@ -0,0 +1,13 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+
+export const ALIGN = {
+  right: 'flex-end',
+  left: 'flex-start',
+  center: 'center',
+};
diff --git a/src/header-navigation/examples-icons.js b/src/header-navigation/examples-icons.js
new file mode 100644
index 000000000..1df6e700c
--- /dev/null
+++ b/src/header-navigation/examples-icons.js
@@ -0,0 +1,11 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+
+export function HamburgerIcon(stroke: string = 'black') {
+  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="24" stroke="${stroke}"><g transform="translate(-250 -3)" ><g><g transform="translate(254 14)"><path id="npath0_fill" d="M0 1a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1z" /></g><g transform="translate(254 8)"><path id="npath0_fill" d="M0 1a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1z" /></g><g transform="translate(254 20)"><path id="npath0_fill" d="M0 1a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1z" /></g></g></g></svg>`;
+}
diff --git a/src/header-navigation/examples-list.js b/src/header-navigation/examples-list.js
new file mode 100644
index 000000000..6e2dd7bdf
--- /dev/null
+++ b/src/header-navigation/examples-list.js
@@ -0,0 +1,17 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+
+/* eslint-env node */
+/* eslint-disable flowtype/require-valid-file-annotation */
+module.exports = {
+  HEADER_NAVIGATION_WITH_MENU_ELEMENT: 'With Dropdown Menu',
+  HEADER_NAVIGATION_WITH_RIGHT_MENU: 'With Right Menu',
+  HEADER_NAVIGATION_WITH_OVERRIDES: 'With changed styles',
+  HEADER_NAVIGATION_DIFFERENT_ALIGNMENTS: 'With alignments',
+  HEADER_NAVIGATION_SEARCH_BAR: 'With search bar',
+  HEADER_NAVIGATION_STYLED_NAV_LIST: 'With different styled Navigstion List',
+};
diff --git a/src/header-navigation/examples.js b/src/header-navigation/examples.js
new file mode 100644
index 000000000..2c7cdb849
--- /dev/null
+++ b/src/header-navigation/examples.js
@@ -0,0 +1,316 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+/* eslint-disable react/display-name*/
+
+import * as React from 'react';
+import {withProps} from '../helpers';
+import {Button} from '../button';
+import {StatefulMenu as Menu, KEY_STRINGS} from '../menu';
+import {StatefulSelect as Search} from '../select';
+import {styled} from '../styles';
+import COLORS from '../select/examples-colors';
+import {HamburgerIcon} from './examples-icons';
+
+import {
+  HeaderNavigation,
+  ALIGN,
+  StyledNavigationItem as NavigationItem,
+  StyledNavigationList as NavigationList,
+} from './index';
+import tests from './examples-list';
+import {TYPE} from '../select';
+
+const Link = withProps(
+  styled('a', props => ({
+    ':hover': {color: props.$theme.colors.primary400},
+  })),
+  {tabIndex: '0'},
+);
+// $FlowFixMe
+Link.displayName = 'Link';
+
+const Hamburger = styled('div', props => ({
+  lineHeight: 'initial',
+}));
+Hamburger.displayName = 'Hamburger';
+
+const StyledHamburgerIcon = styled('span', props => ({
+  ':after': {
+    content: `url('data:image/svg+xml;utf8,${HamburgerIcon(
+      props.$theme.colors.foreground,
+    )}');`,
+  },
+}));
+
+const ExtraStyledNavigationList = styled(NavigationList, props => ({
+  backgroundColor: 'red',
+}));
+
+let ITEMS = [];
+for (let i = 0; i < 100; i++) {
+  ITEMS.push({label: `Item ${i}`});
+}
+const options = {
+  options: COLORS,
+  labelKey: 'id',
+  valueKey: 'color',
+  placeholder: 'Choose a color',
+  maxDropdownHeight: '300px',
+};
+class MenuContainer extends React.Component<
+  {isOpen: boolean, align: string},
+  {isOpen: boolean},
+> {
+  static defaultProps = {
+    isOpen: false,
+    align: '',
+  };
+  state = {
+    isOpen: this.props.isOpen,
+  };
+  toggleMenu() {
+    this.setState({
+      isOpen: !this.state.isOpen,
+    });
+  }
+  render() {
+    return (
+      <React.Fragment>
+        <Hamburger
+          tabIndex="0"
+          aria-label="Navigation menu"
+          onKeyDown={e => {
+            switch (e.key) {
+              case KEY_STRINGS.ArrowDown:
+              case KEY_STRINGS.Space:
+              case KEY_STRINGS.Enter:
+                this.toggleMenu();
+                return;
+            }
+          }}
+          onClick={() => this.toggleMenu()}
+        >
+          <StyledHamburgerIcon />
+        </Hamburger>
+        {this.state.isOpen && (
+          <Menu
+            items={ITEMS}
+            overrides={{
+              List: {
+                style: {
+                  cursor: 'pointer',
+                  position: 'absolute',
+                  height: '90vh',
+                  right: this.props.align === ALIGN.right ? '0' : 'initial',
+                },
+              },
+            }}
+          />
+        )}
+      </React.Fragment>
+    );
+  }
+}
+
+export default {
+  [tests.HEADER_NAVIGATION_WITH_MENU_ELEMENT]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation>
+          <NavigationList align={ALIGN.left}>
+            <NavigationItem>
+              <MenuContainer />
+            </NavigationItem>
+            <NavigationItem>Uber</NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.center} />
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Link>Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Three</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Button>Get started</Button>
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+  [tests.HEADER_NAVIGATION_WITH_RIGHT_MENU]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation>
+          <NavigationList align={ALIGN.center}>
+            <NavigationItem>
+              <Link>Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Three</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>Uber</NavigationItem>
+            <NavigationItem>
+              <MenuContainer align={ALIGN.right} />
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+  [tests.HEADER_NAVIGATION_WITH_OVERRIDES]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation
+          overrides={{Root: {style: {backgroundColor: 'lightblue'}}}}
+        >
+          <NavigationList align={ALIGN.left}>
+            <NavigationItem>
+              <MenuContainer />
+            </NavigationItem>
+            <NavigationItem>Uber</NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Link>Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Three</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Button>Get started</Button>
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+  [tests.HEADER_NAVIGATION_SEARCH_BAR]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation>
+          <NavigationList align={ALIGN.left}>
+            <NavigationItem>
+              <MenuContainer />
+            </NavigationItem>
+            <NavigationItem>Uber</NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.center}>
+            <NavigationItem>
+              <Link>Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Tab Link Three</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem style={{width: '300px'}}>
+              <Search
+                {...options}
+                type={TYPE.search}
+                getOptionLabel={props => props.option.id}
+                onChange={() => {}}
+                rows={8}
+                multi
+              />
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+  [tests.HEADER_NAVIGATION_DIFFERENT_ALIGNMENTS]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation>
+          <NavigationList align={ALIGN.left}>
+            <NavigationItem>Uber</NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.center}>
+            <NavigationItem>
+              <Link>Centered Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Centered Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Centered Tab Link Three</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Link>Right Aligned Link</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Button>Get started</Button>
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+  [tests.HEADER_NAVIGATION_STYLED_NAV_LIST]: () => {
+    return (
+      <React.Fragment>
+        <div style={{marginTop: '50px'}} />
+        <HeaderNavigation>
+          <ExtraStyledNavigationList align={ALIGN.left}>
+            <NavigationItem>Uber</NavigationItem>
+          </ExtraStyledNavigationList>
+          <ExtraStyledNavigationList align={ALIGN.center}>
+            <NavigationItem>
+              <Link>Centered Tab Link One</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Centered Tab Link Two</Link>
+            </NavigationItem>
+            <NavigationItem>
+              <Link>Centered Tab Link Three</Link>
+            </NavigationItem>
+          </ExtraStyledNavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Link>Right Aligned Link</Link>
+            </NavigationItem>
+          </NavigationList>
+          <NavigationList align={ALIGN.right}>
+            <NavigationItem>
+              <Button>Get started</Button>
+            </NavigationItem>
+          </NavigationList>
+        </HeaderNavigation>
+      </React.Fragment>
+    );
+  },
+};
diff --git a/src/header-navigation/header-navigation.js b/src/header-navigation/header-navigation.js
new file mode 100644
index 000000000..56c71f885
--- /dev/null
+++ b/src/header-navigation/header-navigation.js
@@ -0,0 +1,32 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import React from 'react';
+import {getOverrides} from '../helpers/overrides';
+import type {PropsT} from './types';
+import {Root as StyledRoot} from './styled-components';
+
+class HeaderNavigation extends React.Component<PropsT, {}> {
+  static defaultProps = {
+    overrides: {},
+  };
+  componentDidMount() {
+    if (__DEV__) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        'HeaderNavigation component is in a beta state, and may change without notice in the near future',
+      );
+    }
+  }
+  render() {
+    const {overrides, ...restProps} = this.props;
+    const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot);
+    return <Root role="navigation" {...restProps} {...rootProps} />;
+  }
+}
+
+export default HeaderNavigation;
diff --git a/src/header-navigation/index.js b/src/header-navigation/index.js
new file mode 100644
index 000000000..69648215a
--- /dev/null
+++ b/src/header-navigation/index.js
@@ -0,0 +1,16 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+export {default as HeaderNavigation} from './header-navigation';
+// Styled elements
+export {
+  Root as StyledRoot,
+  NavigationItem as StyledNavigationItem,
+  NavigationList as StyledNavigationList,
+} from './styled-components';
+export {ALIGN} from './constants';
+export * from './types';
diff --git a/src/header-navigation/stories.js b/src/header-navigation/stories.js
new file mode 100644
index 000000000..c42830f3f
--- /dev/null
+++ b/src/header-navigation/stories.js
@@ -0,0 +1,22 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+/* global module */
+import {storiesOf} from '@storybook/react';
+
+// Styled elements
+import examples from './examples';
+import {withReadme} from 'storybook-readme';
+//$FlowFixMe
+import HeaderNavigationReadme from '../../rfcs/header-navigation-component.md';
+
+Object.entries(examples).forEach(([description, example]) =>
+  storiesOf('Header Navigation', module)
+    .addDecorator(withReadme(HeaderNavigationReadme))
+    // $FlowFixMe
+    .add(description, example),
+);
diff --git a/src/header-navigation/styled-components.js b/src/header-navigation/styled-components.js
new file mode 100644
index 000000000..92d5d87c7
--- /dev/null
+++ b/src/header-navigation/styled-components.js
@@ -0,0 +1,64 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import {styled, asPrimaryExport} from '../styles';
+import {ALIGN} from './index';
+
+export const Root = styled('nav', props => {
+  const {$theme} = props;
+  const {
+    sizing: {scale500},
+    typography: {font400},
+    colors: {border},
+  } = $theme;
+  return {
+    ...font400,
+    cursor: 'pointer',
+    display: 'flex',
+    paddingBottom: scale500,
+    paddingTop: scale500,
+    borderBottom: `1px solid ${border}`,
+  };
+});
+
+export const NavigationItem = styled('div', props => {
+  const {$theme} = props;
+  const {
+    sizing: {scale800},
+  } = $theme;
+  return {
+    alignSelf: 'center',
+    paddingLeft: scale800,
+  };
+});
+
+export const NavigationList: React.ComponentType<{
+  align: string,
+  children: React$Node,
+}> = (asPrimaryExport(
+  styled('div', props => {
+    const {$align, $theme} = props;
+    const {
+      sizing: {scale800},
+    } = $theme;
+    return {
+      display: 'flex',
+      ':first-child': {
+        padding: 0,
+      },
+      ':last-child': {
+        padding: 0,
+      },
+      flex: $align === ALIGN.right || $align === ALIGN.left ? 'none' : '1',
+      paddingLeft: scale800,
+      paddingRight: scale800,
+      justifySelf: $align,
+      justifyContent: $align,
+    };
+  }),
+  ['align'],
+): React.ComponentType<*>);
diff --git a/src/header-navigation/types.js b/src/header-navigation/types.js
new file mode 100644
index 000000000..410d2564b
--- /dev/null
+++ b/src/header-navigation/types.js
@@ -0,0 +1,16 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import type {OverrideT} from '../helpers/overrides';
+
+export type OverridesT = {
+  Root?: OverrideT<*>,
+};
+
+export type PropsT = {
+  overrides: OverridesT,
+};
diff --git a/src/styles/__mocks__/as-primary-export-hoc.js b/src/styles/__mocks__/as-primary-export-hoc.js
new file mode 100644
index 000000000..ab10df4b5
--- /dev/null
+++ b/src/styles/__mocks__/as-primary-export-hoc.js
@@ -0,0 +1,10 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import styled from './styled';
+const asPrimaryExport = styled;
+export default asPrimaryExport;
diff --git a/src/styles/as-primary-export-hoc.js b/src/styles/as-primary-export-hoc.js
new file mode 100644
index 000000000..66f956b0d
--- /dev/null
+++ b/src/styles/as-primary-export-hoc.js
@@ -0,0 +1,26 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import * as React from 'react';
+
+// transforms props for native styled components adding $ symbol to avoid to supported warning
+export default function asPrimaryExport(
+  StyledComponent: React.ComponentType<*>,
+  propsTransformNames: Array<string>,
+) {
+  return function withStyledPropsHOC(props: {}) {
+    const styledProps = Object.keys(props).reduce((acc, key) => {
+      if (key[0] === '$' || propsTransformNames.indexOf(key) < 0) {
+        acc[key] = props[key];
+      } else if (propsTransformNames.indexOf(key) >= 0) {
+        acc['$' + key] = props[key];
+      }
+      return acc;
+    }, {});
+    return <StyledComponent {...styledProps} />;
+  };
+}
diff --git a/src/styles/index.js b/src/styles/index.js
index a90ceb699..039d8feff 100644
--- a/src/styles/index.js
+++ b/src/styles/index.js
@@ -6,6 +6,7 @@ LICENSE file in the root directory of this source tree.
 */
 // @flow
 export {default as styled} from './styled';
+export {default as asPrimaryExport} from './as-primary-export-hoc';
 export {hexToRgb} from './util';
 export {default as ThemeProvider} from './theme-provider';
 export type {ThemeT} from './types';


Test patch:
diff --git a/src/header-navigation/__tests__/__snapshots__/header-navigation.test.js.snap b/src/header-navigation/__tests__/__snapshots__/header-navigation.test.js.snap
new file mode 100644
index 000000000..28bb9dba2
--- /dev/null
+++ b/src/header-navigation/__tests__/__snapshots__/header-navigation.test.js.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Stateless header navigation should render component: Component has correct render 1`] = `
+<HeaderNavigation
+  overrides={Object {}}
+>
+  <MockStyledComponent
+    role="navigation"
+  >
+    <nav
+      role="navigation"
+      styled-component="true"
+    >
+      Some tag
+    </nav>
+  </MockStyledComponent>
+</HeaderNavigation>
+`;
diff --git a/src/header-navigation/__tests__/__snapshots__/styled-components.test.js.snap b/src/header-navigation/__tests__/__snapshots__/styled-components.test.js.snap
new file mode 100644
index 000000000..2637d0e93
--- /dev/null
+++ b/src/header-navigation/__tests__/__snapshots__/styled-components.test.js.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Header Navigation styled components NavigationItem NavigationItem: NavigationItem has correct styles 1`] = `
+Object {
+  "alignSelf": "center",
+  "paddingLeft": "$theme.sizing.scale800",
+}
+`;
+
+exports[`Header Navigation styled components NavigationList NavigationList: NavigationList has correct styles when set to align center 1`] = `
+Array [
+  "align",
+]
+`;
+
+exports[`Header Navigation styled components NavigationList NavigationList: NavigationList has correct styles when set to align flex-end 1`] = `
+Array [
+  "align",
+]
+`;
+
+exports[`Header Navigation styled components NavigationList NavigationList: NavigationList has correct styles when set to align flex-start 1`] = `
+Array [
+  "align",
+]
+`;
+
+exports[`Header Navigation styled components StyledRoot Root: StyledRoot has correct styles 1`] = `
+Object {
+  "borderBottom": "1px solid $theme.colors.border",
+  "cursor": "pointer",
+  "display": "flex",
+  "fontFamily": "$theme.typography.font400.fontFamily",
+  "fontSize": "$theme.typography.font400.fontSize",
+  "fontWeight": "$theme.typography.font400.fontWeight",
+  "lineHeight": "$theme.typography.font400.lineHeight",
+  "paddingBottom": "$theme.sizing.scale500",
+  "paddingTop": "$theme.sizing.scale500",
+}
+`;
diff --git a/src/header-navigation/__tests__/header-navigation.test.js b/src/header-navigation/__tests__/header-navigation.test.js
new file mode 100644
index 000000000..1127175b4
--- /dev/null
+++ b/src/header-navigation/__tests__/header-navigation.test.js
@@ -0,0 +1,42 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import React from 'react';
+import {mount} from 'enzyme';
+import {styled} from '../../styles';
+import {HeaderNavigation} from '../index';
+
+describe('Stateless header navigation', function() {
+  let wrapper, children;
+  let allProps: any = {};
+
+  beforeEach(function() {
+    children = 'Some tag';
+  });
+
+  afterEach(function() {
+    jest.restoreAllMocks();
+    wrapper && wrapper.unmount();
+  });
+
+  test('should render component', function() {
+    wrapper = mount(
+      <HeaderNavigation {...allProps}>{children}</HeaderNavigation>,
+    );
+    expect(wrapper).toMatchSnapshot('Component has correct render');
+  });
+
+  test('should replace overriden root', function() {
+    const newRoot = styled('div', {color: 'red'});
+    allProps.overrides = {Root: newRoot};
+    wrapper = mount(
+      <HeaderNavigation {...allProps}>{children}</HeaderNavigation>,
+    );
+    const renderedRoot = wrapper.find(allProps.overrides.Root);
+    expect(renderedRoot).toHaveLength(1);
+  });
+});
diff --git a/src/header-navigation/__tests__/styled-components.test.js b/src/header-navigation/__tests__/styled-components.test.js
new file mode 100644
index 000000000..59b08a3fe
--- /dev/null
+++ b/src/header-navigation/__tests__/styled-components.test.js
@@ -0,0 +1,60 @@
+/*
+Copyright (c) 2018 Uber Technologies, Inc.
+
+This source code is licensed under the MIT license found in the
+LICENSE file in the root directory of this source tree.
+*/
+// @flow
+import React from 'react';
+import {shallow, mount} from 'enzyme';
+import {
+  StyledRoot,
+  StyledNavigationList,
+  StyledNavigationItem,
+  ALIGN,
+} from '../index';
+
+describe('Header Navigation styled components', () => {
+  describe('StyledRoot', function() {
+    test('Root', () => {
+      const component = shallow(
+        <StyledRoot>
+          <div />
+        </StyledRoot>,
+      );
+      expect(component.instance().getStyles()).toMatchSnapshot(
+        'StyledRoot has correct styles',
+      );
+    });
+  });
+  describe('NavigationList', function() {
+    test('NavigationList', () => {
+      const component = mount(
+        <StyledNavigationList>
+          <div />
+        </StyledNavigationList>,
+      );
+      Object.values(ALIGN).forEach(align => {
+        component.setProps({
+          align,
+        });
+        expect(component.instance().getStyles()).toMatchSnapshot(
+          // $FlowFixMe
+          `NavigationList has correct styles when set to align ${align}`,
+        );
+      });
+    });
+  });
+  describe('NavigationItem', function() {
+    test('NavigationItem', () => {
+      const component = shallow(
+        <StyledNavigationItem>
+          <div />
+        </StyledNavigationItem>,
+      );
+      expect(component.instance().getStyles()).toMatchSnapshot(
+        'NavigationItem has correct styles',
+      );
+    });
+  });
+});
diff --git a/src/test/test-framework-setup.js b/src/test/test-framework-setup.js
index f73b74e76..9ab6a67a5 100644
--- a/src/test/test-framework-setup.js
+++ b/src/test/test-framework-setup.js
@@ -11,4 +11,5 @@ import toHaveStyleRule from './expect-to-have-style-rule';
 expect.extend({toHaveStyleRule});
 
 jest.mock('../styles/styled.js');
+jest.mock('../styles/as-primary-export-hoc');
 jest.mock('../utils/get-bui-id.js');