Closed nilaybrahmbhatt closed 6 years ago
Check out https://github.com/tvkhoa/react-tippy
Unfortunately react-tippy
hasn't been updated since September.
I recommend someone make a new wrapper for React? If I knew how, I would provide an offical one for people to use...
Maybe you don't need a "reactized" version of Tippy. It looks like a good idea, but you end up having outdated react libs. In my opion, view-only libs could be used directly as pure JS code, mixed into your React component. The tricky thing here is that you have to put this code in the correct lifecycle methods of the react component as you can't use document.onload
when using React.
I'm using Tippy here with React like this:
componentDidMount() {
window.tippy(window.document.querySelector('#tippedObj`)
...
}
You could use within componentDidUpdate
method too...
window.document.querySelector
to specify the object you want to apply Tippy towindow.tippy
. This is a clean way to use libs that puts global objects to be used in the page, just like you would do with jQuery
.Hope it helps! 👍
With react being so popular, maybe a "how to use with react" in the official docs would be good.
I am now working on a wrapper.
Have something like this.
import React, { Fragment, Component } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import tippy from 'tippy.js'
import classNames from 'classnames'
const PUBLIC_TOOLTIP_NAME = 'data-sl-tooltip-id'
export default class Tooltip extends Component {
static propTypes = {
// Simple text to show
text: PropTypes.string,
// React component to render inside tooltip
component: PropTypes.object,
// If no children is provided, default icon is shown, you can add className using this prop
iconClassName: PropTypes.string,
// If no children is provided, default icon is shown, you can add style using this prop
iconStyle: PropTypes.object,
// The events on the reference element which cause the tooltip to show
trigger: PropTypes.oneOf(['mouseenter focus', 'click', 'manual']),
// If true, the tooltip's position will be updated on each animation frame so
// the tooltip will stick to its reference element if it moves
sticky: PropTypes.bool,
// The placement of the tooltip in relation to its reference
placement: PropTypes.string, // 'bottom', 'left', 'right', 'top-start', 'top-end', etc.
// If true, multiple tooltips can be on the page when triggered by clicks
multiple: PropTypes.bool,
// If true, the tooltip becomes interactive and won't close when hovered over or clicked
interactive: PropTypes.bool,
// The maximum width of the tooltip. Add units such as px or rem
// Avoid exceeding 300px due to mobile devices, or don't specify it at all
maxWidth: PropTypes.string,
// Offsets the tooltip popper in 2 dimensions. Similar to the distance option,
// but applies to the parent popper element instead of the tooltip
offset: PropTypes.string, // '50, 20' = 50px x-axis offset, 20px y-axis offset
// Delays showing/hiding a tooltip after a trigger event was fired, in ms
// Number or Array [show, hide] e.g. [100, 500]
delay: PropTypes.array,
// How far the tooltip is from its reference element in pixels
distance: PropTypes.number,
// The transition duration
// Number or Array [show, hide]
duration: PropTypes.array,
// The type of animation to use
// 'shift-away', 'shift-toward', 'fade', 'scale', 'perspective'
animation: PropTypes.oneOf(['shift-away', 'shift-toward', 'fade', 'scale', 'perspective']),
// Whether to display the arrow. Disables the animateFill option
arrow: PropTypes.bool,
// Transforms the arrow element to make it larger, wider, skinnier, offset, etc.
// CSS syntax: 'scaleX(0.5)', 'scale(2)', 'translateX(5px)' etc.
arrowTransform: PropTypes.string,
// The type of arrow. 'sharp' is a triangle and 'round' is an SVG shape
arrowType: PropTypes.oneOf(['sharp', 'round']),
// If true, the tooltip will flip (change its placement) if there is not enough
// room in the viewport to display it
flip: PropTypes.bool,
// If true, whenever the title attribute on the reference changes, the tooltip
// will automatically be updated
dynamicTitle: PropTypes.bool,
}
static defaultProps = {
trigger: 'mouseenter focus',
sticky: false,
placement: 'top',
multiple: false,
interactive: false,
maxWidth: '300px',
offset: '',
delay: [500, 200],
distance: 16,
duration: [200, 200],
animation: 'fade',
arrow: false,
arrowTransform: '',
arrowType: 'sharp',
flip: false,
dynamicTitle: false,
}
state = { id: `sl_${Date.now()}_${Math.round(Math.random() * 1000000000)}` }
componentDidMount() {
tippy(`[${PUBLIC_TOOLTIP_NAME}="${this.state.id}"]`, this.getTippyProperties())
}
getTippyProperties() {
const defaults = {
iconStyle: {},
iconClassName: '',
dynamicTitle: this.props.dynamicTitle,
animateFill: true,
createPopperInstanceOnInit: true,
trigger: this.props.trigger,
sticky: this.props.sticky,
placement: this.props.placement,
performance: true,
multiple: this.props.multiple,
interactive: this.props.interactive,
maxWidth: this.props.maxWidth,
offset: this.props.offset,
delay: this.props.delay,
distance: this.props.distance,
duration: this.props.duration,
animation: this.props.animation,
arrow: this.props.arrow,
arrowTransform: this.props.arrowTransform,
arrowType: this.props.arrowType,
flip: this.props.flip,
}
if (this.props.component && this.htmlElement) {
const newHTMLElement = this.htmlElement.cloneNode(true)
newHTMLElement.style.display = 'block'
defaults.html = newHTMLElement
}
return defaults
}
getProps = hasTitle =>
Object.assign({}, { [PUBLIC_TOOLTIP_NAME]: this.state.id }, hasTitle ? { title: this.props.text } : {})
renderChildren(hasTitle = true) {
if (!this.props.children) {
return (
<i
{...this.getProps(hasTitle)}
className={classNames('icon', 'icon-question', this.props.iconClassName)}
style={{ marginLeft: 8, ...this.props.iconStyle }}
/>
)
}
return this.props.children(this.getProps(hasTitle))
}
renderPortal() {
return ReactDOM.createPortal(
<div style={{ display: 'none' }} ref={c => (this.htmlElement = c)}>
{this.props.component}
</div>,
window.document.body,
)
}
render() {
if (this.props.component) {
return (
<Fragment>
{this.renderChildren(false)}
{this.renderPortal()}
</Fragment>
)
}
return this.renderChildren()
}
}
Then in app
<Tooltip text="Tooltip" trigger="click">
{tooltipProps => (
<div {...tooltipProps}>
Click me
</div>
)}
</Tooltip>
or
<Tooltip component={<div>My cool component</div>} trigger="click">
{tooltipProps => (
<div {...tooltipProps}>
Click me
</div>
)}
</Tooltip>
that seem way too overkill
Maybe :) It has some bugs, but I guess I prefer more robust solution.
I've made two component wrapper examples for React and Hyperapp linked as gists.
https://github.com/atomiks/tippyjs#component--library-wrappers
Hi @atomiks thank you for creating the wrappers. It really helps.
react-tippy had a useContext
flag that dealt with Redux/Router issues with the context of a Component.
Is this something that you could look into and update the wrapper examples?
@tvkhoa made the improvement int he 1.01v => https://github.com/tvkhoa/react-tippy/commit/e324e62001e77cd1b24046093e39b40fc07b7e16
Thanks
v3 (in beta, stable releasing soon): https://www.npmjs.com/package/@tippy.js/react
I have created a component called Tooltip
as below and placed in my Root component. Therefore all tooltips appear correctly with a default layout. And whenever a dynamic component is to be loaded which requires Tippy
, I import Tooltip
component and then just call Tooltip.rebuild()
in the componentDidMount
method. It works but I'm not sure whether it's a good practice or not to import tippy dynamically like this.
class Tooltip extends React.Component<Props, State> {
static rebuild() {
import('tippy.js').then(({ default: tippy }) => {
tippy('[title]', tippyOptions);
});
}
componentDidMount() {
Tooltip.rebuild();
}
render() {
return null;
}
}
export default Tooltip;
// Root.jsx render:
<div className="root">
{!auth && <TopBar />}
<Header />
{renderRoutes(routes)}
{!auth && <Footer />}
<Toastr />
<Tooltip />
</div>