/* * This file is part of Cockpit. * * Copyright (C) 2016 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ "use strict"; var React = require('react'); require('./tooltip.css'); /* A tooltip, styled via Patternfly/Bootstrap. * * Example: * * * * * * Whenever the mouse hovers over the children of a Tooltip, * the text (or arbitrary element) in the "tip" property is shown. */ var Tooltip = React.createClass({ getInitialState: function () { return { open: false, pos: "top" }; }, onMouseover: function () { this.setState({ open: true }); }, onMouseout: function () { this.setState({ open: false }); }, render: function () { var self = this; if (!self.props.tip) return self.props.children; /* Placement * * We assume that there is enough space to display the tooltip * in its natural width, and that there is either enough space * above or below the element that will show the tooltip. * * If there is enough space above, the tooltip goes to the * top, otherwise below. It is then moved left or right until * it is fully visible. */ /* The life of a tooltip * * The tooltip starts out at position (-10000,0) so that it is * well outside its parent. This seems to give it its natural * width without the browser trying to fit it into the parent, * which usually results in a undesired tall and narrow * layout. * * On first interaction and before moving it into place, we * prevent the tooltip from resizing by setting the "width" * and "height" styles to its current dimensions. * * Once moved into place, it stays where it is and is just * raised and lowered in the Z dimension in addition to being * made visible and invisible. */ function fixDOMElements(tip) { var child = tip && tip.previousElementSibling; // Do nothing unless fully mounted if (!tip || !child) return; // Stop resizing if (!tip.style.width || !tip.style.height) { tip.style.width = tip.offsetWidth + "px"; tip.style.height = tip.offsetHeight + "px"; } // Position it if (tip.offsetLeft === -10000) { var left = child.offsetLeft + 0.5*child.offsetWidth - 0.5*tip.offsetWidth; var top = child.offsetTop - tip.offsetHeight; var arrow = tip.getElementsByClassName("tooltip-arrow")[0]; var arrow_left = tip.offsetWidth / 2; // Figure out where it is on the page var abs_left = left, abs_top = top, max_width = child.offsetWidth; var el = tip.offsetParent; while (el) { abs_left += el.offsetLeft; abs_top += el.offsetTop; max_width = el.offsetWidth; el = el.offsetParent; } // Move it left/right until it is full in view var x_delta = 0; if (abs_left < 0) x_delta = -abs_left + 5; else if (abs_left + tip.offsetWidth > max_width) x_delta = -(abs_left + tip.offsetWidth - max_width) - 5; left += x_delta; arrow_left -= x_delta; // Move it to bottom if abs_top < 0 if (abs_top < 0) { top = child.offsetTop + child.offsetHeight; self.setState({ pos: "bottom" }); } tip.style.left = left + "px"; tip.style.top = top + "px"; arrow.style.left = arrow_left + "px"; } tip.style.zIndex = self.state.open ? 2000 : -2000; } var classes = "tooltip " + self.state.pos; if (self.state.open) classes += " in"; return (
{self.props.children}
{self.props.tip}
); } }); module.exports = { Tooltip: Tooltip, };