Skip to content

Conversation

@kmcfaul
Copy link
Contributor

@kmcfaul kmcfaul commented Dec 5, 2018

Issue reference: #984

What: Fix focus trap disabling the dropdown toggle function. Pipe onSelect event to DropdownItem, allowing its disabled property to control if the event is fired. In our example docs, this means that clicking a disabled link will not close the dropdown.

@patternfly-build
Copy link
Collaborator

PatternFly-React preview: https://1014-pr-patternfly-react-patternfly.surge.sh

mcarrano
mcarrano previously approved these changes Dec 6, 2018
Copy link
Member

@mcarrano mcarrano left a comment

Choose a reason for hiding this comment

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

Looks great. Thanks for making these changes @kmcfaul !

@jgiardino
Copy link
Contributor

Thanks for making these updates! I tested across different devices.

  • Testing this with a mouse, it works as expected.
  • Testing this with the keyboard, it works as expected.
  • Testing on an iOS device, I can touch the toggle and collapse the menu. But touching anywhere outside the dropdown toggle or menu does not collapse the menu. Whereas with a mouse, I am able to click anywhere outside the toggle and menu to collapse the menu.
    • Since we plan to use the solution implemented for modal in feat(PF4-Modal): Adds keyboard and screen reader focus trapping #1011 with the dropdown panel variation, it might be worth checking if this solution provides any similar interaction. Although I'm assuming we would only implement this for the variation we're calling Dropdown Panel (i.e. custom contents in the popup), and this might not help us with the menu solution.
  • Testing this with JAWS, NVDA and VO, I am able to place focus on the disabled elements, which is expected (as noted in the APG for Menu—scroll down to the green info box). When I hit the Enter key while a disabled item has focus, the menu dismisses. Unfortunately, I can't find any suggestion on what should happen if the Enter key is pressed at this time. My suggestion would be to ignore Enter key presses when the menu is open and a disabled item has screen reader focus. I don't think this is a major issue if that's not possible.

<Component
{...additionalProps}
className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
onClick={onSelect}
Copy link
Contributor

@michaelkro michaelkro Dec 7, 2018

Choose a reason for hiding this comment

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

There is a bit of an issue with always setting onClick to onSelect here. If the dropdown has multiple button items, this will set all of their onClick handlers to onSelect (so all buttons will fire the same action).

One possible solution would be to add a handleOnClick handler to DropdownItem and do something like the following

// ...
return (
  <li>
    <Component
      {...additionalProps}
      className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
      onClick={event => handleOnClick && handleOnClick(event, onSelect)}
    >
      {children}
    </Component>
  </li>
);

Then the consuming app could do something like

<DropdownItem
  // ...
  component="button"
  handleOnClick={(event, onSelect) => {
    // fire off the particular action for this button
    onSelect();
  }}
>
// ...
</DropdownItem>

But this would mean that all DropdownItems (even the "link" ones) will require an explicit handleOnClick handler to ensure that onSelect is fired.

};

const DropdownItem = ({ className, children, isHovered, component: Component, isDisabled, ...props }) => {
const DropdownItem = ({ className, children, isHovered, onSelect, component: Component, isDisabled, ...props }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

whoops! onSelect needs to be added to the propTypes

@coveralls
Copy link

coveralls commented Dec 8, 2018

Pull Request Test Coverage Report for Build 3880

  • 14 of 16 (87.5%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.07%) to 81.129%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/patternfly-4/react-core/src/components/Dropdown/DropdownItem.js 11 12 91.67%
packages/patternfly-4/react-core/src/components/Dropdown/DropdownMenu.js 1 2 50.0%
Totals Coverage Status
Change from base Build 3866: -0.07%
Covered Lines: 4371
Relevant Lines: 5079

💛 - Coveralls

@kmcfaul
Copy link
Contributor Author

kmcfaul commented Dec 11, 2018

Added onSelect to prop types, updated the onClick to also call the button's onClick method in addition to the onSelect, updated snapshots

{...additionalProps}
className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
onClick={() => {
Component === 'button' ? (props.onClick && props.onClick(), onSelect()) : onSelect();
Copy link
Contributor

@michaelkro michaelkro Dec 12, 2018

Choose a reason for hiding this comment

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

Nice, def cleaner than my suggestion! Just a couple more things :)

  1. Now that we're explicitly setting onClick handler using a prop, onClick should be added to the propTypes, and can be destructured along with the other props that we exclude from ...props

  2. We should probably pass the onClick event object to the callbacks, just in case the consumers need it

  3. Seeing an eslint error here

    [eslint] Expected an assignment or function call and instead saw an expression. [no-unused-expressions]

    We can do something like the following to get rid of it

    onClick={event => {
      if (Component === 'button') {
        onClick && onClick(event);
        onSelect(event);
      } else {
        onSelect(event);
      }
    }}

    That being said, I'm not sure why Travis isn't running prettier, or maybe this particular rule has been removed. Are we not standardizing around prettier and eslint? @jschuler

position={position}
aria-labelledby={id}
onClick={event => onSelect && onSelect(event)}
onSelect={event => onSelect && onSelect(event)}
Copy link
Contributor

Choose a reason for hiding this comment

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

One last thing, I'd like to discuss the semantics of using onSelect as a handler for click events.

The native select event is fired when some text is being selected, whereas, the native change event is fired for select elements when a change to the element's value is committed by the user.

Since this is a custom implementation, and we're actually hooking into the click event, technically, it does not matter what we call our event handler. In fact, just to make sure, I went through and changed all the onSelects to onSomeEvent, and things seem to work just fine.

However, since this is going to become part of <Dropdown />'s API, I figured it was worth bringing up

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, now that I think about it again, I realize that <Dropdown /> isn't actually a select... it's a menu. So onChange doesn't necessarily make sense either.

This is sort of a weird use case here... What do you think we should do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This may be more of a case of bad nomenclature, as this onSelect isn't a property of the DropdownMenu but a prop that gets passed down to the DropdownItem itself where it is set to the onClick function.

Copy link
Collaborator

@jschuler jschuler Dec 19, 2018

Choose a reason for hiding this comment

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

Suggested change
onSelect={event => onSelect && onSelect(event)}
onSelect={onSelect}

className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
onClick={event => {
if (Component === 'button') {
onClick && onClick(event);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why both onClick and onSelect?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, onSelect is added in the Menu. Maybe remove from propTypes so it is only documented that user can add onClick to the item?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, nevermind, better would be to use context i think

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be best to create a context in Dropdown, and then pass the onSelect through that instead of props. Additionally, when you return the onSelect to the user, apart from the event maybe also return the index

@kmcfaul kmcfaul force-pushed the dropdown-interaction-issues branch 2 times, most recently from cd03a63 to fd5f478 Compare January 7, 2019 14:41
} else if (Component === 'button') {
additionalProps.disabled = isDisabled;
}
class DropdownItem extends React.Component {
Copy link
Contributor

@michaelkro michaelkro Jan 8, 2019

Choose a reason for hiding this comment

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

Out of curiosity, what is the reasoning behind switching to a class based component? In any case, if we decide to keep this as a class, we can switch the context stuff over to the contextType syntax

EDIT: hmm, maybe not on the contextType. Trying to get it working locally and it's being weird.

if (Component === 'button') onClick && onClick(event);
onSelect && onSelect(event);
} else {
Function.prototype;
Copy link
Contributor

Choose a reason for hiding this comment

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

Getting an eslint error here

Expected an assignment or function call and instead saw an expression.eslint(no-unused-expressions)

I think, if we want the handler to do nothing in case isDisabled is true, we can just omit this else block

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think you are right, I will remove the block

isDisabled: false,
href: '#'
href: '#',
onSelect: Function.prototype,
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing from propTypes :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, I'm wrong, we need to do the opposite. I think we need to remove onSelect from defaultProps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Must've missed removing it when I took out the proptypes, will remove

Select event should not fire when disabled actions in a menu are selected.
disabled items may be enabled by application code, so should still have the onSelect attached.
Ensures that if the component is a button, the unique onClick is still fired in addition to the
onSelect
@kmcfaul kmcfaul force-pushed the dropdown-interaction-issues branch from 1857284 to 8640789 Compare January 9, 2019 20:42
tlabaj
tlabaj previously approved these changes Jan 9, 2019
Copy link
Contributor

@tlabaj tlabaj left a comment

Choose a reason for hiding this comment

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

LGTM

jgiardino
jgiardino previously approved these changes Jan 9, 2019
Copy link
Contributor

@jgiardino jgiardino left a comment

Choose a reason for hiding this comment

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

Same here, since the issue I noted about touch is captured in a separate issue.

'': PropTypes.any
'': PropTypes.any,
/** Callback for click event */
onClick: PropTypes.func
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also need to update the props in the DropdownItem.d.ts file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add the onClick to the ts file

@kmcfaul kmcfaul dismissed stale reviews from jgiardino and tlabaj via ccd4cf3 January 9, 2019 21:52
@jschuler jschuler merged commit a72d4e6 into patternfly:master Jan 10, 2019
@tlabaj tlabaj mentioned this pull request Jan 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants