Center tooltip along top or bottom of target
This adjusts the positioning to work more the way we want: * Tooltip is position on the top or bottom edge of the target depending on where space is available * Tooltip and chevron are centered In addition, more bits borrowed from `ContextualMenu` are not needed, so they have been removed for simplicity. Part of https://github.com/vector-im/riot-web/issues/9753 Part of https://github.com/vector-im/riot-web/issues/9716
This commit is contained in:
parent
6dcdad028e
commit
32bf4588dd
2 changed files with 50 additions and 116 deletions
|
@ -39,79 +39,13 @@ limitations under the License.
|
||||||
z-index: 5001;
|
z-index: 5001;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InteractiveTooltip_right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_right {
|
|
||||||
right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_right {
|
|
||||||
position: absolute;
|
|
||||||
right: -8px;
|
|
||||||
top: 0px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 8px solid transparent;
|
|
||||||
border-left: 8px solid $menu-bg-color;
|
|
||||||
border-bottom: 8px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_right::after {
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 7px solid transparent;
|
|
||||||
border-left: 7px solid $menu-bg-color;
|
|
||||||
border-bottom: 7px solid transparent;
|
|
||||||
position: absolute;
|
|
||||||
top: -7px;
|
|
||||||
right: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_left {
|
|
||||||
left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_left {
|
|
||||||
position: absolute;
|
|
||||||
left: -8px;
|
|
||||||
top: 0px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 8px solid transparent;
|
|
||||||
border-right: 8px solid $menu-bg-color;
|
|
||||||
border-bottom: 8px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_left::after {
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 7px solid transparent;
|
|
||||||
border-right: 7px solid $menu-bg-color;
|
|
||||||
border-bottom: 7px solid transparent;
|
|
||||||
position: absolute;
|
|
||||||
top: -7px;
|
|
||||||
left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip_top {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top {
|
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top {
|
||||||
top: 8px;
|
top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_top {
|
.mx_InteractiveTooltip_chevron_top {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: calc(50% - 8px);
|
||||||
top: -8px;
|
top: -8px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -132,17 +66,13 @@ limitations under the License.
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InteractiveTooltip_bottom {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom {
|
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom {
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InteractiveTooltip_chevron_bottom {
|
.mx_InteractiveTooltip_chevron_bottom {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: calc(50% - 8px);
|
||||||
bottom: -8px;
|
bottom: -8px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
|
@ -33,21 +33,19 @@ function getOrCreateContainer() {
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This style of tooltip takes a `target` element's rect and centers the tooltip
|
||||||
|
* along one edge of the target.
|
||||||
|
*/
|
||||||
export default class InteractiveTooltip extends React.Component {
|
export default class InteractiveTooltip extends React.Component {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
top: PropTypes.number,
|
// A DOMRect from the target element
|
||||||
bottom: PropTypes.number,
|
targetRect: PropTypes.object.isRequired,
|
||||||
left: PropTypes.number,
|
|
||||||
right: PropTypes.number,
|
|
||||||
chevronOffset: PropTypes.number,
|
|
||||||
chevronFace: PropTypes.string, // top, bottom, left, right or none
|
|
||||||
// Function to be called on menu close
|
// Function to be called on menu close
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func,
|
||||||
|
|
||||||
// If true, insert an invisible screen-sized element behind the
|
// If true, insert an invisible screen-sized element behind the
|
||||||
// menu that when clicked will close it.
|
// menu that when clicked will close it.
|
||||||
hasBackground: PropTypes.bool,
|
hasBackground: PropTypes.bool,
|
||||||
|
|
||||||
// The component to render as the context menu
|
// The component to render as the context menu
|
||||||
elementClass: PropTypes.element.isRequired,
|
elementClass: PropTypes.element.isRequired,
|
||||||
// on resize callback
|
// on resize callback
|
||||||
|
@ -56,58 +54,64 @@ export default class InteractiveTooltip extends React.Component {
|
||||||
closeTooltip: PropTypes.func,
|
closeTooltip: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
contentRect: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
collectContentRect = (element) => {
|
||||||
|
// We don't need to clean up when unmounting, so ignore
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
contentRect: element.getBoundingClientRect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const props = this.props;
|
||||||
|
const { targetRect } = props;
|
||||||
|
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
const targetLeft = targetRect.left + window.pageXOffset;
|
||||||
|
const targetBottom = targetRect.bottom + window.pageYOffset;
|
||||||
|
const targetTop = targetRect.top + window.pageYOffset;
|
||||||
|
|
||||||
|
// Align the tooltip vertically on whichever side of the target has more
|
||||||
|
// space available.
|
||||||
const position = {};
|
const position = {};
|
||||||
let chevronFace = null;
|
let chevronFace = null;
|
||||||
const props = this.props;
|
if (targetBottom < window.innerHeight / 2) {
|
||||||
|
position.top = targetBottom;
|
||||||
if (props.top) {
|
chevronFace = "top";
|
||||||
position.top = props.top;
|
|
||||||
} else {
|
} else {
|
||||||
position.bottom = props.bottom;
|
position.bottom = window.innerHeight - targetTop;
|
||||||
|
chevronFace = "bottom";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.left) {
|
// Center the tooltip horizontally with the target's center.
|
||||||
position.left = props.left;
|
position.left = targetLeft + targetRect.width / 2;
|
||||||
chevronFace = 'left';
|
|
||||||
} else {
|
|
||||||
position.right = props.right;
|
|
||||||
chevronFace = 'right';
|
|
||||||
}
|
|
||||||
|
|
||||||
const chevronOffset = {};
|
const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />;
|
||||||
if (props.chevronFace) {
|
|
||||||
chevronFace = props.chevronFace;
|
|
||||||
}
|
|
||||||
const hasChevron = chevronFace && chevronFace !== "none";
|
|
||||||
|
|
||||||
if (chevronFace === 'top' || chevronFace === 'bottom') {
|
|
||||||
chevronOffset.left = props.chevronOffset;
|
|
||||||
} else {
|
|
||||||
chevronOffset.top = props.chevronOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chevron = hasChevron ?
|
|
||||||
<div style={chevronOffset} className={"mx_InteractiveTooltip_chevron_" + chevronFace} /> :
|
|
||||||
undefined;
|
|
||||||
const className = 'mx_InteractiveTooltip_wrapper';
|
|
||||||
|
|
||||||
const menuClasses = classNames({
|
const menuClasses = classNames({
|
||||||
'mx_InteractiveTooltip': true,
|
'mx_InteractiveTooltip': true,
|
||||||
'mx_InteractiveTooltip_left': !hasChevron && position.left,
|
|
||||||
'mx_InteractiveTooltip_right': !hasChevron && position.right,
|
|
||||||
'mx_InteractiveTooltip_top': !hasChevron && position.top,
|
|
||||||
'mx_InteractiveTooltip_bottom': !hasChevron && position.bottom,
|
|
||||||
'mx_InteractiveTooltip_withChevron_left': chevronFace === 'left',
|
|
||||||
'mx_InteractiveTooltip_withChevron_right': chevronFace === 'right',
|
|
||||||
'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top',
|
'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top',
|
||||||
'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom',
|
'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const menuStyle = {};
|
||||||
|
if (this.state.contentRect) {
|
||||||
|
menuStyle.left = `-${this.state.contentRect.width / 2}px`;
|
||||||
|
}
|
||||||
|
|
||||||
const ElementClass = props.elementClass;
|
const ElementClass = props.elementClass;
|
||||||
|
|
||||||
return <div className={className} style={{...position}}>
|
return <div className="mx_InteractiveTooltip_wrapper" style={{...position}}>
|
||||||
<div className={menuClasses}>
|
<div className={menuClasses} style={menuStyle} ref={this.collectContentRect}>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
<ElementClass {...props} onFinished={props.closeTooltip} onResize={props.windowResize} />
|
<ElementClass {...props} onFinished={props.closeTooltip} onResize={props.windowResize} />
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue