-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add functions to ContextualMenuItem to open and close menus on command #4741
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
Add functions to ContextualMenuItem to open and close menus on command #4741
Conversation
| const anchorRef = createRef<HTMLAnchorElement>(); | ||
| const openSubMenu = this._onItemSubMenuExpand.bind(this); | ||
| const dismissSubMenu = this._onSubMenuDismiss.bind(this); | ||
| const dismissMenu = this.dismiss.bind(this, undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to bind these sine the functions are defined a variables the this should be correct. And for dismissMenu you shound't need to pass in undefined since the parameters are optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm passing undefined because this.dismiss takes in the event (optional) as the first param but dismissMenu takes in one param which is dismissAll, which is the second param in this.dismiss. So I'm padding the params basically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will look into not binding them. I was just following the pattern throughout the file where there are binds
|
Hmmm I will see why I'm adding 350B...I wouldn't think it would be that much based on the changes |
- Move private ContextualMenuItem functions back to local functions - Renames and refactoring
joschect
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tempted to say that we should approach this from another angle. Ref's should be avoided in general, and in this case props could be a way of expressing which item/menu should be open. I'm not sure how much more work is required but I do believe that that's the correct way to move in. @dzearing or @cliffkoh might disagree with me though.
| hasMenu: true | ||
| }; | ||
| } | ||
| const btnRef = createRef<HTMLButtonElement>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really should avoid using refs whenever possible. Especially not in the way that this one is used. Instead of passing a ref down, pass a queryselector through or something like that. If anything the item itself should decide what the target is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ref is required so that the submenu positions itself correctly on the containing and elements (which is what they normally position on) which aren't available in ContextualMenuItem. An alternative approach is to move out the button and anchor items into their own component, like ContextualMenuSplitButton was moved. This would remove the need to pass down a ref (in the code you can see that I don't pass down that ref to ContextualMenuSplitButton). Again curious to hear @dzearing and @cliffkoh opinions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kelseyyoung the best sounding approach you've described would be to split button and anchor into their own components ( ContextualMenuButton and ContextualMenuAnchor) to remove the need to pass down a ref
| const { contextualMenuItemAs } = this.props; | ||
|
|
||
| const openSubMenu = this._onItemSubMenuExpand.bind(this); | ||
| const dismissSubMenu = this._onSubMenuDismiss.bind(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to use bind?
- Move all wrapper items into their own folder under ContextualMenu
…menu-item-functions
|
@jspurlin and @joschect, I've updated the PR where Button and Anchor are moved to their own components, I've called these along with Split Button "ContextualMenuItemWrapper" components. This change means no real changes to ContextualMenuItem. The componentRef in IContextualMenuItem is now passed to one of these wrapper components |
| }; | ||
|
|
||
| let { keytipProps } = item; | ||
| if (keytipProps && itemHasSubmenu) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was not properly copied over, will fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you fixed this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
|
After testing in WAC I'm having some problems where the internal ref that determines where to open the submenu isn't being set fast enough. I'll try to figure out why tomorrow |
- Rename the prop to componentRef on IContextualMenuItem - Add tests for split button and anchor - Pass a prop function which returns the element to use in openSubMenu to ContextualMenuItem
…menu-item-functions
|
Another update: the previous iteration had issues where our internal refs weren't being correctly set. I've moved the open/dismiss functions back to ContextualMenuItem. For openSubMenu to get the element to position on, I've added a 'getContainerElement' prop in ContextualMenuItem which is set by the wrapper components (SplitButton, Button, Anchor). This involves refs internally in the wrapper components but doesn't involve passing them down to ContextualMenuItem. Would love to hear feedback from @joschect and @dzearing |
|
|
||
| export const ContextualMenuItem: React.StatelessComponent<IContextualMenuItemProps> = (props) => { | ||
| const { item, classNames } = props; | ||
| export class ContextualMenuItem extends BaseComponent<IContextualMenuItemProps, {}> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did the extends change from React.StatelessComponent to BaseComponent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To take advantage of componentRef. You can't use a ref on a functional component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option would be to just extend PureComponent and assign ref instead of componentRef. But then naming the prop 'componentRef' is a little misleading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any decision here?
| disabled={ disabled } | ||
| > | ||
| { (keytipAttributes: any): JSX.Element => ( | ||
| <a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ContextualMenuAnchor and ContextualMenuButton are very similar (a different tag is used and a few different props are used). Did you consider creating a generic component that takes a tag and itemprops and would return the correct type of item?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a bad idea, you could even have the tag be a prop that get's passed down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jeremy and I talked about it; in my opinion there are still a few differences between the two and may require more work. I don't know if this PR should be that far reaching. I understand there's now some duplication across these two files, but I guess I would rather log a bug to fix this later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you reply with the bug info for the future work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| openSubMenu={ openSubMenu } | ||
| dismissSubMenu={ dismissSubMenu } | ||
| dismissMenu={ dismissMenu } | ||
| getContainerElement={ this._getContainerElement } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this solution much better overall. I might call it "getMenuTarget" or somehting like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will rename to 'getSubmenuTarget'
|
|
||
| private _renderAnchorMenuItem(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number, focusableElementIndex: number, totalItemCount: number, hasCheckmarks: boolean, hasIcons: boolean): React.ReactNode { | ||
| const { contextualMenuItemAs } = this.props; | ||
| const dismissMenu = this.dismiss.bind(this, undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't bind this here, you don't need to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I have to here. dismiss takes in the event as the first argument but I only pass through the boolean dismissAll in my callback. So I'm binding undefined to only pass through the second argument, basically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event can be undefined, it's fine to have it be undefined all the way up
| </div>); | ||
| } | ||
|
|
||
| private _onItemMouseEnter = (ev: React.MouseEvent<HTMLElement>): void => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are the same as the ButtonItem methods, they should either get passed in as props, or inherited from some base component to reduce code duplication.
…menu-item-functions
- Move shared functions into protected members - Rename getContainerElement to getSubmenuTarget
|
Updated the PR, these are the changes: |
* master: (52 commits) Marqueeselection style update (microsoft#4803) Applying package updates. FocusZone: Add the ability to stop focus from propagating outside the FocusZone (microsoft#4823) Unknown persona coin (microsoft#4809) Applying package updates. BaseButton: Allow Alt + Down on menu buttons to open the menu (microsoft#4807) Applying package updates. Website: Scroll to top of page on navigation (microsoft#4810) Applying package updates. ActivityItem: fix getstyles (microsoft#4802) Mark Slider#ValuePosition enum as deprecated as it is unused. (microsoft#4799) Icon: ensure `styles` still works. (microsoft#4805) Popup: Added check for onBlur to prevent focus setting focus to be incorrectly disabled when closing a menu in Chrome (microsoft#4804) Update package.json Move <Layer> to use React portals when available (microsoft#4724) Fix breadcrumb rendering issue when overflow item is at last index (microsoft#4787) Fixes focus issue for contextual menus (microsoft#4744) Remove redundant selected items prop on BaseExtendedPicker (microsoft#4769) SearchBox: New prop for turning off icon animation (microsoft#4794) Add functions to ContextualMenuItem to open and close menus on command (microsoft#4741) ...
Pull request checklist
$ npm run changeDescription of changes
This work is related to keytips. It gives a way for a developer, given a ref to a contextual menu item, to open and close its menus on command instead of mocking a click or keypress. They are similar to the functions added to BaseButton (openMenu and dismissMenu). The functions added are:
For ContextualMenuItem, these functions will call optional props that are assigned by ContextualMenu. The openSubMenu prop function requires a target to attach the subMenu to; this is also assigned in ContextualMenu by passing down a ref to the container element (the anchor in _renderAnchorMenuItem and the button in _renderButtonItem)
ContextualMenuSplitButton also uses this interface and has access to these functions. It does not need to take in a ref for openSubMenu because the containing div (this._splitButton) already has a ref and is what the subMenu should attach to
Part of the change requires ContextualMenuItem to become a BaseComponent to take advantage of componentRef
Focus areas to test
Wrote unit tests and tested manually