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

[popover2] fix(ContextMenu2): Tooltip2 and autofocus interactions #4744

Merged
merged 4 commits into from
Jun 3, 2021

Conversation

adidahiya
Copy link
Contributor

@adidahiya adidahiya commented Jun 1, 2021

Fixes #4742

Checklist

  • Includes tests
  • Update documentation

Changes proposed in this pull request:

An alternative (much more complicated) fix for #4719, which avoids the regression described in #4742.

I was not able to implement #4719 by simply using mouse events when autoFocus={true} on the ContextMenu2 Popover2. For some reason the React event system seems to propagate mouse enter events to the target after the ContextMenu2 is opened, and that re-opens the tooltip on the shared target.

So I decided to try this alternative approach where ContextMenu2 and Tooltip2 communicate with each other via React context.

In the <ContextMenu2><Tooltip2>...</Tooltip2></ContextMenu2> case, the parent ContextMenu2 creates a Tooltip2Context with forceDisable: true, and the child Tooltip2 reacts to that by consuming the context state.

In the <Tooltip2><ContextMenu2>...</ContextMenu2></Tooltip2> case, the child ContextMenu2 dispatches actions to the parent Tooltip2 context, telling it to force disable itself.

Reviewers should focus on:

Is this React context approach overkill?

Screenshot

2021-06-01 19 29 57

@adidahiya adidahiya changed the title [WIP] [popover2] fix(ContextMenu2): fix Tooltip2 and autofocus interactions [popover2] fix(ContextMenu2): fix Tooltip2 and autofocus interactions Jun 1, 2021
@blueprint-bot
Copy link

s/Popover2Context/Tooltip2Context

Previews: documentation | landing | table

@adidahiya
Copy link
Contributor Author

I probably need to write some more unit tests testing various nesting scenarios, making sure that multiple contexts don't interact with each other unexpectedly.

@adidahiya adidahiya requested review from styu and invliD June 1, 2021 23:47
Copy link
Contributor

@styu styu left a comment

Choose a reason for hiding this comment

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

ooc why is it that we need to do this for context menus but not popovers? e.g. in the scenario where you nest <Tooltip2><Popover2></Popover2></Tooltip2>

// it was likely created by a parent ContextMenu2
return (
<Tooltip2Context.Consumer>
{([state]) => <Tooltip2Provider initialState={state}>{this.renderPopover}</Tooltip2Provider>}
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 if you have nested tooltips? (acknowledging that it's not the best UX, but not ruling out that its possibility 😬 )

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 don't think it would be a possibility in terms of tooltips nested inside tooltip content, but nesting of tooltip targets could be possible (just like nesting of ContextMenu2 targets, as demonstrated in the ContextMenu2 docs example). I'll add a test for it

Copy link
Contributor

Choose a reason for hiding this comment

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

oops yes, i meant targets, thanks for clarifying!

@@ -129,6 +130,9 @@ export const ContextMenu2: React.FC<ContextMenu2Props> = React.forwardRef<any, C
...restProps
} = props;

// ancestor Tooltip2Context state doesn't affect us since we don't care about parent ContextMenu2s, we only want to
// force disable parent Tooltip2s in certain cases through dispatching actions
const [, popover2Dispatch] = React.useContext(Tooltip2Context);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
const [, popover2Dispatch] = React.useContext(Tooltip2Context);
const [, tooltip2Dispatch] = React.useContext(Tooltip2Context);

should be renamed here and below...

// it was likely created by a parent ContextMenu2
return (
<Tooltip2Context.Consumer>
{([state]) => <Tooltip2Provider initialState={state}>{this.renderPopover}</Tooltip2Provider>}
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 don't think it would be a possibility in terms of tooltips nested inside tooltip content, but nesting of tooltip targets could be possible (just like nesting of ContextMenu2 targets, as demonstrated in the ContextMenu2 docs example). I'll add a test for it

@adidahiya
Copy link
Contributor Author

@styu

ooc why is it that we need to do this for context menus but not popovers? e.g. in the scenario where you nest <Tooltip2><Popover2></Popover2></Tooltip2>

so there is actually some brittle code in Popover/Popover2 which force disables direct Tooltip/Tooltip2 children here:

// force disable single Tooltip2 child when popover is open

the above code breaks when there is even one level of indirection (consider a reusable component which renders a Tooltip around something). In places where I'm upgrading to ContextMenu2 I knew that I would need a more robust solution than this existing Popover > Tooltip pattern, so I opted for the React context approach. I may need to consider doing the same thing for Popover2 <> Tooltip2 interactions...

@blueprint-bot
Copy link

address self-review, add unit tests

Previews: documentation | landing | table

@adidahiya
Copy link
Contributor Author

I've added tests for some nesting scenarios, and verified these scenarios work in docs-app:

2021-06-03 11 51 02

2021-06-03 17 17 13

I didn't change the Popover2 > Tooltip2 disable behavior in this PR because I didn't want to introduce scope creep here. Might revisit that in a future PR.


export const Tooltip2Provider = ({ children, forceDisable }: Tooltip2ProviderProps) => {
const [state, dispatch] = React.useReducer(tooltip2Reducer, {});
// // if we have a parent context controlling our state, just use its value and don't use the local state
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove commented code

@blueprint-bot
Copy link

remove commented code

Previews: documentation | landing | table

@adidahiya
Copy link
Contributor Author

Editor's note: this feature was straightforward enough to implement, but notoriously difficult to test. I think I would have more success / an easier time with React Testing Library. Blueprint's testing infra is due for a refresh :/

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.

ContextMenu2 does not close on escape keypress
3 participants