Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

[RFC] Tooltip - base component #1453

Closed
mnajdova opened this issue Jun 5, 2019 · 7 comments · Fixed by #1455
Closed

[RFC] Tooltip - base component #1453

mnajdova opened this issue Jun 5, 2019 · 7 comments · Fixed by #1455
Labels
vsts Paired with ticket in vsts

Comments

@mnajdova
Copy link
Contributor

mnajdova commented Jun 5, 2019

Feature Request

Problem description

We need a Tooltip component as part of Stardust, which is basically a popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it. It should follow the WAI-ARIA Tooltip Pattern

How other frameworks implement the Tooltip component

Material Design

The tooltip is implemented as a component which wraps the element associated with it. For example:

<Tooltip title="Delete">
  <IconButton aria-label="Delete">
    <DeleteIcon />
  </IconButton>
</Tooltip>

Semantic UI React

Do not implement dedicated Tooltip component, they are reusing the Popup on hover for the Tooltip scenarios.

Reakit

The recommended implementation is again in a form of component wrapping the element associated with the tooltip. Example:

import React from "react";
import { Button } from "reakit/Button";
import {
  Tooltip as ReakitTooltip,
  TooltipReference,
  useTooltipState
} from "reakit/Tooltip";

function Tooltip({ children, title, ...props }) {
  const tooltip = useTooltipState();
  return (
    <>
      <TooltipReference {...tooltip}>
        {referenceProps =>
          React.cloneElement(React.Children.only(children), referenceProps)
        }
      </TooltipReference>
      <ReakitTooltip {...tooltip} {...props}>
        {title}
      </ReakitTooltip>
    </>
  );
}

function Example() {
  return (
    <Tooltip title="Tooltip">
      <Button>Reference</Button>
    </Tooltip>
  );
}

Fabric UI

Again the Tooltip is implemented as a wrapper component (TooltipHost) which is appearing on hover/focus. Example:

import * as React from 'react';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { TooltipHost, TooltipDelay, DirectionalHint } from 'office-ui-fabric-react/lib/Tooltip';
import { getId } from 'office-ui-fabric-react/lib/Utilities';

export class TooltipCustomExample extends React.Component<any, any> {
  // Use getId() to ensure that the ID is unique on the page.
  // (It's also okay to use a plain string without getId() and manually ensure uniqueness.)
  private _hostId: string = getId('tooltipHost');

  public render(): JSX.Element {
    return (
      <TooltipHost
        calloutProps={{ gapSpace: 20 }}
        tooltipProps={{
          onRenderContent: () => {
            return (
              <div>
                <ul style={{ margin: 0, padding: 0 }}>
                  <li>1. One</li>
                  <li>2. Two</li>
                </ul>
              </div>
            );
          }
        }}
        delay={TooltipDelay.zero}
        id={this._hostId}
        directionalHint={DirectionalHint.bottomCenter}
      >
        <DefaultButton aria-labelledby={this._hostId} text="Hover Over Me" />
      </TooltipHost>
    );
  }
}

React bootstrap

Here, there is an Overlay component, which is associated with the element via the target prop defined on it (they use ref for this).

class Example extends React.Component {
  constructor(...args) {
    super(...args);

    this.attachRef = target => this.setState({ target });
    this.state = {
      show: false,
    };
  }

  render() {
    const { show, target } = this.state;
    return (
      <>
        <Button
          variant="danger"
          ref={this.attachRef}
          onClick={() => this.setState({ show: !show })}
        >
          Click me to see
        </Button>
        <Overlay target={target} show={show} placement="right">
          {({
            placement,
            scheduleUpdate,
            arrowProps,
            outOfBoundaries,
            show: _show,
            ...props
          }) => (
            <div
              {...props}
              style={{
                backgroundColor: 'rgba(255, 100, 100, 0.85)',
                padding: '2px 10px',
                color: 'white',
                borderRadius: 3,
                ...props.style,
              }}
            >
              Simple tooltip
            </div>
          )}
        </Overlay>
      </>
    );
  }
}

render(<Example />);

Proposed solution

Following the common practices, we will add new Tooltip component, that will have the following API:

Common Stardust props

  • accessibility
  • styles
  • variables
  • className
  • content - the content that will appear inside the tooltip
  • children

Custom component props

  • target - the element to which the tooltip is associated with
  • mouseLeaveDelay - delay in ms for the mouse leave event, before the tooltip will be closed.
  • pointing - whether the tooltip should show pointer towards the target
  • align - alignment for the tooltip. Can have one of the following values: 'top' | 'bottom' | 'start' | 'end' | 'center'
  • mountNode - Existing element the tooltip should be bound to.
  • offset - offset value to apply to rendered component. Accepts the following units:
    • px or unit-less, interpreted as pixels
    • %, percentage relative to the length of the trigger element
    • %p, percentage relative to the length of the component element
    • vw, CSS viewport width unit
    • vh, CSS viewport height unit
  • position - has one of the following value: 'above' | 'below' | 'before' | 'after'. It has higher priority than align. If position is vertical ('above' | 'below') and align is also vertical ('top' | 'bottom') or if both position and align are horizontal ('before' | 'after' and 'start' | 'end' respectively), then provided value for 'align' will be ignored and 'center' will be used instead.
    With this API the component will be similar to the Popup component, and it will be easy for the users to learn the new component's API.

With this API the component will be similar to the Popup component, and it will be easy for the users to learn the new component's API.

@pkumarie2011 pkumarie2011 added the vsts Paired with ticket in vsts label Jun 5, 2019
@layershifter
Copy link
Member

Let's also inspect AntDesign:
https://ant.design/components/popover/
https://ant.design/components/tooltip/

@kuzhelov
Copy link
Contributor

kuzhelov commented Jun 5, 2019

Another example - Kendo UI library of Telerik, represents a set of components that come for cost: https://www.telerik.com/kendo-react-ui/components/tooltip/ . Their implementation strategy, essentially, follows the solution vector you've suggested in the RFC.

@mnajdova
Copy link
Contributor Author

mnajdova commented Jun 6, 2019

The only remaining question is whether we want to allow actionable elements to be added inside the Tooltip. ARIA specifies that the Tooltip should not have focusable elements and this is followed by most of the Frameworks. AntDesign for example have two components the Tooltip - which doesn't support this, and the Popover - similar to our Popup which supports this.

@jurokapsiar
Copy link
Contributor

Agree with the focusable concern. Moving focus to inside of the tooltip is very tricky at least and might be confusing for the users (both keyboard and screen reader). Normal popup should be used for surfaces with focusable elements inside. There is a valid use case for such scenario - coach marks. We will investigate the accessibility side of it, but I think we should keep these scenarios separate.

@codepretty
Copy link
Collaborator

@jurokapsiar does this mean we won't have support to have a link inside a tooltip for now?

image

@jurokapsiar
Copy link
Contributor

If it was up to me, I would say we should never allow them in the tooltip scenario (we can have links in popups though). Let's discuss the usecases offline, I will then post the summary of the discussion here.

@jurokapsiar
Copy link
Contributor

@codepretty this is what was agreed offline:

There are 3 different scenarios:

  1. if the surface is appearing on hover/focus and contains explanatory information, it is a tooltip and it should not contain focusable content
  2. if the surface is appearing on hover or click, it is a popup and can contain focusable elements
  3. if it appears on a different event (application state signal, tutorial purposes), it is a coachmark

We are focusing on the within this issue.

There might still be use cases when actionable elements might be put into the tooltip, we will review those use cases and come up with appropriate component representation. But that is outside of the scope of this issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
vsts Paired with ticket in vsts
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants