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

React: Visualize CreateOrDelete button in table header #10257

Closed
wants to merge 5 commits into from

Conversation

stacey-gammon
Copy link
Contributor

Taking the ReactIcons one step farther up the DOM and introducing buttons with tooltips.

Works on top of the react icons PR.

@stacey-gammon
Copy link
Contributor Author

stacey-gammon commented Feb 9, 2017

UPDATE: Migrated both buttons into a component inside Visualize to avoid the blip, as well as the tooltip prop conflict.

ORIGINAL COMMENT:
@cjcenizal This PR has a blip when hiding/showing the trash and plus icons and I believe it's due to this part of the ui framework style:

  .kuiToolBarSection > * + * {
    margin-left: 10px;
    /* 1 */ }

If I remove it, I'm guessing the angular versions using that code will not work correctly. If that's so, then perhaps I should create a TableActionsComponent to include both the plus and the hide icon and swap it out everywhere, then safely remove that css.

@stacey-gammon stacey-gammon mentioned this pull request Feb 9, 2017
2 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.

Nice work! Just had a few suggestions.

>
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-trash"></span>
</button>
<react-component name="TrashButtonComponent" props="trashButtonProps"></react-component>
Copy link
Contributor

Choose a reason for hiding this comment

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

The jitter for these icons is due to both of these elements existing in the DOM at the same time. I think we should use ng-if to conditionally hide/reveal them instead... I tried it out and it fixes the problem:

<react-component
  name="TrashButtonComponent"
  props="trashButtonProps"
  ng-if="listingController.getSelectedItemsCount() > 0"
></react-component>

<react-component
  name="PlusButtonComponent"
  props="plusButtonProps"
  ng-if="listingController.getSelectedItemsCount() === 0"
></react-component>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How cool, I didn't know you could use ng-if on the ngreact element. I wonder if that will still work with the reactDirective syntax. I will try it out!

Copy link
Contributor Author

@stacey-gammon stacey-gammon Feb 10, 2017

Choose a reason for hiding this comment

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

I ended up getting rid of the need for ng-if by combining these both into one react component. The main reason being the tooltip prop conflicted with the angular one and both tooltips were showing. As a bonus, less logic was needed inside the controller, since fewer props are passed.

throw new Error('Only href or onClick can be defined, not both');
}

if (props.hide) return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should remove this property and logic. Instead, we can hide elements in Angular using ng-if and in React by omitting them from the render function in the parent, e.g.

render() {
  let button;

  if (someCondition === true) {
    button = <Button />;
  }

  return (
    <div>
      Possibly a button:
      {button}
    </div>
  );
}

const classes = classnames('kuiButton', props.className);

if (props.href) {
return <KuiButtonLink {...props} />;
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 make a hard split between Button and ButtonLink. Can we remove this condition here, disallow the href prop in Button, and disallow onClick in ButtonLink? This way, the engineer has to specify exactly which one to use, instead of delegating the decision to the code.

>
{ props.children }
</button>
</KuiTooltip>;
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens in the event that no tooltip is specified? Shouldn't we just return the element without wrapping it in Tooltip?

export function KuiButton(props) {
  const classes = classnames('kuiButton', props.className);
  const button = (
    <button
      className={ classes }
      aria-label={ props.tooltip 
      onClick={ props.onClick }
    >
      { props.children }
    </button>
  );

  if (props.tooltip) {
    return <KuiTooltip text={ props.tooltip }>{button}</KuiTooltip>;
  } else {
    return button;
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Another benefit of this approach is that it makes it easier to migrate from many Tooltip instances to using a single Tooltip instance in an owner component. For example, if we create a VisualizeListing component, we can add a Tooltip instance in that component's render method, and then set tooltip to false/undefined for all of the components it owns

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The migration would work the same with the current implementation since the logic to not render the ReactTooltip is inside KuiTooltip. I did it this way to avoid adding the conditional logic to everything that might have a tooltip. I guess I don't feel very strongly either way, so I can move it out.

{ children }
</a>;

return <KuiTooltip text={ tooltip }> { buttonLink } </KuiTooltip>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here regarding returning a wrapped vs unwrapped element.

import React from 'react';

import { KuiButton } from './kui_button';
import { PlusIcon } from '../icon/plus_icon';
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry to make this comment late, but I think we should name these semantically, e.g. PlusIcon -> CreateIcon, PlusButton -> CreateButton, TrashButton -> DeleteButton. This is because we may want to change the icons that represent these actions, and it will be easier to change them within the implementation and have the entire UI update, than to change each use case one-by-one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense

@@ -23,6 +23,14 @@ import { VisualizeConstants } from './visualize_constants';
import savedObjectRegistry from 'ui/saved_objects/saved_object_registry';
import savedVisusalizationProvider from 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register';

import { CreateOrDeleteButton } from './listing/create_or_delete_button';

import uiModules from 'ui/modules';
Copy link
Contributor Author

@stacey-gammon stacey-gammon Feb 10, 2017

Choose a reason for hiding this comment

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

Question: For app specific react components, should we add the connection at the base of the app (here - index.js) or where they are used (visualize_listing.js). I'm not sure which I like better.

Copy link
Member

Choose a reason for hiding this comment

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

If we want to have the import location consistent with how it's done in React+JSX, we should import the components where they are needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @weltenwort.

import { CreateButtonLink } from 'ui_framework/components/button/create_button_link';
import { DeleteButton } from 'ui_framework/components/button/delete_button';

export function CreateOrDeleteButton({ showCreate, doDelete }) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Next step would probably be to throw this into ui_framework and pass in createHref and objectType, so other types can re-use it.

I don't love how there isn't symmetry between the create and delete process. e.g. one is an href one is an action.

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 avoid destructuring props? I've often had to define a function inside of a stateless functional component, and it's easier to differentiate between these kinds of functions and those provided by props when they are referred to as functionName and props.functionName.

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree. I find destructuring much cleaner in most cases.

@@ -0,0 +1,15 @@
import 'ngreact';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question: Should we collect all ui framework components in here, or try to keep this file updated with only those that are currently being used?

Copy link
Member

Choose a reason for hiding this comment

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

IMHO there would be no benefit in implementing but not exporting some components. We won't be able to consistently apply the "currently being used" criterion for long anyway - it'll probably degrade to a mish-mash after a while.

@stacey-gammon
Copy link
Contributor Author

@cjcenizal Updated the code to wrap both buttons in a single react component to simplify some of the code, and avoid conflicts with the tooltip prop.

I also added some question/comments in the code about areas I was unsure of.

@stacey-gammon stacey-gammon changed the title React buttons React: Visualize CreateOrDelete button in table header Feb 13, 2017
export function CreateOrDeleteButton({ showCreate, doDelete }) {
if (showCreate) {
return <CreateButtonLink href = "#/visualize/new"
tooltip = "Create new visualization" />;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a fan of this kind of aligning. What about this?

return (
  <CreateButtonLink
    href = "#/visualize/new"
    tooltip = "Create new visualization" />
);

(This works nicely with deeper structures, for examples. It also doesn't break on "search and replace" and things like that. Hopefully prettier becomes production-ready really fast, so we no longer have to think about these kinds of things 🎉)

Copy link
Contributor

Choose a reason for hiding this comment

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

If we put the closing bracket on its own line, we'll be aligned with the HTML styleguide:

return (
  <CreateButtonLink
    href = "#/visualize/new"
    tooltip = "Create new visualization"
  />
);

Copy link
Contributor

Choose a reason for hiding this comment

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

import { CreateIcon } from '../icon/create_icon';

export function CreateButtonLink(props) {
return <KuiButtonLink className="kuiButton--primary" {...props}>
Copy link
Contributor

Choose a reason for hiding this comment

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

If props contains className it will override the existing className on this. This should probably be:

const { className, ...rest } = props
const classes = classnames('kuiButton--primary', className)
return <KuiButtonLink className={classes} {...rest}>

or something like that.

export function KuiButton({ className, onClick, tooltip, children}) {
const classes = classnames('kuiButton', className);
const button = <button
className={ classes }
Copy link
Contributor

Choose a reason for hiding this comment

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

Should have 2 spaces, not 4, when indenting this

Copy link
Contributor

Choose a reason for hiding this comment

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

TODO: need to implement the tooltips
- Combine the two buttons into a single react component inside
visualize.  Otherwise the tooltip property became an issue because
there is also an angular tooltip prop.
['react']
extends:
['@elastic/kibana',
'plugin:react/recommended']
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kjbekkelund added the react eslint here. Do you suggest the recommended list? or should I add more?

@spalger am I adding these rules the correct way?

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Good start. It doesn't include "stylistic errors". Unless too busy, maybe @cjcenizal can sync up which other options might be relevant based on the html styleguide? (see https://github.com/yannickcr/eslint-plugin-react#jsx-specific-rules, e.g. https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md)

In general, though, I think we should just start with this and fix that as we go. This is definitely a good start.


CreateOrDeleteButton.propTypes = {
showCreate: React.PropTypes.bool,
doDelete: React.PropTypes.func
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 change this to isCreate to indicate that it's a boolean and not a function, and onDeleteClick to indicate that it's a callback?

@uboness
Copy link

uboness commented Feb 14, 2017

I don't think a CreateOrDelete button should be a component. we should have a create button and a delete button and show one of them based on some state... also, some after some feedback, we talked about not hiding the create button after all, even when rows are selected.

The UX we define today is not about switching between a delete and a create tools... but to simply show the appropriate tools for the current context (in the table's case, selection mode)... these tools can be different, depending on the context, the data shown and where it's shown.

@stacey-gammon
Copy link
Contributor Author

Makes sense @uboness and I agree it's awkward. As discussed in Slack, originally I had just two buttons that were controlled via an ng-if. This caused a problem with their tooltips - both angular and react were using the tooltip prop. So throwing them together was a bit of an afterthought to avoid that problem. If the components can be nested one level deeper, I can get rid of that awkardness, since all the code will reside inside react.

I've gotten a lot of great feedback from this PR. How about I abandon any more iterations on it at this time, and start working on the next larger component, which will remove the need to combine these two buttons into one?

@kjbekkelund Let me know what you think - maybe we can zoom some time tomorrow about a good candidate component that can make the official leap.

@uboness
Copy link

uboness commented Feb 14, 2017

I've gotten a lot of great feedback from this PR. How about I abandon any more iterations on it at this time, and start working on the next larger component, which will remove the need to combine these two buttons into one?

+1 we should definitely not loose all this great feedback moving forward.

@stacey-gammon stacey-gammon deleted the react-buttons branch October 24, 2017 13:54
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