Coder Social home page Coder Social logo

react-floater's Introduction

React Floater

NPM version CI Quality Gate Status Coverage

Advanced tooltips for React!

View the demo

Highlights

  • πŸ– Easy to use: Just set the content
  • πŸ›  Flexible: Personalize the options to fit your needs
  • 🟦 Typescript: Nicely typed

Usage

npm install react-floater

Import it in your app:

import Floater from 'react-floater';

<Floater content="This is the Floater content">
  <span>click me</span>
</Floater>;

And voΓ­la!

Customization

You can use a custom component to render the Floater with the component prop.
Check WithStyledComponents.ts in the demo for an example.

Props

autoOpen boolean β–ΆοΈŽ false
Open the Floater automatically.

callback (action: 'open' | 'close', props: Props) => void
It will be called when the Floater changes state.

children ReactNode
An element to trigger the Floater.

component ComponentType | ReactElement
A React element or function to use as a custom UI for the Floater.
The prop closeFn will be available in your component.

content ReactNode
The Floater content. It can be anything that can be rendered.
This is required unless you pass a component.

debug boolean β–ΆοΈŽ false
Log some basic actions.
You can also set a global variable ReactFloaterDebug = true;

disableFlip boolean β–ΆοΈŽ false
Disable changes in the Floater position on scroll/resize.

disableHoverToClick boolean β–ΆοΈŽ false
Don't convert the hover event to click on mobile.

event 'hover' | 'click' β–ΆοΈŽ click
The event that will trigger the Floater.

This won't work in a controlled mode.

eventDelay number β–ΆοΈŽ 0.4
The amount of time (in seconds) the floater should wait after a mouseLeave event before hiding.

Only valid for event type hover.

footer ReactNode
It can be anything that can be rendered.

getPopper (popper: PopperInstance, origin: 'floater' | 'wrapper') => void
Get the popper.js instance.

hideArrow boolean β–ΆοΈŽ false
Don't show the arrow. Useful for centered or modal layout.

offset number β–ΆοΈŽ 15
The distance between the Floater and its target in pixels.

open boolean
The switch between normal and controlled modes.

Setting this prop will disable normal behavior.

modifiers PopperModifiers
Customize popper.js modifiers.

Type Definition
interface PopperModifiers {
  applyStyles?: Partial<ApplyStylesModifier>;
  arrow?: Partial<ArrowModifier>;
  computeStyles?: Partial<ComputeStylesModifier>;
  eventListeners?: Partial<EventListenersModifier>;
  flip?: Partial<FlipModifier>;
  hide?: Partial<HideModifier>;
  offset?: Partial<OffsetModifier>;
  popperOffsets?: Partial<PopperOffsetsModifier>;
  preventOverflow?: Partial<PreventOverflowModifier>;
}

Don't use it unless you know what you're doing

placement Placement β–ΆοΈŽ bottom
The placement of the Floater. It will update the position if there's no space available.

Type Definition
type Placement = 
| "auto" | "auto-start" | "auto-end"
| "top" | "top-start" | "top-end"
| "bottom" | "bottom-start" | "bottom-end"
| "right"| "right-start" | "right-end"
| "left" | "left-start" | "left-end"
| "center"

portalElement string|HTMLElement
A css selector or element to render the tooltips

showCloseButton boolean β–ΆοΈŽ false
It will show a ⨉ button to close the Floater.
This will be true when you change the wrapperOptions position.

styles Styles
Customize the UI.

Type Definition
interface Styles {
  arrow: CSSProperties & {
    length: number;
    spread: number;
  };
  close: CSSProperties;
  container: CSSProperties;
  content: CSSProperties;
  floater: CSSProperties;
  floaterCentered: CSSProperties;
  floaterClosing: CSSProperties;
  floaterOpening: CSSProperties;
  floaterWithAnimation: CSSProperties;
  floaterWithComponent: CSSProperties;
  footer: CSSProperties;
  options: {
    zIndex: number;
  };
  title: CSSProperties;
  wrapper: CSSProperties;
  wrapperPosition: CSSProperties;
}

target string | HTMLElement
The target element to calculate the Floater position. It will use the children as the target if it's not set.

title ReactNode
It can be anything that can be rendered.

wrapperOptions WrapperOptions
Position the wrapper relative to the target.
You need to set a target for this to work.

Type Definition
interface WrapperOptions {
  offset: number; // The distance between the wrapper and the target. It can be a negative value.
  placement: string; // the same options as above, except center
  position: bool; // Set to true to position the wrapper
}

Styling

You can customize everything with the styles prop.
Only set the properties you want to change, and the default styles will be merged.

Check the styles.ts for the syntax.

Modes

Default
The wrapper will trigger the events and use itself as the Floater's target.

<Floater content="This is the Floater content">
  <span>click me</span>
</Floater>

Proxy
The wrapper will trigger the events, but the Floater will use the target prop to position itself.

<div className="App">
  <img src="some-path" />

  <Floater content="This is the Floater content" target=".App img">
    <span>click me</span>
  </Floater>
</div>

Beacon
It is the same as the proxy mode, but the wrapper will be positioned relative to the target.

<div className="App">
  <img
    src="https://upload.wikimedia.org/wikipedia/commons/2/2d/Google-favicon-2015.png"
    width="100"
    className="my-super-image"
  />

  <Floater
    content="This is the Floater content"
    target=".my-super-image"
    wrapperOptions={{
      offset: -22,
      placement: 'top',
      position: true,
    }}
  >
    <span style={{ color: '#f04', fontSize: 34 }}>β—‰</span>
  </Floater>
</div>

Controlled
Setting a boolean to the open prop will enter the controlled mode and not respond to events.
In this mode, you don't even need to have children

<div className="App">
  <img src="some-path" />
  <Floater content="This is the Floater content" open={true} target=".App img" />
</div>

react-floater's People

Contributors

deevus avatar erlends avatar gilbarbara avatar jconst avatar jucallej avatar marcindabrowski avatar molp avatar perliedman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

react-floater's Issues

Floater ignores rapid changes in 'open' prop

πŸ› Bug Report

When rapidly changing the open prop, i.e. toggling the Floater when it is opening/closing, the Floater will ignore the change in prop.

To Reproduce

  1. Make a floater attach to a button controlled by the open prop
  2. Double-Click the target button to open it and close it while it is still opening

Expected behavior

The target button should be closed after the second click, especially since the Floater accepts the prop state of open: false despite not being closed (since it maintains its own internal state of opened/closed).

MVP

  • render the children in an inline element (span) with event handlers (click, mouseEnter/mouseLeave)
  • dispatch a callback when the handlers are triggered (and autoOpen is false)
  • render the tooltip on mount when open is true
  • calculate the offset of the children (popper)
  • calculate the size of the tooltip on screen (popper)
  • calculate the position of the tooltip and move itself to correct position (popper)
  • handle fixed elements (popper?)
  • hoverable tooltips should have a prop to change the behavior to show itself on the first mouseEnter and start a clock to hide on mouseLeave
  • customize arrow to any valid size (length and spread?)
  • add wrapper on title when it's not a react element
  • add wrapper to footer when it's not a react element
  • animate opacity closing
  • show a close button to hide click type and ability to disable (default: false)
  • test React Portal connection to the parent

scroll into view

it should scroll the window if the tooltip is outside the viewport.
if it's out above the top boundary scroll down to the top of the tooltip.
If below the bottom boundary scroll up until the tooltip bottom is visible.

Allow user to manually set the offset of the arrow

I've been using this library indirectly for an Electron app (with react-joyride), and found the need to be able to move the arrow. This is because I wanted the arrow to point to something outside of the application window.

Use cases for others may be that they create a dialog and want a step to point to it from a parent window.

Unknown prop innerRef for button

In my UI kit I have a warning
Unknown prop innerRef on tag

image

Code from styleguidist

    <div>
      <Tooltip
        content={(
          <div>
            I have an <b>eventDelay</b> prop for <i>hover</i> event that can be adjusted (move you mouse away)!
          </div>)}
        event="hover"
        eventDelay={2.5}
        placement="top"
      >
        <Button>HOVER</Button>
      </Tooltip>
      <p>2.5s delay</p>
    </div>

Button component send all property to button because is needed for custom analytics or another cases

Provide a way to delay opening of the Floater

When using this library for tooltip components, I would like to be able to delay making the Floater visible. This is so that it replicates the behaviour of tooltips you'd normally find on desktop applications.

add support for refs

The problem here is that ref is only set after the first parent render, so it doesn't exist when the Tooltip mounts.

Ideas

  • have a prop to display a beacon using the portal (wrapperOptions)
  • render a transparent overlay that catches outside clicks and closes the tooltip
  • hide on scroll
  • close tooltip by clicking on itself

In case you need any of these, use a controlled tooltip and handle scrolling on the host app

click outside to close the floater

How to close the floater by click outside?

wx20180517-204016 2x

Our site may have many floaters in one page. The floater pops by click event, will be only closed by click the anchor button(i here) or the X button. That was very ugly, could you please give me some advice to implement the functionality.

eventDelay on mobile

When a hover event is converted to click on mobile I expect the floater to disappear after eventDelay time value has elapsed, instead the floater remains until the user click on the trigger again.
Is this a possible new feature? Or is there another way of achieving this?

Floater does not always go away in controlled mode

πŸ› Bug Report

If you thrash the state of the floater in controlled mode, it sometimes does not go away even though open={false}

To Reproduce

Quickly move your mouse over the "Hover Me" button. You should eventually get a floater with the text "I should not be open"

Expected behavior

The floater should disappear when open={false} in controlled mode

Link to repl or repo (highly encouraged)

https://codesandbox.io/s/pwwppv1060?fontsize=14

Run npx envinfo --system --binaries --npmPackages react-floater

Paste the results here:

npx: installed 1 in 1.991s

  System:
    OS: Linux 5.0 Fedora 29 (Workstation Edition) 29 (Workstation Edition)
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 4.92 GB / 31.15 GB
    Container: Yes
    Shell: 4.4.23 - /bin/bash
  Binaries:
    Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node
    Yarn: 1.13.0 - /home/linuxbrew/.linuxbrew/bin/yarn
    npm: 6.8.0 - ~/Projects/vimily/bonjoro/node_modules/.bin/npm
  npmPackages:
    react-floater: ^0.6.4 => 0.6.4 

Scrolling in Safari becomes very choppy when disableAnimation is false on many floaters

πŸ› Bug Report

I have about 40 components with a floater on each element that displays a tolltip when the user hovers over an element. The 40 components are in a scollable container and the user can scroll down and up. On Chrome the scrolling is very smooth, but on Safari the scrolling is very choppy.
Disabling animation with disableAnimation=true on the Floater make scrolling on Safari smooth again.

To Reproduce

See above

Expected behavior

Smooth scrolling in Safari as it is in Chrome

Run npx envinfo --system --binaries --npmPackages react-floater

System:
OS: macOS 10.14.5
CPU: (4) x64 Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
Memory: 75.25 MB / 8.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 11.2.0 - ~/.nvm/versions/node/v11.2.0/bin/node
npm: 6.4.1 - ~/.nvm/versions/node/v11.2.0/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
npmPackages:
react-floater: ^0.6.4 => 0.6.4

Generate React warning

Expected behavior
No warning

Actual behavior

screen shot 2018-07-08 at 16 19 49

Steps to reproduce the problem
Just load a Floater component

React version
15.4.2

React-Tooltips version
3.6.1

Mobile behavior hover

Example: On mobile phones when event is 'hover' and tooltip is a button what opening modal window
I'm click on this button for opening modal and I have active tooltip.

Cannot set property "tooltip" of undefined

I'm getting this error when doing this:

return (
  <span ref={this.textRef} id={`text-${children}-${context || 'NO_CONTEXT'}`}>
    {this.getText(children)}
    {isMissing && (
      <Tooltip content="This is the tooltip content">
        <span style={{ color: '#f04', fontSize: 34 }}>β—‰</span>
      </Tooltip>
    )}
  </span>
);

Don't know if it's related to: #12

Mildly controlled floaters

πŸš€ Feature Idea

It should be possible for the target to trigger a float that will only be closed by the closeFn.

Motivation

If a floater has a lot of content, then we want it to stay up even when the users mouse has moved - esp if the target is small. Then it should be possible to pass a open callback to the target that when clicked triggers the floater and lets it stay up for longer.

Example


<Floater content={
   (closeFloater)=> <div>
           <button onClick={closeFloater.closeFn}>Close</Button>
            <p>Some content</p></div> }
>
      {(open) => <button onClick={open}> Trigger me!</button>}
</Floater>

browser tests

Devices

  • IE 11
  • Edge
  • Safari
  • Mobile Safari
  • Firefox
  • Chrome
  • Chrome Mobile
  • Android 4.4+

Environments

  • react 15 and 16
  • clean react-create-app

ReactFloater erros on tour stop

Expected behavior

Tour stops, I make some operations, tour run

Actual behavior

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the ReactFloater component.

Steps to reproduce the problem

if ([EVENTS.STEP_BEFORE].includes(type) && !this.checkVisible(htmlTarget)) {
  this.setState({
    run : false,
    tutorialLoading: true
  }, () => {
    this.setState({
      run : true,
      stepIndex : 8
    });
  });
}

React version

"react": "^15.5.4",

React-Joyride version

"react-joyride": "^2.0.0-10",

Error stack

If you are having UI issues, make sure to send a public URL or codesandbox example.

Floater will not close when hover control is clicked (iPad, iOS 13, Safari)

πŸ› Bug Report

Using Safari on an iPad, the "convert hover to click" will open the floater but will not close it.

To Reproduce

Open https://d0pt7.csb.app in Safari on an iPadAir 2, iOS version 13.3.1. Note: This does not appear to be an issue on iOS 12. Click on any of the buttons in the hover section of the page, the floater will show. Click on the same button again and the floater will not close.

Expected behavior

The floater should hide once the button is clicked for a second time

  System:
    OS: macOS Mojave 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 1.12 GB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.15.1 - ~/.nvm/versions/node/v10.15.1/bin/node
    Yarn: 1.5.1 - /usr/local/bin/yarn
    npm: 6.9.0 - ~/.nvm/versions/node/v10.15.1/bin/npm

Joyride automatically starts when using center

πŸ› Bug Report

I would like the first joyride step to be at the center of the attached element.
Whenever I do that, I don't see beacon but rather the open tooltip.

To Reproduce

add placement: "center" to the first step of joyride.

Expected behavior

I expect to see the beacon in the center of the element when I open the page and not the open tooltip.

Not possible to hide tooltip when event === hover and wrapperOptions.position === true

Expected behavior
Tooltip should be hidden when mouseleave from one and mouseenter to another tooltip

Actual behavior
Tooltip is not hidden. Floater innerHTML (in this case <span>?</span>) disappeared.

Steps to reproduce the problem

Here is the code

const FloaterComponent = ({ target }) => {
  return <div>
    <Floater
      target={target}
      event="hover"
      eventDelay={1}
      wrapperOptions={{ position: true }}
      content="This is Floater Content">
      <span>?</span>
    </Floater>
  </div>;
};

const Floaters = () => {
  return <div>
    <FloaterComponent target="#one" />
    <FloaterComponent target="#two" />
    <FloaterComponent target="#three" />
    <FloaterComponent target="#four" />
  </div>;
};

ReactDOM.render(
  <div className="wrapper">
    <div id="one">One</div>
    <div id="two">Two</div>
    <div id="three">Three</div>
    <div id="four">Four</div>
    <Floaters />
  </div>,
  document.getElementById('application-container')
);

React version
16.3.1

React-Tooltips version
0.5.2

Error stack

`global` is undefined

πŸ› Bug Report

When I am trying to use the es build of the library with Rollup, I see this error:

Unexpected Syntax: global is not defined

This is related to the issue I am seeing:
#59

To Reproduce

Use the library with rollup, with target es.

Expected behavior

global is not in the ES6 spec and should not be implicitly assumed to have a value. Maybe have something like
var global = typeof self !== undefined ? self : this; on top of the module.

Placement Center with Spotlight

πŸš€ Feature Idea

It would be great to be able to put a label in the center of a spotlight.

Motivation

It would look cleaner and not scroll when highlighting big components

Example

Would be great of instead of this. The label with the info would appear in the middle of the spotlight! I know offsetting is a solution, but will cause a lot of trouble in different size displays.
image

Please provide an example of how this feature would be used.

Wrapped spans shrink width of children

Expected behavior
Responsive and flexibility. Maintain the rendering of children wrapped by floater.
Expect to customize the wrapping spans around the children.

If probably could be a single span wrapped around and a classname as a prop to customize that span

Actual behavior
It wraps the children with 2 spans which doesnt retain the rendering of the children.
The child wrapper has its with set to 100%. A span is an inline element unlike div.

Steps to reproduce the problem
Set children width to 100% wrapped with react-floater in a fixed width container (perhaps).
Additional spans wrapped around children will shrink its width.

React version
16

React-Tooltips version
0.5.5

Error stack

Do not render the floater content before the floater is opened

πŸš€ Feature Idea

When using the component render function the function should not be called before the floater needs to be opened.

Motivation

I got a series of places to use the floater and rendering all the floaters at once delays the rendering of the whole component.

When I render I see all the render functions called at once.

Example

See motivation.

decouple Floater and Popper using some kind of pub-sub

This code

initPopper(target = this.target) {
    const { positionWrapper } = this.state;
    const { disableFlip, getPopper, hideArrow, offset, placement, wrapperOptions } = this.props;
    const flipBehavior = placement === 'top' || placement === 'bottom' ? 'flip' : [
      'right',
      'bottom-end',
      'top-end',
      'left',
      'top-start',
      'bottom-start',
    ];

    /* istanbul ignore else */
    if (placement === 'center') {
      this.setState({ status: STATUS.IDLE });
    }
    else if (target && this.floaterRef) {
      new Popper(target, this.floaterRef, {
        placement,
        modifiers: {
          arrow: {
            enabled: !hideArrow,
            element: this.arrowRef,
            ...this.options.arrow,
          },
          computeStyle: this.options.computeStyle,
          flip: {
            enabled: !disableFlip,
            behavior: flipBehavior,
            ...this.options.flip,
          },
          keepTogether: this.options.keepTogether,
          hide: this.options.hide,
          inner: this.options.inner,
          offset: {
            offset: `0, ${offset}px`,
            ...this.options.offset,
          },
          preventOverflow: this.options.preventOverflow,
          shift: this.options.shift,
        },
        onCreate: (data) => {
          this.popper = data;

          getPopper(data, 'floater');

          this.setState({
            currentPlacement: data.placement,
            status: STATUS.IDLE,
          });

          if (placement !== data.placement) {
            setTimeout(() => {
              data.instance.update();
            }, 1);
          }
        },
        onUpdate: (data) => {
          this.popper = data;
          const { currentPlacement } = this.state;

          if (data.placement !== currentPlacement) {
            this.setState({ currentPlacement: data.placement });
          }
        },
      });
    }

    if (positionWrapper) {
      const wrapperOffset = !is.undefined(wrapperOptions.offset) ? wrapperOptions.offset : 0;

      new Popper(this.target, this.wrapperRef, {
        placement: wrapperOptions.placement || placement,
        modifiers: {
          arrow: {
            enabled: false,
          },
          offset: {
            offset: `0, ${wrapperOffset}px`,
          },
          flip: {
            enabled: false,
          },
        },
        onCreate: (data) => {
          this.wrapperPopper = data;
          this.setState({ statusWrapper: STATUS.IDLE });

          getPopper(data, 'wrapper');

          if (placement !== data.placement) {
            setTimeout(() => {
              data.instance.update();
            }, 1);
          }
        },
      });
    }
  }

particulary calling this.setState inside "lifecycle" of Popper gives this error:
https://github.com/gilbarbara/react-joyride/issues/373
The error seems to creep out if Floater is already unmounted and Popper wants to set its state... voila! :D

Possibility to round the arrow or provide SVG for it

πŸš€ Feature Idea

Hi there, we're already using a popper card in our app but the arrow is not a triangle, and we'd like to override react-joyride tooltip arrow (so the arrow of react-floater)

Motivation

Unify the arrow looks of the Tooltips in our app

Example

this is our arrow
image

Any idea how we could do that ? The code looks like it's drawing polygons!

add unit testing

Scenarios:

  • basic usage
  • custom styles
  • different prop combinations
  • content, footer and title with multiple node types

Multiple tooltips close to each other sometimes do not hide

Thanks for your awesome library. I ran into a small issue:

Expected behavior
The tooltip should hide when hovered quickly on multiple tooltips

Actual behavior
The tooltips are not hidden... its a bit hard to reproduce, but basically if you do it quick enough it will not hide some of them.

Steps to reproduce the problem

  1. Create multiple tooltips.
  2. Set the event type to hover on all of them.
  3. Hover quickly on them

React version
16.2

React-Tooltips version
0.4.5

screen shot 2018-03-17 at 11 28 17 pm

beacon doesn't render on load

I realize this is likely a question for stackoverflow, but I can't seem to find an answer there, and I'm at a loss. I can't get the beacon to render, if you can point out what i'm doing wrong I would be highly appreciative.

Expected behavior
upon component load if this.state.run = true, beacon should load first step

Actual behavior
no errors, but don't see a beacon anywhere

Steps to reproduce the problem
const Joyride = !!global.window && require('react-joyride').default

this.state = {
steps: [
{
target: '.someDiv',
content: 'some text',
placement: 'bottom',
},
],
run: true
}

render(){
let { steps, run } = this.state;
return(
<Joyride
run={this.state.run}
autoStart
steps={this.state.steps}
styles={defaultOptions}
callback={(data)=>this.handleJoyrideCallback(data)}/>
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.