情報については、テザー ツールチップを使用します。これは、DropJS の非常に単純なラッパー (いくつかのデフォルト クラスと CSS クラスを追加するだけ) であるため、DropJS で同じ種類のコードを使用できることを願っています。
ラッパー コンポーネント WithTooltip を作成しました。次のように簡単に使用できます。
render: function () {
return (
<WithTooltip content={this.renderTooltipContent()} position="bottom left">
{this.renderContent()}
</WithTooltip>
);
}
ツールチップ (またはドロップ) コンテンツは、単純なテキストだけでなく、React コンポーネントでもあることに注意してください。動作は「ポータル」に非常に似ています
ツールチップ コンテンツ内でもReact コンテキストを使用できますが、0.14 以降では、新しいメソッドを使用する必要があります。renderSubtreeIntoContainer
現在使用している生の完全な WithTooltip コードを次に示します。
'use strict';
var React = require("react");
var _ = require("lodash");
var $ = require("jquery");
var TetherTooltip = require("tether-tooltip");
var WithLongHoverBehavior = require("common/withLongHoverBehavior");
var AppMediaqueries = require("appMediaqueries");
// See https://github.com/facebook/react/issues/4081
// See https://github.com/facebook/react/pull/4184
// See https://github.com/facebook/react/issues/4301
//var renderSubtreeIntoContainer = require("react-dom").unstable_renderSubtreeIntoContainer;
var ValidTooltipPositions = [
'top left',
'left top',
'left middle',
'left bottom',
'bottom left',
'bottom center',
'bottom right',
'right bottom',
'right middle',
'right top',
'top right',
'top center'
];
var TooltipConstraints = [
{
to: 'window',
attachment: 'together',
// Can be important because tether can switch from top to bottom, or left to right,
// but it does not handle correctly bottom-left to bottom-right for exemple
// Using pin will at least make the tooltip stay on the screen without overflow
// (but there's a CSS bug that makes the tooltip arrow hidden by the content I think)
pin: true
}
];
/**
* A wrapper to set around components that must have a tooltip
* The tooltip knows how to reposition itself according to constraints on scroll/resize...
* See http://github.hubspot.com/tooltip/
*/
var WithTooltip = React.createClass({
propTypes: {
// The children on which the tooltip must be displayed on hover
children: React.PropTypes.node.isRequired,
// The prefered position (by default it will try to constrain the tooltip into window boundaries
position: React.PropTypes.oneOf(ValidTooltipPositions),
// The tooltip content (can be an inlined HTML string or simple text)
// If not defined, the tooltip will be disabled
content: React.PropTypes.node,
// Permits to disable the tooltip
disabled: React.PropTypes.bool,
// Wether this tooltip can be hovered or not (useful if the tooltip contains buttons)
hoverable: React.PropTypes.bool
},
isDisabled: function() {
if ( this.props.disabled ) {
return true;
}
else if ( !this.props.content ) {
return true;
}
else {
return false;
}
},
// TODO can probably be optimized?
resetTooltipForCurrentProps: function() {
// The timeout is required because otherwise TetherTooltip messes up with animations entering (ReactCSSTransitionGroup)
// TODO find why! is there a better solution?
setTimeout(function() {
if (this.isMounted()) {
this.destroyTooltip();
// Disable tooltips for mobile, as there's no mouse it does not make sense
// In addition we have encountered weird behaviors in iPhone/iOS that triggers "mouseover" events on touch,
// even after calling preventDefault on the touchstart/end events :(
if ( AppMediaqueries.isMobile() ) {
this.destroyTooltip();
return;
}
if ( !this.isDisabled() ) {
var target = React.findDOMNode(this);
if ( $(target).width() === 0 && $(target).height() === 0 ) {
console.warn("WithTooltip: you are setting a tooltip on an element with 0 width/height. This is probably unwanted behavior",target);
}
this.tetherTooltip = new TetherTooltip({
target: target,
position: this.props.position || 'bottom left',
content: " ", // Disable as we manage the content ourselves
// See https://github.com/HubSpot/tooltip/issues/5#issuecomment-33735589
tetherOptions: {
constraints: TooltipConstraints
}
});
if ( this.props.hoverable ) {
$(this.getTetherTooltipNode()).addClass("tooltip-hoverable");
}
// We mount the tooltip content ourselves because we want to be able to mount React content as tooltip
var tooltipContentNode = $(this.getTetherTooltipNode()).find(".tooltip-content")[0];
if ( React.isValidElement(this.props.content) ) {
//renderSubtreeIntoContainer(this, this.props.content, tooltipContentNode);
React.render(this.props.content, tooltipContentNode);
}
else {
tooltipContentNode.innerHTML = this.props.content;
}
}
}
}.bind(this),0);
},
componentDidMount: function() {
this.resetTooltipForCurrentProps();
},
componentDidUpdate: function(previousProps) {
var positionHasChanged = (this.props.position !== previousProps.position);
var contentHasChanged = (this.props.content !== previousProps.content);
var disabledHasChanged = (this.props.disabled !== previousProps.disabled);
var childrenHasChanged = (this.props.children !== previousProps.children);
var hasChanged = positionHasChanged || disabledHasChanged || contentHasChanged || childrenHasChanged;
if ( hasChanged ) {
this.resetTooltipForCurrentProps();
}
},
componentWillUnmount: function() {
this.destroyTooltip();
},
destroyTooltip: function() {
if ( this.tetherTooltip ) {
this.tetherTooltip.destroy();
delete this.tetherTooltip;
}
},
getTooltipTarget: function() {
if (typeof this.props.children === 'string') {
return <span>{this.props.children}</span>;
} else {
return React.Children.only(this.props.children);
}
},
// It may return nothing if the tooltip is already removed from DOM
getTetherTooltipNode: function() {
return this.tetherTooltip && this.tetherTooltip.drop && this.tetherTooltip.drop.drop;
},
onLongHover: function() {
$(this.getTetherTooltipNode()).addClass("long-hover");
},
onHoverEnd: function() {
$(this.getTetherTooltipNode()).removeClass("long-hover");
},
render: function() {
return (
<WithLongHoverBehavior longHoverDelay={2500} onLongHover={this.onLongHover} onHoverEnd={this.onHoverEnd}>
{this.getTooltipTarget()}
</WithLongHoverBehavior>
);
}
});
module.exports = WithTooltip;