Files
flipper/src/ui/components/TooltipProvider.js
Sara Valderrama d26779cd16 Update Tooltip implementation for Flipper
Summary:
Basic tooltips available. Use is:

  <Tooltip
     title="This is what will show up inside the tooltip"
     options={{ // can include any or none of these (if not included default will be used)
        position, // 'above', 'below', 'toRight', or 'toLeft'
        showTail, // whether or not tooltip should have tail
        delay, // how long to wait on hover before showing tooltip
        // supported css properties
        backgroundColor,
        color,
        maxWidth,
        width,
        borderRadius,
        padding,
        lineHeight,
     }}>
     <ElementTooltipWillShowUpFor/>
  </Tooltip>

Reviewed By: danielbuechele

Differential Revision: D9596287

fbshipit-source-id: 233b1ad01b96264bbc1f62f3798e3d69d1ab4bae
2018-09-04 10:44:12 -07:00

254 lines
6.1 KiB
JavaScript

/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import styled from '../styled/index.js';
import {colors} from './colors.js';
import {Component} from 'react';
const PropTypes = require('prop-types');
const defaultOptions = {
backgroundColor: colors.blueGrey,
position: 'below',
color: colors.white,
showTail: true,
maxWidth: '200px',
width: 'auto',
borderRadius: 4,
padding: '6px',
lineHeight: '20px',
delay: 0,
};
export type TooltipOptions = {
backgroundColor?: string,
position?: 'below' | 'above' | 'toRight' | 'toLeft',
color?: string,
showTail?: boolean,
maxWidth?: string,
width?: string,
borderRadius?: number,
padding?: string,
lineHeight?: string,
delay?: number, // in milliseconds
};
const TooltipBubble = styled('div')(props => ({
position: 'absolute',
zIndex: 99999999999,
backgroundColor: props.options.backgroundColor,
lineHeight: props.options.lineHeight,
padding: props.options.padding,
borderRadius: props.options.borderRadius,
width: props.options.width,
maxWidth: props.options.maxWidth,
top: props.top,
left: props.left,
bottom: props.bottom,
right: props.right,
color: props.options.color,
}));
// vertical offset on bubble when position is 'below'
const BUBBLE_BELOW_POSITION_VERTICAL_OFFSET = -10;
// horizontal offset on bubble when position is 'toLeft' or 'toRight'
const BUBBLE_LR_POSITION_HORIZONTAL_OFFSET = 5;
// offset on bubble when tail is showing
const BUBBLE_SHOWTAIL_OFFSET = 5;
// horizontal offset on tail when position is 'above' or 'below'
const TAIL_AB_POSITION_HORIZONTAL_OFFSET = 15;
// vertical offset on tail when position is 'toLeft' or 'toRight'
const TAIL_LR_POSITION_HORIZONTAL_OFFSET = 5;
const TooltipTail = styled('div')(props => ({
position: 'absolute',
display: 'block',
whiteSpace: 'pre',
height: '10px',
width: '10px',
lineHeight: '0',
zIndex: 99999999998,
transform: 'rotate(45deg)',
backgroundColor: props.options.backgroundColor,
top: props.top,
left: props.left,
bottom: props.bottom,
right: props.right,
}));
type TooltipProps = {
children: React$Node,
};
type TooltipObject = {
rect: ClientRect,
title: React$Node,
options: TooltipOptions,
};
type TooltipState = {
tooltip: ?TooltipObject,
timeoutID: ?TimeoutID,
};
export default class TooltipProvider extends Component<
TooltipProps,
TooltipState,
> {
static childContextTypes = {
TOOLTIP_PROVIDER: PropTypes.object,
};
state = {
tooltip: null,
timeoutID: undefined,
};
getChildContext() {
return {TOOLTIP_PROVIDER: this};
}
open(container: HTMLDivElement, title: React$Node, options: TooltipOptions) {
const node = container.childNodes[0];
if (node == null || !(node instanceof HTMLElement)) {
return;
}
if (options.delay) {
this.state.timeoutID = setTimeout(() => {
this.setState({
tooltip: {
rect: node.getBoundingClientRect(),
title,
options: options,
},
});
}, options.delay);
return;
}
this.setState({
tooltip: {
rect: node.getBoundingClientRect(),
title,
options: options,
},
});
}
close() {
if (this.state.timeoutID) {
clearTimeout(this.state.timeoutID);
}
this.setState({tooltip: null});
}
getTooltipTail(tooltip: TooltipObject) {
const opts = Object.assign(defaultOptions, tooltip.options);
if (!opts.showTail) {
return null;
}
let left = 'auto';
let top = 'auto';
let bottom = 'auto';
let right = 'auto';
if (opts.position === 'below') {
left = tooltip.rect.left + TAIL_AB_POSITION_HORIZONTAL_OFFSET;
top = tooltip.rect.bottom;
} else if (opts.position === 'above') {
left = tooltip.rect.left + TAIL_AB_POSITION_HORIZONTAL_OFFSET;
bottom = window.innerHeight - tooltip.rect.top;
} else if (opts.position === 'toRight') {
left = tooltip.rect.right + TAIL_LR_POSITION_HORIZONTAL_OFFSET;
top = tooltip.rect.top;
} else if (opts.position === 'toLeft') {
right =
window.innerWidth -
tooltip.rect.left +
TAIL_LR_POSITION_HORIZONTAL_OFFSET;
top = tooltip.rect.top;
}
return (
<TooltipTail
key="tail"
top={top}
left={left}
bottom={bottom}
right={right}
options={opts}
/>
);
}
getTooltipBubble(tooltip: TooltipObject) {
const opts = Object.assign(defaultOptions, tooltip.options);
let left = 'auto';
let top = 'auto';
let bottom = 'auto';
let right = 'auto';
if (opts.position === 'below') {
left = tooltip.rect.left;
top = tooltip.rect.bottom;
if (opts.showTail) {
top += BUBBLE_SHOWTAIL_OFFSET;
}
} else if (opts.position === 'above') {
bottom = window.innerHeight - tooltip.rect.top;
if (opts.showTail) {
bottom += BUBBLE_SHOWTAIL_OFFSET;
}
left = tooltip.rect.left;
} else if (opts.position === 'toRight') {
left = tooltip.rect.right + BUBBLE_LR_POSITION_HORIZONTAL_OFFSET;
if (opts.showTail) {
left += BUBBLE_SHOWTAIL_OFFSET;
}
top = tooltip.rect.top + BUBBLE_BELOW_POSITION_VERTICAL_OFFSET;
} else if (opts.position === 'toLeft') {
right =
window.innerWidth -
tooltip.rect.left +
BUBBLE_LR_POSITION_HORIZONTAL_OFFSET;
if (opts.showTail) {
right += BUBBLE_SHOWTAIL_OFFSET;
}
top = tooltip.rect.top + BUBBLE_BELOW_POSITION_VERTICAL_OFFSET;
}
return (
<TooltipBubble
key="bubble"
top={top}
left={left}
bottom={bottom}
right={right}
options={opts}>
{tooltip.title}
</TooltipBubble>
);
}
getTooltipElement() {
const {tooltip} = this.state;
return (
tooltip &&
tooltip.title && [
this.getTooltipTail(tooltip),
this.getTooltipBubble(tooltip),
]
);
}
render() {
return [this.getTooltipElement(), this.props.children];
}
}