Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tooltip component #174

Merged
merged 12 commits into from
Dec 6, 2017
Merged

tooltip component #174

merged 12 commits into from
Dec 6, 2017

Conversation

nreese
Copy link
Contributor

@nreese nreese commented Nov 30, 2017

Adds tooltip component

Adds new service noOverflowPlacement that contains reusable logic for determining placement (top, right, bottom, left) that avoids clipping by window view port

Copy link
Contributor

@bevacqua bevacqua left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some code review comments trying to keep the coding style consistent with what we have in EUI

render() {
let {isSticky, visible, size, className, children, ...others} = this.props;

const newClasses = classnames('tooltip-container', visible ? 'tooltip-container-visible' : 'tooltip-container-hidden',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this function, you can use an object for clarity when it comes to optional classes:

const newClasses = classnames('tooltip-container', {
  'tooltip-container--visible': visible,
  'tooltip-container--hidden': !visible,
  'tooltip-container--hoverable': isSticky,
  [`tooltip-${ size }`]: size !== 'auto'
}, className)

}

render() {
let {isSticky, visible, size, className, children, ...others} = this.props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use const here?


constructor(props) {
super(props);
this.state = {visible: props.trigger === 'manual' ? props.display : false};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be best to break this up into multiple lines to keep things simple:

const visible = props.trigger === 'manual' ? props.display : false
this.state = { visible };

Consider adding a comment about what the conditional does

return (
<div {...newProps}>
{this.props.children}
<Tooltip {...{isSticky, size: this.props.size, visible}}>{tooltip}</Tooltip>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to a variable for readability:

const tooltipProps = {isSticky, size, visible}
return ...<Tooltip {...tooltipProps}>...

onMouseLeave: this.hoverHandler.bind(this)
};
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move all of this out of the way and into its own function:

const triggerHandler = getTriggerHandler.call(this)

...

function getTriggerHandler() {
  if (trigger === 'manual') {
    return {}
  }

  if (triggerHandler === 'click') {
    return {
      onClick: e => this.clickHandler(e, onClick)
    }
  }

  return {
    onClick,
    onMouseEnter: this.hoverHandler.bind(this),
    onMouseLeave: this.hoverHandler.bind(this)
  }
}

}

render() {
const {isSticky, placement, tooltip, trigger, className, clickHideDelay, onEntered, onExited, theme, size, onClick, display, ...others} = this.props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe break this up to list one property per line?

onClick(e);
setTimeout(() => {
this.setState({visible: false});
}, this.props.clickHideDelay);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this component accomplish? It looks like the Focus Trap we're already using elsewhere can do this job without duplicating logic /cc @cjcenizal

export class Tooltip extends React.PureComponent {
static propTypes = {
visible: PropTypes.bool,
size: PropTypes.oneOf(['auto','sm', 'md', 'lg']),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EUI standard sizing properties are single letters: s, m, l. auto is fine as-is

static defaultProps = {
visible: true,
size: 'auto',
isSticky: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue that we either have property names like isVisible isSticky everywhere or visible, sticky, for consistency

isSticky: false
};

constructor(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant constructor can be safely removed

@snide
Copy link
Contributor

snide commented Dec 1, 2017

@nreese Cool. We really need this. I will rewrite the CSS layer (and can do a pass for naming consistency of your props as well) for you tomorrow so don't worry about that side. I can make this pretty with some nice quick animations! From a feature perspective there are a few things that we should probably have.

  • The placement prop should likely accept auto as a value (it should also be the default prop). Basically it would just use top as the value unless the tooltip hit any of the sides of the window. Likely gonna need to do some JS calculations for position. For example, if the tooltip has no room on the right, it should use left as a the placement.
  • Tooltips should accept both a title and a body prop. Body is required, title is not.

@cchaos
Copy link
Contributor

cchaos commented Dec 1, 2017

...unless the tooltip hit any of the sides of the window. Likely gonna need to do some JS calculations for position. For example, if the tooltip has no room on the right, it should use left as a the placement.

+1

I noticed the Pivotal's example for the right positioned tooltip not only extends visually past the window but also causes the window to scroll horizontally. It would be nice if ours was smarter than that and ensures that the tooltip is always positioned inside the current window.

@cjcenizal cjcenizal mentioned this pull request Dec 1, 2017
9 tasks
Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just have a few minor suggestions but overall this looks like a great start. I think we can merge this to unblock you, and then we can refine the styles and functionality in subsequent PRs.

</p>
</div>
}
demo={
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract this demo code into an separate file, so we can also show the source code for it? (Similar to the docs for the other components).

text={
<div>
<p>
https://styleguide.pivotal.io/tooltips#tooltip-triggers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this? I think it's good to keep the link the description of this PR as a reference, but I think we should own this component instead of relying on external documentation for it.


export default props => (
<GuidePage title={props.route.name}>
<GuideSection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a CallOut here with a note that we're going to be changing this around a lot? I just want people to be aware that the Tooltip will change as we define our requirements more clearly.

import {
  EuiButton,
  EuiCallOut,
  EuiCode,
  EuiSpacer,
} from '../../../../src/components';

/* ... */

  <GuidePage title={props.route.name}>
    <EuiCallOut
      title="Work in progress"
      color="warning"
    >
      <p>
        This component is still undergoing active development, and its interface and implementation
        are both subject to change.
      </p>
    </EuiCallOut>

    <EuiSpacer size="l" />

image


return (
<div className={newClasses} {...others}>
<div className="tooltip-content">{children}</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re @snide's comment about body and title, I think we should stick with using children in lieu of body personally -- this feels more ergonomic to me. We've been trying to use children wherever possible. And this way we can add the title prop later without needing to change anything else about the interface.

@cjcenizal
Copy link
Contributor

@nreese Looks like the CI is failing due to linting errors. You can run npm run lint and npm run lint-fix. We have a precommit hook which will also run these for you.

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just merged some changes into master which will cause merge conflicts. Here are the steps for resolving them.

@@ -101,6 +101,9 @@ import TextExample
import ToastExample
from '../../views/toast/toast_example';

import TooltipExample
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fix the merge conflicts with master, this will have to become:

import { TooltipExample }
  from '../../views/tooltip/tooltip_example';

@@ -212,6 +215,9 @@ const components = [{
}, {
name: 'Toast',
component: ToastExample,
}, {
name: 'Tooltip',
component: TooltipExample,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This array of objects has become just a single-dimensional array of references to the example files, so you can replace this object with just TooltipExample.

}
/>
</GuidePage>
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire example will become:

import React from 'react';

import { renderToHtml } from '../../services';

import {
  GuidePage,
  GuideSection,
  GuideSectionTypes,
} from '../../components';

import {
  EuiCallOut,
  EuiSpacer,
  EuiButton,
  EuiCode,
} from '../../../../src/components';

import TooltipExamples from './examples';
const examplesSource = require('!!raw-loader!./examples');
const examplesHtml = renderToHtml(TooltipExamples);

export const TooltipExample = {
  title: 'Tooltip',
  intro: (
    <div>
      <EuiCallOut
        title="Work in progress"
        color="warning"
      >
        <p>
          This component is still undergoing active development, and its interface and implementation
          are both subject to change.
        </p>
      </EuiCallOut>

      <EuiSpacer size="l" />
    </div>
  ),
  sections: [{
    title: 'Tooltip',
    source: [{
      type: GuideSectionTypes.JS,
      code: '',
    }, {
      type: GuideSectionTypes.HTML,
      code: '',
    }],
    text: (
      <p>
      </p>
    ),
    demo: <TooltipExamples />,
  }],
};

<div>
<div>
Check out this
<TooltipTrigger tooltip="I am the body" title="I am the title">
Copy link
Contributor

@cjcenizal cjcenizal Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the way whitespace is interpreted, there is no space between "this" and "tooltip" in these examples:

image

We can fix this like this:

      Check out this {(
        <TooltipTrigger tooltip="I am the body" title="I am the title">
          <span className="overlay-trigger" tabIndex="0">tooltip with title.</span>
        </TooltipTrigger>
      )}

@snide snide mentioned this pull request Dec 4, 2017
51 tasks
@nreese nreese changed the title [WIP] tooltip component tooltip component Dec 5, 2017
@nreese
Copy link
Contributor Author

nreese commented Dec 5, 2017

@cchaos Are you planning on doing the CSS work against this PR or should I finish getting this PR reviewed and merged and then have the CSS work done against master?

@cchaos
Copy link
Contributor

cchaos commented Dec 5, 2017

@nreese Yeah I think that is the plan.

@nreese
Copy link
Contributor Author

nreese commented Dec 5, 2017

@bevacqua @snide I have moved the PR out of the WIP state and am ready for reviews

@nreese nreese requested a review from snide December 5, 2017 16:50
Copy link
Contributor

@snide snide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this for now given the warning at the top. @cchaos and I will work to get the styling done and likely cleanup some of the props / interface so that it's a little simpler, closer to our spec. Functionally though it does everything we need it to do for design to get involved. Thanks @nreese.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants