UNPKG

8.63 kBJavaScriptView Raw
1import React, { Fragment } from 'react';
2import { TouchableOpacity, Modal, View, StatusBar, I18nManager, StyleSheet, Platform, } from 'react-native';
3import { withTheme } from '../config';
4import { ScreenWidth, ScreenHeight, isIOS } from '../helpers';
5import Triangle from './Triangle';
6import getTooltipCoordinate, { getElementVisibleWidth, } from './getTooltipCoordinate';
7const defaultProps = {
8 withOverlay: true,
9 overlayColor: 'rgba(250, 250, 250, 0.70)',
10 highlightColor: 'transparent',
11 withPointer: true,
12 toggleOnPress: true,
13 toggleAction: 'onPress',
14 height: 40,
15 width: 150,
16 containerStyle: {},
17 backgroundColor: '#617080',
18 onClose: () => { },
19 onOpen: () => { },
20 skipAndroidStatusBar: false,
21 ModalComponent: Modal,
22 closeOnlyOnBackdropPress: false,
23};
24class Tooltip extends React.Component {
25 constructor() {
26 super(...arguments);
27 this._isMounted = false;
28 this.state = {
29 isVisible: false,
30 yOffset: 0,
31 xOffset: 0,
32 elementWidth: 0,
33 elementHeight: 0,
34 };
35 this.toggleTooltip = () => {
36 const { onClose } = this.props;
37 this.getElementPosition();
38 this._isMounted &&
39 this.setState((prevState) => {
40 if (prevState.isVisible) {
41 onClose && onClose();
42 }
43 return { isVisible: !prevState.isVisible };
44 });
45 };
46 this.wrapWithPress = (toggleOnPress, toggleAction, children) => {
47 if (toggleOnPress) {
48 return (<TouchableOpacity {...{ [toggleAction]: this.toggleTooltip }} delayLongPress={250} activeOpacity={1}>
49 {children}
50 </TouchableOpacity>);
51 }
52 return children;
53 };
54 this.containerStyle = (withOverlay, overlayColor) => {
55 return {
56 backgroundColor: withOverlay ? overlayColor : 'transparent',
57 flex: 1,
58 };
59 };
60 this.getTooltipStyle = () => {
61 const { yOffset, xOffset, elementHeight, elementWidth } = this.state;
62 const { height, backgroundColor, width, withPointer, containerStyle, } = this.props;
63 const { x, y } = getTooltipCoordinate(xOffset, yOffset, elementWidth, elementHeight, ScreenWidth, ScreenHeight, width, height, withPointer);
64 return StyleSheet.flatten([
65 {
66 position: 'absolute',
67 [I18nManager.isRTL ? 'right' : 'left']: x,
68 top: y,
69 width,
70 height,
71 backgroundColor,
72 // default styles
73 display: 'flex',
74 alignItems: 'center',
75 justifyContent: 'center',
76 flex: 1,
77 borderRadius: 10,
78 padding: 10,
79 },
80 containerStyle,
81 ]);
82 };
83 this.renderPointer = (tooltipY) => {
84 const { yOffset, xOffset, elementHeight, elementWidth } = this.state;
85 const { backgroundColor, pointerColor } = this.props;
86 const pastMiddleLine = yOffset > (tooltipY || 0);
87 return (<View style={{
88 position: 'absolute',
89 top: pastMiddleLine ? yOffset - 13 : yOffset + elementHeight - 2,
90 [I18nManager.isRTL ? 'right' : 'left']: xOffset +
91 getElementVisibleWidth(elementWidth, xOffset, ScreenWidth) / 2 -
92 7.5,
93 }}>
94 <Triangle style={{ borderBottomColor: pointerColor || backgroundColor }} isDown={pastMiddleLine}/>
95 </View>);
96 };
97 this.getTooltipHighlightedButtonStyle = () => {
98 const { highlightColor } = this.props;
99 const { yOffset, xOffset, elementWidth, elementHeight } = this.state;
100 return {
101 position: 'absolute',
102 top: yOffset,
103 [I18nManager.isRTL ? 'right' : 'left']: xOffset,
104 backgroundColor: highlightColor,
105 overflow: 'visible',
106 width: elementWidth,
107 height: elementHeight,
108 };
109 };
110 this.renderTouchableHighlightedButton = () => {
111 const TooltipHighlightedButtonStyle = this.getTooltipHighlightedButtonStyle();
112 return (<TouchableOpacity testID="tooltipTouchableHighlightedButton" onPress={() => this.toggleTooltip()} style={TooltipHighlightedButtonStyle}>
113 {this.props.children}
114 </TouchableOpacity>);
115 };
116 this.renderStaticHighlightedButton = () => {
117 const TooltipHighlightedButtonStyle = this.getTooltipHighlightedButtonStyle();
118 return (<View style={TooltipHighlightedButtonStyle}>{this.props.children}</View>);
119 };
120 this.renderHighlightedButton = () => {
121 const { closeOnlyOnBackdropPress } = this.props;
122 if (closeOnlyOnBackdropPress) {
123 return this.renderTouchableHighlightedButton();
124 }
125 else {
126 return this.renderStaticHighlightedButton();
127 }
128 };
129 this.renderContent = (withTooltip) => {
130 const { popover, withPointer, toggleOnPress, toggleAction } = this.props;
131 if (!withTooltip) {
132 return this.wrapWithPress(toggleOnPress, toggleAction, this.props.children);
133 }
134 const tooltipStyle = this.getTooltipStyle();
135 return (<View>
136 {this.renderHighlightedButton()}
137 {withPointer && this.renderPointer(tooltipStyle.top)}
138 <View style={tooltipStyle} testID="tooltipPopoverContainer">
139 {popover}
140 </View>
141 </View>);
142 };
143 this.getElementPosition = () => {
144 const { skipAndroidStatusBar } = this.props;
145 this.renderedElement &&
146 this.renderedElement.measure((_frameOffsetX, _frameOffsetY, width, height, pageOffsetX, pageOffsetY) => {
147 this._isMounted &&
148 this.setState({
149 xOffset: pageOffsetX,
150 yOffset: isIOS || skipAndroidStatusBar
151 ? pageOffsetY
152 : pageOffsetY -
153 Platform.select({
154 android: StatusBar.currentHeight,
155 ios: 20,
156 default: 0,
157 }),
158 elementWidth: width,
159 elementHeight: height,
160 });
161 });
162 };
163 this.renderStaticModalContent = () => {
164 const { withOverlay, overlayColor } = this.props;
165 return (<Fragment>
166 <TouchableOpacity style={this.containerStyle(withOverlay, overlayColor)} onPress={this.toggleTooltip} activeOpacity={1}/>
167 <View>{this.renderContent(true)}</View>
168 </Fragment>);
169 };
170 this.renderTogglingModalContent = () => {
171 const { withOverlay, overlayColor } = this.props;
172 return (<TouchableOpacity style={this.containerStyle(withOverlay, overlayColor)} onPress={this.toggleTooltip} activeOpacity={1}>
173 {this.renderContent(true)}
174 </TouchableOpacity>);
175 };
176 this.renderModalContent = () => {
177 const { closeOnlyOnBackdropPress } = this.props;
178 if (closeOnlyOnBackdropPress) {
179 return this.renderStaticModalContent();
180 }
181 else {
182 return this.renderTogglingModalContent();
183 }
184 };
185 }
186 componentDidMount() {
187 this._isMounted = true;
188 // wait to compute onLayout values.
189 requestAnimationFrame(this.getElementPosition);
190 }
191 componentWillUnmount() {
192 this._isMounted = false;
193 }
194 render() {
195 const { isVisible } = this.state;
196 const { onOpen, ModalComponent } = this.props;
197 return (<View collapsable={false} ref={(e) => {
198 this.renderedElement = e;
199 }}>
200 {this.renderContent(false)}
201 <ModalComponent animationType="fade" visible={isVisible} transparent onShow={onOpen}>
202 {this.renderModalContent()}
203 </ModalComponent>
204 </View>);
205 }
206}
207Tooltip.defaultProps = defaultProps;
208export { Tooltip };
209export default withTheme(Tooltip, 'Tooltip');