Skip to content

Commit 183c091

Browse files
authored
feat(popover): add popover component (#35)
* initial popover * complete Popover component * remove position constant
1 parent 6c8c289 commit 183c091

22 files changed

Lines changed: 8141 additions & 345 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
],
3636
"license": "MIT",
3737
"dependencies": {
38+
"focus-trap-react": "^5.0.0",
3839
"react": "16.6.3",
3940
"react-art": "16.6.3",
4041
"react-dom": "16.6.3",

src/components/Dialog/Dialog.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ const DialogBase = (props: IDialogProps) => {
3939
dangerouslySetInlineStyle,
4040
} = props;
4141

42-
if (!isVisible) return null;
43-
4442
const {
4543
modalContainerStyle,
4644
overlayStyle,

src/components/Dialog/Modal.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
import { Modal } from 'react-native';
1+
import * as React from 'react';
2+
import { Modal as RNModal, ModalProps as RNModalProps } from 'react-native';
3+
4+
export interface ModalProps extends RNModalProps {
5+
/** For Web Desktop, whether should scroll when user initiates non-touch scroll (mouse, keyboard) */
6+
isScrollable?: boolean;
7+
}
8+
9+
const Modal = (props: ModalProps) => <RNModal {...props} />;
210

311
export default Modal;

src/components/Dialog/Modal.web.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// Temporary usage until it is integrated
22
// https://github.com/necolas/react-native-web/issues/1020
3+
// @ts-ignore: FIX: Fix typing of this module
4+
import FocusTrap from 'focus-trap-react';
35
import * as React from 'react';
46
import * as ReactDOM from 'react-dom';
5-
import { ModalProps } from 'react-native';
7+
8+
import { ModalProps } from './Modal';
69

710
const ESC_KEY = 27;
811

9-
class Modal extends React.Component<ModalProps> {
12+
class Modal extends React.PureComponent<ModalProps> {
1013
public el: HTMLDivElement;
1114
public modalRoot: HTMLBodyElement;
1215
public content: React.RefObject<HTMLDivElement> = React.createRef();
@@ -18,10 +21,14 @@ class Modal extends React.Component<ModalProps> {
1821
}
1922

2023
public componentDidMount() {
24+
// TODO: find a better solution
25+
// Currently, when the body height is shorter than the content that is scrollable
26+
// it will jump scroll to top when the modal is opened.
27+
// This hack keeps the body height the same length as the content
28+
document.body.style.position = 'relative';
29+
document.body.style.height = 'initial';
30+
document.body.style.minHeight = 'initial';
2131
this.modalRoot.appendChild(this.el);
22-
if (this.content.current) {
23-
this.content.current.focus();
24-
}
2532
}
2633

2734
public componentWillUnmount() {
@@ -38,32 +45,33 @@ class Modal extends React.Component<ModalProps> {
3845
};
3946

4047
public render() {
41-
const { transparent, visible } = this.props;
48+
const { transparent, visible, isScrollable = false } = this.props;
4249

50+
// Prevent body scroll
51+
if (visible && !isScrollable) document.body.style.overflow = 'hidden';
52+
// Reset to normal
53+
if (!visible) document.body.style.overflow = '';
4354
if (!visible) return null;
4455

45-
return (
46-
<div>
47-
{ReactDOM.createPortal(
48-
<div
49-
ref={this.content}
50-
onKeyDown={this.handleKeyDown}
51-
tabIndex={-1}
52-
style={{
53-
backgroundColor: transparent ? 'transparent' : 'white',
54-
bottom: 0,
55-
left: 0,
56-
position: 'fixed',
57-
right: 0,
58-
top: 0,
59-
zIndex: 1000,
60-
}}
61-
>
62-
{this.props.children}
63-
</div>,
64-
this.el,
65-
)}
66-
</div>
56+
return ReactDOM.createPortal(
57+
<FocusTrap>
58+
<div
59+
ref={this.content}
60+
onKeyDown={this.handleKeyDown}
61+
style={{
62+
backgroundColor: transparent ? 'transparent' : 'white',
63+
bottom: 0,
64+
left: 0,
65+
position: isScrollable ? 'absolute' : 'fixed',
66+
right: 0,
67+
top: 0,
68+
zIndex: 1000,
69+
}}
70+
>
71+
{this.props.children}
72+
</div>
73+
</FocusTrap>,
74+
this.el,
6775
);
6876
}
6977
}

src/components/FormField/FormField.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import * as React from 'react';
33
import { Box } from '../Layout';
44
import { Label, Text } from '../Typography';
55

6-
export interface IFormFieldProps {
6+
export interface IFormFieldProps<TChildren = any> {
77
error?: string;
88
label?: string;
99
description?: string;
10-
children: React.ReactElement<any>;
10+
children: TChildren;
1111
}
1212

1313
const FormField = (props: IFormFieldProps) => {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as React from 'react';
2+
import { findNodeHandle, UIManager, View, ViewProps } from 'react-native';
3+
4+
export interface LayoutMeasureProps extends ViewProps {
5+
onMeasure?: (props: LayoutMeasurements) => void;
6+
}
7+
8+
export interface LayoutMeasurements {
9+
height: number;
10+
pageX: number;
11+
pageY: number;
12+
width: number;
13+
x: number;
14+
y: number;
15+
}
16+
17+
class LayoutMeasure extends React.Component<
18+
LayoutMeasureProps,
19+
LayoutMeasurements
20+
> {
21+
private container: React.RefObject<View>;
22+
23+
constructor(props: LayoutMeasureProps) {
24+
super(props);
25+
this.container = React.createRef();
26+
}
27+
28+
public render() {
29+
const { onMeasure = () => null, ...viewProps } = this.props;
30+
31+
return (
32+
<View
33+
ref={this.container}
34+
onLayout={e => {
35+
UIManager.measure(
36+
findNodeHandle(this.container.current)!,
37+
(x, y, width, height, pageX, pageY) =>
38+
onMeasure({
39+
...e.nativeEvent.layout,
40+
pageX,
41+
pageY,
42+
}),
43+
);
44+
}}
45+
{...viewProps}
46+
/>
47+
);
48+
}
49+
}
50+
51+
export default LayoutMeasure;

src/components/Helpers/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export {
2+
default as LayoutMeasure,
3+
LayoutMeasureProps,
4+
LayoutMeasurements,
5+
} from './LayoutMeasure';

src/components/Popover/Popover.mdx

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
---
2+
name: Popover
3+
menu: Components
4+
---
5+
6+
import { Playground, PropsTable } from 'docz';
7+
import Popover from './Popover';
8+
import { Button } from '../Button';
9+
import { Box, Spacing } from '../Layout';
10+
import { Text } from '../Typography';
11+
import { Toggle } from 'react-powerplug';
12+
13+
## Usage
14+
15+
### Popovers
16+
17+
<Spacing paddingBottom={18} zIndex={1}>
18+
<Box flexDirection="row" justifyContent="space-between">
19+
<Toggle>
20+
{({ on, toggle }) => (
21+
<Popover
22+
isVisible={on}
23+
onClose={toggle}
24+
position="top-right"
25+
content={
26+
<Box backgroundColor="white" width={200} height={200}>
27+
<Text>
28+
Long text that should be well visible and non-obstructing
29+
</Text>
30+
</Box>
31+
}
32+
>
33+
<Button onPress={toggle} isInline>
34+
Top right
35+
</Button>
36+
</Popover>
37+
)}
38+
</Toggle>
39+
<Toggle>
40+
{({ on, toggle }) => (
41+
<Popover
42+
isVisible={on}
43+
onClose={toggle}
44+
position="top"
45+
content={
46+
<Box backgroundColor="white" width={200} height={200}>
47+
<Text>
48+
Long text that should be well visible and non-obstructing
49+
</Text>
50+
</Box>
51+
}
52+
>
53+
<Button onPress={toggle} isInline>
54+
Top
55+
</Button>
56+
</Popover>
57+
)}
58+
</Toggle>
59+
<Toggle>
60+
{({ on, toggle }) => (
61+
<Popover
62+
isVisible={on}
63+
onClose={toggle}
64+
position="top-left"
65+
content={
66+
<Box backgroundColor="white" width={200} height={200}>
67+
<Text>
68+
Long text that should be well visible and non-obstructing
69+
</Text>
70+
</Box>
71+
}
72+
>
73+
<Button onPress={toggle} isInline>
74+
Top left
75+
</Button>
76+
</Popover>
77+
)}
78+
</Toggle>
79+
</Box>
80+
</Spacing>
81+
82+
<Spacing paddingY={18} zIndex={2}>
83+
<Box flexDirection="row" justifyContent="space-between">
84+
<Toggle>
85+
{({ on, toggle }) => (
86+
<Popover
87+
isVisible={on}
88+
onClose={toggle}
89+
position="left"
90+
content={
91+
<Box backgroundColor="white" width={200} height={200}>
92+
<Text>
93+
Long text that should be well visible and non-obstructing
94+
</Text>
95+
</Box>
96+
}
97+
>
98+
<Button onPress={toggle} isInline>
99+
Left
100+
</Button>
101+
</Popover>
102+
)}
103+
</Toggle>
104+
<Toggle>
105+
{({ on, toggle }) => (
106+
<Popover
107+
isVisible={on}
108+
onClose={toggle}
109+
position="right"
110+
content={
111+
<Box backgroundColor="white" width={200} height={200}>
112+
<Text>
113+
Long text that should be well visible and non-obstructing
114+
</Text>
115+
</Box>
116+
}
117+
>
118+
<Button onPress={toggle} isInline>
119+
Right
120+
</Button>
121+
</Popover>
122+
)}
123+
</Toggle>
124+
</Box>
125+
</Spacing>
126+
127+
<Spacing paddingTop={18} zIndex={3}>
128+
<Box flexDirection="row" justifyContent="space-between">
129+
<Toggle>
130+
{({ on, toggle }) => (
131+
<Popover
132+
isVisible={on}
133+
onClose={toggle}
134+
position="bottom-right"
135+
content={
136+
<Box backgroundColor="white" width={200} height={200}>
137+
<Text>
138+
Long text that should be well visible and non-obstructing
139+
</Text>
140+
</Box>
141+
}
142+
>
143+
<Button onPress={toggle} isInline>
144+
Bottom right
145+
</Button>
146+
</Popover>
147+
)}
148+
</Toggle>
149+
<Toggle>
150+
{({ on, toggle }) => (
151+
<Popover
152+
isVisible={on}
153+
onClose={toggle}
154+
position="bottom"
155+
content={
156+
<Box backgroundColor="white" width={200} height={200}>
157+
<Text>
158+
Long text that should be well visible and non-obstructing
159+
</Text>
160+
</Box>
161+
}
162+
>
163+
<Button onPress={toggle} isInline>
164+
Bottom
165+
</Button>
166+
</Popover>
167+
)}
168+
</Toggle>
169+
<Toggle>
170+
{({ on, toggle }) => (
171+
<Popover
172+
isVisible={on}
173+
onClose={toggle}
174+
position="bottom-left"
175+
content={
176+
<Box backgroundColor="white" width={200} height={200}>
177+
<Text>
178+
Long text that should be well visible and non-obstructing
179+
</Text>
180+
</Box>
181+
}
182+
>
183+
<Button onPress={toggle} isInline>
184+
Bottom left
185+
</Button>
186+
</Popover>
187+
)}
188+
</Toggle>
189+
</Box>
190+
</Spacing>
191+
192+
## Props
193+
194+
<PropsTable of={Popover} />

0 commit comments

Comments
 (0)