diff --git a/README.md b/README.md
index 811b8b8..ff92426 100644
--- a/README.md
+++ b/README.md
@@ -26,19 +26,19 @@ design system. It is a React.js implementation of
## Usage
Make sure to include the bundled CSS in your React Application as well as
-wrapping your content in [``](https://lightelligence-io.github.io/react/#/Components/Frame)
+wrapping your content in [``](https://lightelligence-io.github.io/react/#/Layout/RootContainer)
component.
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import '@lightelligence/react/dist/index.css';
-import { Button, Frame, COLOR_PRIMARY } from '@lightelligence/react';
+import { Button, RootContainer, COLOR_PRIMARY } from '@lightelligence/react';
const App = () => (
-
+
-
+
);
ReactDOM.render(, document.getElementById('root'));
diff --git a/resources/lightelligence.svg b/resources/lightelligence.svg
new file mode 100644
index 0000000..f812d70
--- /dev/null
+++ b/resources/lightelligence.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/src/index.js b/src/index.js
index 3336d36..0a82058 100644
--- a/src/index.js
+++ b/src/index.js
@@ -39,9 +39,15 @@ export * from './controls/TextArea';
export * from './controls/Input';
export * from './layout/Container';
-export * from './layout/Frame';
export * from './layout/Grid';
export * from './layout/Theme';
+export * from './layout/RootContainer';
+
+export * from './navigation/Header';
+export * from './navigation/Sidebar';
+export * from './navigation/SidebarNavigation';
+export * from './navigation/SidebarSelector';
+export * from './navigation/SecondarySidebar';
export * from './constants';
diff --git a/src/layout/Frame/Frame.md b/src/layout/Frame/Frame.md
deleted file mode 100644
index 585cf75..0000000
--- a/src/layout/Frame/Frame.md
+++ /dev/null
@@ -1,8 +0,0 @@
-```jsx
-import { Frame, Card } from '@lightelligence/react';
-
-
- Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
-
-
-```
diff --git a/src/layout/Frame/index.js b/src/layout/Frame/index.js
deleted file mode 100644
index 70ff725..0000000
--- a/src/layout/Frame/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from './Frame';
diff --git a/src/layout/RootContainer/RootContainer.js b/src/layout/RootContainer/RootContainer.js
new file mode 100644
index 0000000..c5956d9
--- /dev/null
+++ b/src/layout/RootContainer/RootContainer.js
@@ -0,0 +1,38 @@
+import { string, node } from 'prop-types';
+import React from 'react';
+import classnames from 'classnames';
+import * as olt from '@lightelligence/styles';
+
+/**
+ * Use the RootContainer component to build a layout of your application.
+ *
+ * It gives you a wrapper that will properly position :
+ *
+ * - [Header Component](#/Navigation/Header)
+ * - [Sidebar Component](#/Navigation/Sidebar)
+ * - [SecondarySidebar Component](#/Navigation/SecondarySidebar)
+ * - [RootMainContainer Component](#/Layout/RootMainContainer)
+ */
+export const RootContainer = ({ className, children, ...props }) => (
+
+ {children}
+
+);
+
+RootContainer.propTypes = {
+ /**
+ * Forward an additional className to the underlying element
+ */
+ className: string,
+ /**
+ * The body of the layout. To work correctly you must include
+ * [Header](#/Navigation/Header), [Sidebar](#/Navigation/Sidebar) and
+ * [RootMainContainer](#/Layout/RootMainContainer)
+ */
+ children: node,
+};
+
+RootContainer.defaultProps = {
+ className: null,
+ children: null,
+};
diff --git a/src/layout/RootContainer/RootContainer.md b/src/layout/RootContainer/RootContainer.md
new file mode 100644
index 0000000..bda3369
--- /dev/null
+++ b/src/layout/RootContainer/RootContainer.md
@@ -0,0 +1,121 @@
+### Example
+
+Note that in this example we take care of triggering the
+[SecondarySidebar](#/Navigation/SecondarySidebar) with a simple use of
+`React.useState`. The user has total control of the interaction between the
+[Header](#/Navigation/Header), [Sidebar](#/Navigation/Sidebar) and
+[SecondarySidebar](#/Navigation/SecondarySidebar)
+
+```js
+import { MemoryRouter } from 'react-router';
+import {
+ RootContainer,
+ Header,
+ ActionButton,
+ Tabs,
+ Tab,
+ Sidebar,
+ SidebarSeparator,
+ SidebarSelectorProperty,
+ SidebarSelectorTenant,
+ SidebarSelectorFilter,
+ SidebarSelectorFilterItem,
+ SidebarNavigation,
+ SidebarNavigationItem,
+ SidebarSubNavigationItem,
+ RootMainContainer,
+ SecondarySidebar,
+ Card,
+} from '@lightelligence/react';
+
+const logo = require('../../../resources/lightelligence.svg');
+
+const [currentNavigation, setCurrentNavigation] = React.useState(false);
+
+const MyHeader = () => (
+ }
+ left={
+
+ }
+ right={
+
+ }
+ >
+
+
+
+
+
+
+
+);
+
+const MySidebar = () => (
+
+ setCurrentNavigation(
+ currentNavigation === 'property' ? null : 'property',
+ )
+ }
+ />,
+ alert('tenant selector')}
+ />,
+ ]}
+ >
+
+ setCurrentNavigation(currentNavigation === 'filter' ? null : 'filter')
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+
+
+
+
+
+
+
+ Content
+
+
+
;
+```
diff --git a/src/layout/RootContainer/RootContainer.test.js b/src/layout/RootContainer/RootContainer.test.js
new file mode 100644
index 0000000..82d506f
--- /dev/null
+++ b/src/layout/RootContainer/RootContainer.test.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { RootContainer } from './RootContainer';
+import { oltStyles } from '../..';
+
+const renderComponent = (props) => {
+ return render();
+};
+
+describe('RootContainer', () => {
+ test('has oltStyles.Layout and oltStyles.Frame', () => {
+ const { getByTestId } = renderComponent();
+
+ const component = getByTestId('root-container');
+ expect(component.classList.contains(oltStyles.Layout)).toBe(true);
+ expect(component.classList.contains(oltStyles.Frame)).toBe(true);
+ });
+ test('forwards className', () => {
+ const { getByTestId } = renderComponent({
+ className: 'myClass',
+ });
+
+ const component = getByTestId('root-container');
+ expect(component.classList.contains('myClass')).toBe(true);
+ });
+ test('renders children', () => {
+ const { getByText } = renderComponent({
+ children: 'Foo',
+ });
+
+ const component = getByText('Foo');
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/layout/RootContainer/RootMainContainer.js b/src/layout/RootContainer/RootMainContainer.js
new file mode 100644
index 0000000..3dd2cd4
--- /dev/null
+++ b/src/layout/RootContainer/RootMainContainer.js
@@ -0,0 +1,44 @@
+import { node, string } from 'prop-types';
+import React from 'react';
+import classnames from 'classnames';
+import * as olt from '@lightelligence/styles';
+
+/**
+ * The RootMainContainer is used in the
+ * [RootContainer Component](#/Layout/RootContainer) as the container of your
+ * body.
+ *
+ * It includes an Overlay, which is used to "blur" the content whenever
+ * a [SecondarySidebar](#/Navigation/SecondarySidebar) is active.
+ *
+ * The RootMainContainer passes all props to the container of the content,
+ * which is using the semantic `main` HTML element.
+ *
+ * The RootMainContainer also has predefined padding, according to the
+ * RootContainer's [Header](#/Navigation/Header) and
+ * [Sidebar](#/Navigation/Sidebar).
+ */
+export const RootMainContainer = ({ className, children, ...props }) => (
+ <>
+
+
+ {children}
+
+ >
+);
+
+RootMainContainer.propTypes = {
+ /**
+ * Forward an additional className to the underlying element
+ */
+ className: string,
+ /**
+ * Body of the layout is the main content of your application
+ */
+ children: node,
+};
+
+RootMainContainer.defaultProps = {
+ className: null,
+ children: null,
+};
diff --git a/src/layout/RootContainer/RootMainContainer.md b/src/layout/RootContainer/RootMainContainer.md
new file mode 100644
index 0000000..fe9477b
--- /dev/null
+++ b/src/layout/RootContainer/RootMainContainer.md
@@ -0,0 +1,48 @@
+### Example
+
+For better understanding on the interaction between the
+[Header](#/Navigation/Header), [Sidebar](#/Navigation/Sidebar) and
+[SecondarySidebar](#/Navigation/SecondarySidebar) please check the
+[RootContainer](#/Layout/RootContainer).
+
+_Please note that the blurred overlay doesn't work in this example_
+
+```js
+import {
+ RootContainer,
+ Header,
+ Sidebar,
+ SidebarSeparator,
+ SidebarSelectorFilter,
+ SidebarSelectorFilterItem,
+ RootMainContainer,
+ SecondarySidebar,
+ Card,
+} from '@lightelligence/react';
+
+const [open, setOpen] = React.useState(false);
+
+
;
};
@@ -79,6 +91,10 @@ Theme.propTypes = {
* Sets the primary color
*/
primaryColor: string,
+ /**
+ * Sets the sidebar color
+ */
+ sidebarColor: string,
/**
* Children
*/
@@ -87,5 +103,6 @@ Theme.propTypes = {
Theme.defaultProps = {
primaryColor: null,
+ sidebarColor: null,
children: null,
};
diff --git a/src/layout/Theme/Theme.md b/src/layout/Theme/Theme.md
index f56c12a..13af93d 100644
--- a/src/layout/Theme/Theme.md
+++ b/src/layout/Theme/Theme.md
@@ -1,5 +1,5 @@
_Theme_ gives you the opportunity to use white-labeling in your application,
-by changing the `primaryColor` of the components.
+by changing the `primaryColor` and the `sidebarColor` of the components.
The white-labeling works only under the wrapped components inside the _Theme_
component, so a good place for that component would be in the root of your
@@ -9,7 +9,7 @@ application.
import { Theme, Chip, Button, Toggle } from '@lightelligence/react';
{/** Try changing the value here */}
-
+
Chip
diff --git a/src/navigation/Header/Header.js b/src/navigation/Header/Header.js
new file mode 100644
index 0000000..7203860
--- /dev/null
+++ b/src/navigation/Header/Header.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import { func, node, string, shape } from 'prop-types';
+import classnames from 'classnames';
+import * as olt from '@lightelligence/styles';
+import { ActionButton } from '../../components/ActionButton';
+
+/**
+ * The Header of your application.
+ *
+ * It consist of
+ *
+ * - Logo container, used for placing an image with your logo
+ * - Left container, usually used with an Action Button rendered on the left
+ * - Right container, usually used with an Action Button rendered on the right
+ * - Content, which is rendered in the middle of the header
+ * - Mobile menu button, which is visible only on mobile devices
+ *
+ * The Header should be used in conjunction with
+ * [RootContainer](#/Layout/RootContainer) component.
+ *
+ * The Header component uses the semantic `header` HTML tag and passes all
+ * props to the `header` React Element.
+ *
+ * The Header by default also implements Action Button's proximity area, so
+ * whenever the user hovers on the header the action buttons are highlighted.
+ */
+export const Header = ({
+ className,
+ children,
+ logo,
+ left,
+ right,
+ onClickMobileMenu,
+ menuButtonProps,
+ ...props
+}) => (
+
+
{logo}
+
{left}
+
{children}
+
{right}
+
+
+
+
+);
+
+Header.propTypes = {
+ /**
+ * The body of the navigation. You can use any type of children there,
+ * however, most useful navigation component for this part of your
+ * header are [Tabs](#/Components/Tabs).
+ *
+ * The body of the header is positioned in the center.
+ */
+ children: node,
+ /**
+ * Forward an additional className to the underlying element
+ */
+ className: string,
+ /**
+ * Logo container is rendered on the far left side of the Header. It is a
+ * placeholder for your logo. Best used with `img` element.
+ */
+ logo: node,
+ /**
+ * Left container is rendered on the left side of the Header, next to the
+ * Logo. It is usually filled with [ActionButton](#/Components/ActionButton).
+ * Useful for adding a Back button there giving the user a way to return to
+ * the previous navigation hierarchy.
+ */
+ left: node,
+ /**
+ * Right container is rendered on the far right side of the Header. Can be
+ * any element, like an [ActionButton](#/Components/ActionButton), giving
+ * a way to the user for quickly logging out.
+ */
+ right: node,
+ /**
+ * On clicking the menu button for mobile. The menu button is visible only
+ * on mobile and you can wire it to display the
+ * [Sidebar](#/Navigation/Sidebar).
+ */
+ onClickMobileMenu: func,
+ /**
+ * Props passed to the mobile menu action button
+ */
+ menuButtonProps: shape({}),
+};
+
+Header.defaultProps = {
+ children: null,
+ className: null,
+ logo: null,
+ left: null,
+ right: null,
+ onClickMobileMenu: null,
+ menuButtonProps: null,
+};
diff --git a/src/navigation/Header/Header.md b/src/navigation/Header/Header.md
new file mode 100644
index 0000000..a3d7917
--- /dev/null
+++ b/src/navigation/Header/Header.md
@@ -0,0 +1,23 @@
+### Example
+
+```jsx harmony
+import { Header, ActionButton, Tabs, Tab } from '@lightelligence/react';
+const logo = require('../../../resources/lightelligence.svg');
+}
+ left={
+
+ }
+ right={
+
+ }
+>
+
+
+
+
+
+
+
+```
diff --git a/src/navigation/Header/Header.test.js b/src/navigation/Header/Header.test.js
new file mode 100644
index 0000000..3a34527
--- /dev/null
+++ b/src/navigation/Header/Header.test.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import { Header } from './Header';
+import { oltStyles } from '../..';
+
+const renderComponent = (props) => {
+ return render();
+};
+
+describe('Header', () => {
+ test('forwards className', () => {
+ const { getByTestId } = renderComponent({
+ className: 'myClass',
+ });
+
+ const component = getByTestId('header');
+ expect(component.classList.contains('myClass')).toBe(true);
+ });
+
+ test('should be action button proximity area', () => {
+ const { getByTestId } = renderComponent();
+
+ const component = getByTestId('header');
+ expect(
+ component.classList.contains(oltStyles.ActionButtonProximityArea),
+ ).toBe(true);
+ });
+
+ test('renders containers content', () => {
+ const { getByText } = renderComponent({
+ left: 'Left Content',
+ right: 'Right Content',
+ logo: 'Logo Content',
+ children: 'Body Content',
+ });
+
+ const leftComponent = getByText('Left Content');
+ const rightComponent = getByText('Right Content');
+ const logoComponent = getByText('Logo Content');
+ const bodyComponent = getByText('Body Content');
+
+ expect(
+ leftComponent.classList.contains(oltStyles.HeaderLeftContainer),
+ ).toBe(true);
+ expect(
+ rightComponent.classList.contains(oltStyles.HeaderRightContainer),
+ ).toBe(true);
+ expect(logoComponent).toBeTruthy();
+ expect(bodyComponent.classList.contains(oltStyles.HeaderBody)).toBe(true);
+ });
+
+ test('should be able to catch mobile menu button', () => {
+ const onClickMobileMenu = jest.fn();
+ const { getByTestId } = renderComponent({
+ menuButtonProps: {
+ 'data-testid': 'mobile-menu',
+ },
+ onClickMobileMenu,
+ });
+
+ const actionButton = getByTestId('mobile-menu');
+ fireEvent.click(actionButton);
+ expect(onClickMobileMenu).toBeCalled();
+ });
+});
diff --git a/src/navigation/Header/index.js b/src/navigation/Header/index.js
new file mode 100644
index 0000000..266dec8
--- /dev/null
+++ b/src/navigation/Header/index.js
@@ -0,0 +1 @@
+export * from './Header';
diff --git a/src/navigation/SecondarySidebar/SecondarySidebar.js b/src/navigation/SecondarySidebar/SecondarySidebar.js
new file mode 100644
index 0000000..5eb90e1
--- /dev/null
+++ b/src/navigation/SecondarySidebar/SecondarySidebar.js
@@ -0,0 +1,108 @@
+import React from 'react';
+import { func, node, string, bool, shape } from 'prop-types';
+import classnames from 'classnames';
+import * as olt from '@lightelligence/styles';
+import { ActionButton } from '../../components/ActionButton';
+
+/**
+ * Secondary Sidebar is used when you want to render another sidebar next to
+ * the main [Sidebar](#/Navigation/Sidebar) component.
+ *
+ * It is rendered as fixed positioned `aside` html element and passes the
+ * corresponding `props` of the SecondarySidebar component.
+ *
+ * The Secondary Sidebar has an optional header that can be used, as well as
+ * mobile navigation controls, which can be used to create interaction
+ * when the user's device is a mobile screen.
+ *
+ * By default the sidebar doesn't have additional padding, so the children
+ * can reach full width of the container.
+ *
+ * Whenever a secondary sidebar is opened, it will "blur" the RootMainContainer
+ * component by modifying the overlay.
+ */
+export const SecondarySidebar = ({
+ className,
+ children,
+ onClickMobileBack,
+ onClickMobileClose,
+ header,
+ open,
+ backButtonProps,
+ closeButtonProps,
+ ...props
+}) => (
+
+);
+
+SecondarySidebar.propTypes = {
+ /**
+ * Forward an additional className to the underlying element
+ */
+ className: string,
+ /**
+ * The body of the sidebar. It can be any additional content.
+ */
+ children: node,
+ /**
+ * On clicking the back button for mobile navigation
+ */
+ onClickMobileBack: func,
+ /**
+ * On clicking the close button for mobile navigation
+ */
+ onClickMobileClose: func,
+ /**
+ * Optional header of the sidebar that is rendered at the top
+ */
+ header: node,
+ /**
+ * Controls if the secondary sidebar should be visible
+ */
+ open: bool,
+ /**
+ * Props passed to the mobile back action button
+ */
+ backButtonProps: shape({}),
+ /**
+ * Props passed to the mobile close action button
+ */
+ closeButtonProps: shape({}),
+};
+
+SecondarySidebar.defaultProps = {
+ children: null,
+ className: null,
+ onClickMobileBack: null,
+ onClickMobileClose: null,
+ header: null,
+ open: false,
+ backButtonProps: null,
+ closeButtonProps: null,
+};
diff --git a/src/navigation/SecondarySidebar/SecondarySidebar.md b/src/navigation/SecondarySidebar/SecondarySidebar.md
new file mode 100644
index 0000000..f00c3e1
--- /dev/null
+++ b/src/navigation/SecondarySidebar/SecondarySidebar.md
@@ -0,0 +1,44 @@
+### Example
+
+The implementation of the secondary's sidebar behaviour is left to the user.
+
+Please check the corresponding [RootContainer](#/Layout/RootContainer) to see a
+better example of how the sidebar work together with RootContainers's overlay
+and RootMainContainer.
+
+```js
+import { MemoryRouter } from 'react-router';
+import {
+ SidebarNavigation,
+ SidebarNavigationItem,
+ SidebarSelectorFilter,
+ SidebarSelectorFilterItem,
+ SidebarSeparator,
+ SecondarySidebar,
+ Sidebar,
+} from '@lightelligence/react';
+
+const [open, setOpen] = React.useState(false);
+
+