Skip to content

Commit

Permalink
feat(Drawer): Drawer for mobile menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Oscar Martinez committed Sep 14, 2018
1 parent 71c3a65 commit 9d050f1
Show file tree
Hide file tree
Showing 24 changed files with 3,136 additions and 414 deletions.
4 changes: 3 additions & 1 deletion catalog/pages/nav_bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Column from "../../../src/components/Grid/Column";
import Row from "../../../src/components/Grid/Row";
import DeviceSizeProvider from "../../../src/components/DeviceSize/Provider";
import DisplayFor from "../../../src/components/DeviceSize";
import DrawerProvider from "../../../src/components/Drawer/Provider";

export default {
path: "/nav_bar",
Expand All @@ -22,7 +23,8 @@ export default {
Column,
Row,
DeviceSizeProvider,
DisplayFor
DisplayFor,
DrawerProvider
},
content: pageLoader(() => import("./index.md"))
};
88 changes: 46 additions & 42 deletions catalog/pages/nav_bar/index.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
### Default

```react
responsive: true
responsive: false
span: 6
state: { invert: false }
---
<DeviceSizeProvider cssOnly>
<Header>
<NavBar message="Hi there this is an optional message. There can be really really really long content here but we are going to try not to overflow this area" position="fixed" invert={state.invert}>
<DisplayFor small medium>
<NavBar.MenuButton isFirst />
</DisplayFor>
<NavBar.LogoContainer>
Aurora
</NavBar.LogoContainer>
<DisplayFor large xLarge>
<NavBar.LinkRow>
<NavBar.Link href="/">Link 1</NavBar.Link>
<NavBar.Link>Link 2</NavBar.Link>
<NavBar.Link>Link 3</NavBar.Link>
<NavBar.Link>
More
<NavBar.LinkList style={{ width: "265px", top: '40px', left: 0}}>
<NavBar.LinkListItem href="/">Link 4</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 5</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 6</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 7 With Really Long Content </NavBar.LinkListItem>
</NavBar.LinkList>
</NavBar.Link>
</NavBar.LinkRow>
</DisplayFor>
<NavBar.Right>
<NavBar.SearchButton />
<NavBar.UserButton isLast />
</NavBar.Right>
</NavBar>
</Header>
<Spacing top={{small: "cozy"}}>
<Container>
<Row>
<Column>
<Button variant="outline" onClick={() => setState({ invert: !state.invert })}>invert</Button>
</Column>
</Row>
</Container>
</Spacing>
</DeviceSizeProvider>
<DrawerProvider>
<DeviceSizeProvider cssOnly>
<Header>
<NavBar message="Hi there this is an optional message. There can be really really really long content here but we are going to try not to overflow this area" position="absolute" invert={state.invert}>
<NavBar.LogoContainer>
Aurora
</NavBar.LogoContainer>
<DisplayFor large xLarge>
<NavBar.LinkRow>
<NavBar.Link href="/">Link 1</NavBar.Link>
<NavBar.Link>Link 2</NavBar.Link>
<NavBar.Link>Link 3</NavBar.Link>
<NavBar.Link>
More
<NavBar.LinkList style={{ width: "275px", top: '40px', left: 0}}>
<NavBar.LinkListItem href="/">Link 4</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 5</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 6</NavBar.LinkListItem>
<NavBar.LinkListItem>Link 7 With Really Long Content </NavBar.LinkListItem>
</NavBar.LinkList>
</NavBar.Link>
</NavBar.LinkRow>
</DisplayFor>
<NavBar.Right>
<NavBar.SearchButton />
<NavBar.UserButton />
<DisplayFor small medium>
<NavBar.DrawerMenu header="Content!">
<Spacing top={{xSmall: "moderate"}}>Some Menu Content</Spacing>
</NavBar.DrawerMenu>
</DisplayFor>
</NavBar.Right>
</NavBar>
</Header>
<Spacing top={{small: "cozy"}}>
<Container>
<Row>
<Column>
<Button variant="outline" onClick={() => setState({ invert: !state.invert })}>invert</Button>
</Column>
</Row>
</Container>
</Spacing>
</DeviceSizeProvider>
</DrawerProvider>
```
3 changes: 3 additions & 0 deletions src/components/Drawer/Context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from "react";

export const { Provider, Consumer } = React.createContext({ isOpen: false });
67 changes: 67 additions & 0 deletions src/components/Drawer/Drawer.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import styled from "styled-components";

import constants from "../../theme/constants";
import spacing from "../../theme/spacing";
import getThemeValue from "../../utils/getThemeValue";

export const DrawerContainer = styled.div`
background-color: ${getThemeValue("onyx", "base")};
color: ${getThemeValue("white", "base")};
height: 100%;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
visibility: hidden;
transform: translateY(-100%);
opacity: 0;
transition: opacity 0.3ms ${constants.easing.easeInOutQuad};
overflow: hidden;
z-index: -1;
&.drawer__content--open {
transform: translateY(0);
visibility: visible;
opacity: 1;
}
`;

export const DrawerContent = styled.div`
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100vh;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
overflow-x: hidden;
`;

export const HeaderContent = styled.div`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
height: 60px;
padding-left: ${spacing.moderate};
`;

export const CloseButton = styled.button`
appearance: none;
cursor: pointer;
border: 0;
outline: 0;
background: transparent;
display: flex;
align-items: center;
height: 60px;
padding: 0 ${spacing.moderate};
`;

export const ContentWrapper = styled.div`
&.drawer--open {
overflow: hidden;
}
`;
77 changes: 77 additions & 0 deletions src/components/Drawer/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* global window: true */
/* eslint react/sort-comp: off, react/no-unused-state: off */
import React, { Children } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";

import { Provider } from "./Context";
import { ContentWrapper } from "./Drawer.styles";
import Portal from "../Portal";

export default class DrawerProvider extends React.Component {
static propTypes = {
children: PropTypes.element.isRequired,
className: PropTypes.string
};

static defaultProps = { className: "" };

contentContainer = React.createRef();

scrollPosition = 0;

shouldComponentUpdate(prevProps, prevState) {
return (
prevState.isOpen !== this.state.isOpen ||
prevState.content !== this.state.content ||
prevProps.children !== this.props.children
);
}

componentDidUpdate() {
const { isOpen } = this.state;
/* istanbul ignore next */
if (
!isOpen &&
typeof window !== "undefined" &&
typeof window.scrollTo === "function"
) {
window.scrollTo(0, this.scrollPosition);
}
}

setContent = content => this.setState(() => ({ content }));

toggleDrawer = () => {
this.setState(({ isOpen }) => {
/* istanbul ignore else */
if (typeof window !== "undefined" && !isOpen) {
this.scrollPosition = window.pageYOffset;
}
return { isOpen: !isOpen };
});
};

state = {
isOpen: false,
toggleDrawer: this.toggleDrawer,
setContent: this.setContent,
content: null
};

render() {
const { className, children } = this.props;
const { isOpen, content } = this.state;
return (
<Provider value={this.state}>
<ContentWrapper
className={classNames({ "drawer--open": isOpen }, className)}
innerRef={this.contentContainer}
>
{Children.only(children)}
{content && <Portal>{content}</Portal>}
</ContentWrapper>
</Provider>
);
}
}
87 changes: 87 additions & 0 deletions src/components/Drawer/__tests__/Provider.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* global window: true */
import React from "react";
import { render, Simulate } from "react-testing-library";

import DrawerProvider from "../Provider";
import { Consumer } from "../Context";

describe("DrawerProvider", () => {
beforeEach(() => {
window.scrollTo = jest.fn();
const modalRoot = global.document.createElement("div");
modalRoot.setAttribute("id", "modal-root");
const body = global.document.querySelector("body");
body.appendChild(modalRoot);
});

afterEach(() => {
const body = global.document.querySelector("body");
const modalRoot = global.document.getElementById("modal-root");
body.removeChild(modalRoot);
});
it("renders child elements", () => {
const { container } = render(
<DrawerProvider>
<div>Content</div>
</DrawerProvider>
);
expect(container.innerHTML).toMatchSnapshot();
});

it("sets content", () => {
const { getByTestId, container } = render(
<DrawerProvider>
<div>
<Consumer>
{({ setContent }) => (
<button
data-testid="button"
onClick={() => {
setContent(
<div data-testid="drawer-content">Drawer Content</div>
);
}}
>
ClickMe!
</button>
)}
</Consumer>
Content
</div>
</DrawerProvider>
);

Simulate.click(getByTestId("button"));

expect(container).toMatchSnapshot();
});

it("toggles drawer", () => {
const { getByTestId, container } = render(
<DrawerProvider>
<div>
<Consumer>
{({ setContent, toggleDrawer }) => (
<button
data-testid="button"
onClick={() => {
setContent(
<div data-testid="drawer-content">Drawer Content</div>
);
toggleDrawer();
}}
>
ClickMe!
</button>
)}
</Consumer>
Content
</div>
</DrawerProvider>
);

Simulate.click(getByTestId("button"));

expect(container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DrawerProvider renders child elements 1`] = `"<div class=\\"sc-ifAKCX eaZCGx\\"><div>Content</div></div>"`;
exports[`DrawerProvider sets content 1`] = `
<div>
<div
class="sc-ifAKCX eaZCGx"
>
<div>
<button
data-testid="button"
>
ClickMe!
</button>
Content
</div>
</div>
</div>
`;

exports[`DrawerProvider toggles drawer 1`] = `
<div>
<div
class="drawer--open sc-ifAKCX eaZCGx"
>
<div>
<button
data-testid="button"
>
ClickMe!
</button>
Content
</div>
</div>
</div>
`;
Loading

0 comments on commit 9d050f1

Please sign in to comment.