Skip to content

Commit

Permalink
new Tooltip component
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcjohnson committed Aug 31, 2021
1 parent f8cf5e4 commit 2717a76
Showing 1 changed file with 275 additions and 0 deletions.
275 changes: 275 additions & 0 deletions components/dash-core-components/src/components/Tooltip.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import PropTypes from 'prop-types';

import _JSXStyle from 'styled-jsx/style'; // eslint-disable-line no-unused-vars

/**
* A tooltip with an absolute position.
*/
const Tooltip = props => {
const {bbox, border_color, background_color, loading_state} = props;
const is_loading = loading_state?.is_loading;
const show = props.show && bbox;

This comment has been minimized.

Copy link
@alexcjohnson

alexcjohnson Aug 31, 2021

Author Collaborator

One modification vs. @xhlulu (and @rreusser)'s work in plotly/dash-core-components#940 and plotly/dash-core-components#982: with missing bbox we don't fail, we just don't show the element. This way you never have to explicitly provide a blank bbox, simplifies usage.

This comment has been minimized.

Copy link
@xhluca

xhluca Aug 31, 2021

makes sense to me!


return (
<>
<div className='dcc-tooltip-bounding-box'>
<span
data-dash-is-loading={is_loading || undefined}
className={`hover hover-${props.direction}`}
>
<span
className={`hover-content ${props.className}`}
style={props.style}
>
{is_loading ? (
<span>{props.loading_text}</span>
) : (
props.children
)}
</span>
</span>
</div>
<style jsx>{`
.dcc-tooltip-bounding-box {
position: absolute;
top: ${bbox?.y0 || 0}px;
left: ${bbox?.x0 || 0}px;
width: ${bbox?.x1 - bbox?.x0 || 0}px;
height: ${bbox?.y1 - bbox?.y0 || 0}px;
display: ${show ? 'inline-block' : 'none'};
pointer-events: ${props.targetable ? 'auto' : 'none'};
}
.hover {
position: absolute;
}
.hover-right {
/* Offset so that the triangle caret lands directly on what's hovered */
transform: translate(5px, 0);
top: 50%;
left: 100%;
}
.hover-left {
transform: translate(-5px, 0);
top: 50%;
}
.hover-bottom {
transform: translate(0, 6px);
top: 100%;
left: 50%;
}
.hover-top {
transform: translate(0, -5px);
left: 50%;
}
.hover-content {
position: absolute;
border: 1px solid ${border_color};
border-radius: 2px;
padding: 5px 10px;
background: ${background_color};
white-space: nowrap;
z-index: ${props.zindex};
pointer-events: none;
}
.hover .hover-content,
.hover-right .hover-content {
transform: translate(0, -50%);
}
.hover-left .hover-content {
transform: translate(-100%, -50%);
}
.hover-top .hover-content {
transform: translate(-50%, -100%);
}
.hover-bottom .hover-content {
transform: translate(-50%, 0);
}
/* Add a small triangle on the left side of the box */
.hover:before,
.hover:after {
content: '';
width: 0;
height: 0;
position: absolute;
border-style: solid;
top: -6px;
z-index: ${props.zindex};

This comment has been minimized.

Copy link
@alexcjohnson

alexcjohnson Aug 31, 2021

Author Collaborator

The before/after pseudo-elements need zindex too, so the caret can cover the border of the box
Screen Shot 2021-08-31 at 2 51 14 PM

This comment has been minimized.

Copy link
@xhluca

xhluca Aug 31, 2021

good idea!

}
.hover:before,
.hover:after,
.hover-right:before,
.hover-right:after {
border-width: 6px 6px 6px 0;
}
.hover-top:before,
.hover-top:after {
border-width: 6px 6px 0 6px;
}
.hover-bottom:before,
.hover-bottom:after {
border-width: 0 6px 6px 6px;
}
.hover-left:before,
.hover-left:after {
border-width: 6px 0 6px 6px;
}
.hover:before,
.hover-right:before {
border-color: transparent ${border_color} transparent
transparent;
left: -5px;
}
.hover:after,
.hover-right:after {
border-color: transparent ${background_color} transparent
transparent;
left: -4px;
}
.hover-left:before {
border-color: transparent transparent transparent
${border_color};
left: -1px;
}
.hover-left:after {
border-color: transparent transparent transparent
${background_color};
left: -2px;
}
.hover-top:before,
.hover-top:after,
.hover-bottom:before,
.hover-bottom:after {
left: -6px;
}
.hover-bottom:before {
border-color: transparent transparent ${border_color}
transparent;
}
.hover-bottom:after {
border-color: transparent transparent ${background_color}
transparent;
top: -5px;
}
.hover-top:before {
border-color: ${border_color} transparent transparent
transparent;
top: -1px;
}
.hover-top:after {
border-color: ${background_color} transparent transparent
transparent;
top: -2px;
}
`}</style>
</>
);
};

Tooltip.defaultProps = {
show: true,
targetable: false,
direction: 'right',
border_color: '#d6d6d6',
background_color: 'white',
className: '',
zindex: 1,
loading_text: 'Loading...',
};

Tooltip.propTypes = {
/**
* The contents of the tooltip
*/
children: PropTypes.node,

/**
* The ID of this component, used to identify dash components
* in callbacks. The ID needs to be unique across all of the
* components in an app.
*/
id: PropTypes.string,

/**
* The class of the tooltip
*/
className: PropTypes.string,

/**
* The style of the tooltip
*/
style: PropTypes.object,

/**
* The bounding box coordinates of the item to label, in px relative to
* the positioning parent of the Tooltip component.
*/
bbox: PropTypes.exact({
x0: PropTypes.number,
y0: PropTypes.number,
x1: PropTypes.number,
y1: PropTypes.number,
}),

/**
* Whether to show the tooltip
*/
show: PropTypes.bool,

/**
* The side of the `bbox` on which the tooltip should open.
*/
direction: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),

/**
* Color of the tooltip border, as a CSS color string.
*/
border_color: PropTypes.string,

/**
* Color of the tooltip background, as a CSS color string.
*/
background_color: PropTypes.string,

/**
* The text displayed in the tooltip while loading
*/
loading_text: PropTypes.string,

/**
* The `z-index` CSS property to assign to the tooltip. Components with
* higher values will be displayed on top of components with lower values.
*/
zindex: PropTypes.number,

/**
* Whether the tooltip itself can be targeted by pointer events.
* For tooltips triggered by hover events, typically this should be left
* `false` to avoid the tooltip interfering with those same events.
*/
targetable: PropTypes.bool,

/**
* Dash-assigned callback that gets fired when the value changes.
*/
setProps: PropTypes.func,

/**
* Object that holds the loading state object coming from dash-renderer
*/
loading_state: PropTypes.shape({
/**
* Determines if the component is loading or not
*/
is_loading: PropTypes.bool,
/**
* Holds which property is loading
*/
prop_name: PropTypes.string,
/**
* Holds the name of the component that is loading
*/
component_name: PropTypes.string,
}),
};

export default Tooltip;

0 comments on commit 2717a76

Please sign in to comment.