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
This commit is contained in:
committed by
Facebook Github Bot
parent
cf3cb0d08f
commit
d26779cd16
13
src/init.js
13
src/init.js
@@ -21,6 +21,7 @@ import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
|
||||
import reducers from './reducers/index.js';
|
||||
import dispatcher from './dispatcher/index.js';
|
||||
import {setupMenuBar} from './MenuBar.js';
|
||||
import TooltipProvider from './ui/components/TooltipProvider.js';
|
||||
const path = require('path');
|
||||
|
||||
const reducer: typeof reducers = persistReducer(
|
||||
@@ -52,11 +53,13 @@ GK.init();
|
||||
setupMenuBar();
|
||||
|
||||
const AppFrame = () => (
|
||||
<ContextMenuProvider>
|
||||
<Provider store={store}>
|
||||
<App logger={logger} bugReporter={bugReporter} />
|
||||
</Provider>
|
||||
</ContextMenuProvider>
|
||||
<TooltipProvider>
|
||||
<ContextMenuProvider>
|
||||
<Provider store={store}>
|
||||
<App logger={logger} bugReporter={bugReporter} />
|
||||
</Provider>
|
||||
</ContextMenuProvider>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
// $FlowFixMe: this element exists!
|
||||
|
||||
@@ -955,9 +955,9 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
'accessibility-focused':
|
||||
'True if this element has the focus of an accessibility service',
|
||||
'content-description':
|
||||
'Text to label the content/functionality of this element ',
|
||||
'Text to label the content or functionality of this element ',
|
||||
'important-for-accessibility':
|
||||
'Marks this element as important to accessibility services, one of AUTO, YES, NO, NO_HIDE_DESCENDANTS',
|
||||
'Marks this element as important to accessibility services; one of AUTO, YES, NO, NO_HIDE_DESCENDANTS',
|
||||
'talkback-focusable': 'True if Talkback can focus on this element',
|
||||
'talkback-focusable-reasons': 'Why Talkback can focus on this element',
|
||||
'talkback-ignored': 'True if Talkback cannot focus on this element',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import type TooltipProvider from './TooltipProvider.js';
|
||||
import type {TooltipOptions} from './TooltipProvider.js';
|
||||
|
||||
import styled from '../styled/index.js';
|
||||
import {Component} from 'react';
|
||||
@@ -19,6 +20,7 @@ const TooltipContainer = styled('div')({
|
||||
type TooltipProps = {
|
||||
title: React$Node,
|
||||
children: React$Node,
|
||||
options?: TooltipOptions,
|
||||
};
|
||||
|
||||
type TooltipState = {
|
||||
@@ -48,7 +50,11 @@ export default class Tooltip extends Component<TooltipProps, TooltipState> {
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (this.ref != null) {
|
||||
this.context.TOOLTIP_PROVIDER.open(this.ref, this.props.title);
|
||||
this.context.TOOLTIP_PROVIDER.open(
|
||||
this.ref,
|
||||
this.props.title,
|
||||
this.props.options || {},
|
||||
);
|
||||
this.setState({open: true});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,34 +6,93 @@
|
||||
*/
|
||||
|
||||
import styled from '../styled/index.js';
|
||||
import {colors} from './colors.js';
|
||||
import {Component} from 'react';
|
||||
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
const TooltipBubble = styled('div')(props => ({
|
||||
backgroundColor: '#000',
|
||||
lineHeight: '25px',
|
||||
padding: '0 6px',
|
||||
borderRadius: 4,
|
||||
position: 'absolute',
|
||||
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,
|
||||
zIndex: 99999999999,
|
||||
pointerEvents: 'none',
|
||||
color: '#fff',
|
||||
marginTop: '-30px',
|
||||
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: ?{
|
||||
rect: ClientRect,
|
||||
title: React$Node,
|
||||
},
|
||||
tooltip: ?TooltipObject,
|
||||
timeoutID: ?TimeoutID,
|
||||
};
|
||||
|
||||
export default class TooltipProvider extends Component<
|
||||
@@ -46,42 +105,149 @@ export default class TooltipProvider extends Component<
|
||||
|
||||
state = {
|
||||
tooltip: null,
|
||||
timeoutID: undefined,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {TOOLTIP_PROVIDER: this};
|
||||
}
|
||||
|
||||
open(container: HTMLDivElement, title: React$Node) {
|
||||
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});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {tooltip} = this.state;
|
||||
|
||||
let tooltipElem = null;
|
||||
if (tooltip != null) {
|
||||
tooltipElem = (
|
||||
<TooltipBubble top={tooltip.rect.top} left={tooltip.rect.left}>
|
||||
{tooltip.title}
|
||||
</TooltipBubble>
|
||||
);
|
||||
getTooltipTail(tooltip: TooltipObject) {
|
||||
const opts = Object.assign(defaultOptions, tooltip.options);
|
||||
if (!opts.showTail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [tooltipElem, this.props.children];
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import DataDescription from './DataDescription.js';
|
||||
import {Component} from 'react';
|
||||
import ContextMenu from '../ContextMenu.js';
|
||||
import Tooltip from '../Tooltip.js';
|
||||
import styled from '../../styled/index.js';
|
||||
import DataPreview from './DataPreview.js';
|
||||
import createPaste from '../../../utils/createPaste.js';
|
||||
@@ -52,6 +53,11 @@ export const InspectorName = styled('span')({
|
||||
color: colors.grapeDark1,
|
||||
});
|
||||
|
||||
const nameTooltipOptions = {
|
||||
position: 'toLeft',
|
||||
showTail: true,
|
||||
};
|
||||
|
||||
export type DataValueExtractor = (
|
||||
value: any,
|
||||
depth: number,
|
||||
@@ -525,9 +531,12 @@ export default class DataInspector extends Component<DataInspectorProps> {
|
||||
const nameElems = [];
|
||||
if (typeof name !== 'undefined') {
|
||||
nameElems.push(
|
||||
<InspectorName key="name" title={(tooltips && tooltips[name]) || ''}>
|
||||
{name}
|
||||
</InspectorName>,
|
||||
<Tooltip
|
||||
title={tooltips && tooltips[name]}
|
||||
key="name"
|
||||
options={nameTooltipOptions}>
|
||||
<InspectorName>{name}</InspectorName>
|
||||
</Tooltip>,
|
||||
);
|
||||
nameElems.push(<span key="sep">: </span>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user