Skip to content

Instantly share code, notes, and snippets.

@lou
Last active February 10, 2023 15:09
Show Gist options
  • Save lou/571b7c0e7797860d6c555a9fdc0496f9 to your computer and use it in GitHub Desktop.
Save lou/571b7c0e7797860d6c555a9fdc0496f9 to your computer and use it in GitHub Desktop.
/*
* Usage:
* <PopoverStickOnHover
* component={<div>Holy guacamole! I'm Sticky.</div>}
* placement="top"
* onMouseEnter={() => { }}
* delay={200}
* >
* <div>Show the sticky tooltip</div>
* </PopoverStickOnHover>
*/
import React from 'react'
import PropTypes from 'prop-types'
import { Overlay, Popover } from 'react-bootstrap'
export default class PopoverStickOnHover extends React.Component {
constructor(props) {
super(props)
this.handleMouseEnter = this.handleMouseEnter.bind(this)
this.handleMouseLeave = this.handleMouseLeave.bind(this)
this.state = {
showPopover: false,
}
}
handleMouseEnter() {
const { delay, onMouseEnter } = this.props
this.setTimeoutConst = setTimeout(() => {
this.setState({ showPopover: true }, () => {
if (onMouseEnter) {
onMouseEnter()
}
})
}, delay);
}
handleMouseLeave() {
clearTimeout(this.setTimeoutConst)
this.setState({ showPopover: false })
}
componentWillUnmount() {
if (this.setTimeoutConst) {
clearTimeout(this.setTimeoutConst)
}
}
render() {
let { component, children, placement } = this.props
const child = React.Children.map(children, (child) => (
React.cloneElement(child, {
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
ref: (node) => {
this._child = node
const { ref } = child
if (typeof ref === 'function') {
ref(node);
}
}
})
))[0]
return(
<React.Fragment>
{child}
<Overlay
show={this.state.showPopover}
placement={placement}
target={this._child}
shouldUpdatePosition={true}
>
<Popover
onMouseEnter={() => {
this.setState({ showPopover: true })
}}
onMouseLeave={this.handleMouseLeave}
id='popover'
>
{component}
</Popover>
</Overlay>
</React.Fragment>
)
}
}
PopoverStickOnHover.defaultProps = {
delay: 0
}
PopoverStickOnHover.propTypes = {
delay: PropTypes.number,
onMouseEnter: PropTypes.func,
children: PropTypes.element.isRequired,
component: PropTypes.node.isRequired
}
@grayinteractive
Copy link

Works great! Thank you!

@dreamerchandra
Copy link

With overlaytrigger


const DefaultMore = ({ ref, onMouseEnter, onMouseLeave }) => (<span className='text-primary' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}> +more  </span>)

const DefaultFirst = ({ firstChild }) => {
  const { props: { children: text } } = firstChild;

  return (<span>{text.slice(0, 10)}...</span>)
}
const DEFAULT_DELAY = 400;

export default function Overlay ({ props, children, placement = "right", trigger = ["hover", "focus"], More = DefaultMore, First = DefaultFirst, delay = DEFAULT_DELAY }) {


  const [FirstChild, ...rest] = children;
  const [showPopover, setShowPopover] = useState(false);

  let setTimeoutConst = null;

  useEffect(() => {
    return () => {
      if (setTimeoutConst) {
        clearTimeout(setTimeoutConst);
      }
    };
  });

  const handleMouseEnter = () => {
    setTimeoutConst = setTimeout(() => {
      setShowPopover(true);
    }, delay);
  };

  const handleMouseLeave = () => {
    clearTimeout(setTimeoutConst);
    setShowPopover(false);
  };

  const Pop = ({ ref, ...triggerHandler }) => {
    return (
      <Popover id="popover-basic" ref={ref} {...triggerHandler} onMouseEnter={() => { setShowPopover(true) }} onMouseLeave={() => setShowPopover(false)}>
        <ul className='hover' >
          {children}
        </ul>
      </Popover>
    )
  };
  Pop.displayName = 'Pop'

  return (
    <>

      {rest.length ? <First firstChild={FirstChild} /> : <>{children}</>}
      {
        rest.length ? <OverlayTrigger show={showPopover} onExiting={() => console.log('exiting')} trigger={trigger} placement={placement} overlay={Pop} className="text-main" delay={{ hide: 400 }}>
          {({ ref, ...triggerHandler }) => (
            <span variant="outline" ref={ref} className="pl-2 cursor-pointer" style={{ border: 'none' }} {...triggerHandler}>
              <More onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}/>
            </span>
          )}
        </OverlayTrigger> : null
      }
    </>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment