-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #297 from kiwicom/new/modal-portal-component
NEW: Modal and Portal component
- Loading branch information
Showing
33 changed files
with
4,495 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
{ | ||
"env": { | ||
"browser": true, | ||
"jest": true | ||
}, | ||
"root": true, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# ClickOutside | ||
To implement ClickOutside component into your project you'll need to add the import: | ||
```jsx | ||
import ClickOutside from "@kiwicom/orbit-components/lib/ClickOutside"; | ||
``` | ||
After adding import into your project you can use it simply like: | ||
```jsx | ||
<ClickOutside onClickOutside={...} > | ||
<div> | ||
Content | ||
</div> | ||
</ClickOutside> | ||
``` | ||
## Props | ||
Table below contains all types of the props available in the ClickOutside component. | ||
|
||
| Name | Type | Default | Description | | ||
| :-------------- | :------------------------------------------ | :-------------- | :------------------------------- | | ||
| children | `React.Node` | | The content of the ClickOutside to render. | ||
| onClickOutside | `(ev: MouseEvent) => void \| Promise<any>` | | Function for handling onClickOutside event. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// @flow | ||
import * as React from "react"; | ||
import { shallow, mount } from "enzyme"; | ||
|
||
import ClickOutside from "../index"; | ||
|
||
describe("ClickOutside mount", () => { | ||
// $FlowExpected | ||
document.addEventListener = jest.fn(); | ||
|
||
const component = mount(<ClickOutside onClickOutside={jest.fn()}>Lorem ipsum</ClickOutside>); | ||
const instance = component.instance(); | ||
|
||
it("should mount", () => { | ||
expect(document.addEventListener).toBeCalledWith("click", instance.handleClickOutside, true); | ||
}); | ||
it("should unmount", () => { | ||
// $FlowExpected | ||
document.removeEventListener = jest.fn(); | ||
component.unmount(); | ||
expect(document.removeEventListener).toBeCalledWith("click", instance.handleClickOutside, true); | ||
}); | ||
}); | ||
|
||
describe("ClickOutside shallow", () => { | ||
it("handler", () => { | ||
const onClickOutside = jest.fn(); | ||
const wrapper = shallow( | ||
<ClickOutside onClickOutside={onClickOutside}>Lorem ipsum</ClickOutside>, | ||
); | ||
|
||
const instance = wrapper.instance(); | ||
|
||
instance.node = document.createElement("div"); | ||
const node = document.createElement("div"); | ||
|
||
const ev = { target: node }; | ||
instance.handleClickOutside(ev); | ||
|
||
expect(onClickOutside).toBeCalledWith(ev); | ||
}); | ||
|
||
it("handler - no node", () => { | ||
const onClickOutside = jest.fn(); | ||
const wrapper = shallow( | ||
<ClickOutside onClickOutside={onClickOutside}>Lorem ipsum</ClickOutside>, | ||
); | ||
|
||
const instance = wrapper.instance(); | ||
const node = document.createElement("div"); | ||
|
||
const ev = { target: node }; | ||
instance.handleClickOutside(ev); | ||
|
||
expect(onClickOutside).not.toBeCalled(); | ||
}); | ||
|
||
it("handler - click inside", () => { | ||
const onClickOutside = jest.fn(); | ||
const wrapper = shallow( | ||
<ClickOutside onClickOutside={onClickOutside}>Lorem ipsum</ClickOutside>, | ||
); | ||
|
||
const instance = wrapper.instance(); | ||
|
||
instance.node = document.createElement("div"); | ||
const node = document.createElement("div"); | ||
instance.node.appendChild(node); | ||
|
||
const ev = { target: node }; | ||
instance.handleClickOutside(ev); | ||
|
||
expect(onClickOutside).not.toBeCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// @flow | ||
import * as React from "react"; | ||
import styled from "styled-components"; | ||
|
||
import type { Props } from "./index"; | ||
|
||
const Inner = styled.div` | ||
width: 100%; | ||
height: 100%; | ||
`; | ||
|
||
class ClickOutside extends React.PureComponent<Props> { | ||
componentDidMount() { | ||
document.addEventListener("click", this.handleClickOutside, true); | ||
} | ||
|
||
componentWillUnmount() { | ||
document.removeEventListener("click", this.handleClickOutside, true); | ||
} | ||
|
||
handleClickOutside = (ev: MouseEvent) => { | ||
const { onClickOutside } = this.props; | ||
|
||
if ( | ||
onClickOutside && | ||
this.node && | ||
ev.target instanceof Node && | ||
!this.node.contains(ev.target) | ||
) { | ||
onClickOutside(ev); | ||
} | ||
}; | ||
|
||
node: ?HTMLDivElement; | ||
|
||
render() { | ||
const { children } = this.props; | ||
|
||
return ( | ||
<Inner | ||
innerRef={node => { | ||
this.node = node; | ||
}} | ||
> | ||
{children} | ||
</Inner> | ||
); | ||
} | ||
} | ||
|
||
export default ClickOutside; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// @flow | ||
|
||
export type Props = {| | ||
+onClickOutside?: (ev: MouseEvent) => void | Promise<any>, | ||
+children: React$Node | React$Node[], | ||
|}; | ||
|
||
declare export default React$ComponentType<Props>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// @flow | ||
import * as React from "react"; | ||
import { storiesOf, setAddon } from "@storybook/react"; | ||
import { action } from "@storybook/addon-actions"; | ||
import styles from "@sambego/storybook-styles"; | ||
import chaptersAddon from "react-storybook-addon-chapters"; | ||
import { withKnobs, text, boolean, select, array } from "@storybook/addon-knobs/react"; | ||
|
||
import Button from "../Button"; | ||
import SIZES from "./consts"; | ||
import ModalHeader from "./ModalHeader"; | ||
import ModalSection from "./ModalSection"; | ||
import Illustration from "../Illustration"; | ||
import Text from "../Text"; | ||
import { NAMES } from "../Illustration/consts"; | ||
import ModalFooter from "./ModalFooter"; | ||
import ChevronLeft from "../icons/ChevronLeft"; | ||
|
||
import Modal from "./index"; | ||
|
||
setAddon(chaptersAddon); | ||
|
||
storiesOf("Modal", module) | ||
.addDecorator(withKnobs) | ||
.addDecorator( | ||
styles({ | ||
padding: "20px", | ||
}), | ||
) | ||
.addWithChapters("Sizes", () => { | ||
const size = select("Size", Object.values(SIZES), SIZES.NORMAL); | ||
const title = text("Title", "Orbit design system"); | ||
const description = text("Title", "I'm lovely description"); | ||
|
||
const onClose = action("onClose"); | ||
const content = text( | ||
"Content", | ||
"You can try all possible configurations of this component. However, check Orbit.Kiwi for more detailed design guidelines.", | ||
); | ||
return { | ||
info: | ||
"You can try all possible configurations of this component. However, check Orbit.Kiwi for more detailed design guidelines.", | ||
chapters: [ | ||
{ | ||
sections: [ | ||
{ | ||
sectionFn: () => ( | ||
<Modal onClose={onClose} size={size}> | ||
<ModalHeader title={title}>{description}</ModalHeader> | ||
<ModalSection> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalSection> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalSection> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalSection> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
</Modal> | ||
), | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
}) | ||
.addWithChapters("Full preview", () => { | ||
const size = select("Size", Object.values(SIZES), SIZES.NORMAL); | ||
const title = text("Title", "Orbit design system"); | ||
const description = text("Description", "Lorem ispum dolor sit amet"); | ||
const illustration = select( | ||
"Illustration", | ||
[undefined, ...Object.values(NAMES)], | ||
"Accommodation", | ||
); | ||
const closable = boolean("closable", true); | ||
const onClose = action("onClose"); | ||
const fixed = boolean("fixedFooter", false); | ||
const suppressed = boolean("suppressed", false); | ||
const content = text( | ||
"Text", | ||
"You can try all possible configurations of this component. However, check Orbit.Kiwi for more detailed design guidelines.", | ||
); | ||
const flex = array("Flex", ["0 0 auto", "1 1 100%"]); | ||
return { | ||
info: | ||
"You can try all possible configurations of this component. However, check Orbit.Kiwi for more detailed design guidelines.", | ||
chapters: [ | ||
{ | ||
sections: [ | ||
{ | ||
sectionFn: () => ( | ||
<Modal onClose={onClose} size={size} closable={closable} fixedFooter={fixed}> | ||
<ModalHeader | ||
title={title} | ||
illustration={illustration && <Illustration name={illustration} size="small" />} | ||
description={description} | ||
suppressed={suppressed} | ||
/> | ||
<ModalSection suppressed={suppressed}> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalSection suppressed={suppressed}> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalSection suppressed={suppressed}> | ||
<Text>{content}</Text> | ||
</ModalSection> | ||
<ModalFooter flex={flex}> | ||
<Button type="secondary" iconLeft={<ChevronLeft />}> | ||
Back | ||
</Button> | ||
<Button block>Continue to Payment</Button> | ||
</ModalFooter> | ||
</Modal> | ||
), | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
}); |
Oops, something went wrong.