-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
refactor tooltip to fix double focusable component #16052
Conversation
@pecanoro @parasharrajat One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
Summary of the changes:
Why I remove the display inline container style? Why are some Tooltip position switched with its parent? For example:
to
It is because some component like Avatar can't receive the Let me know if you have any other question. Sorry for taking it long! |
@@ -65,7 +65,7 @@ const BaseAnchorForCommentsOnly = (props) => { | |||
onPressIn={props.onPressIn} | |||
onPressOut={props.onPressOut} | |||
> | |||
<Tooltip containerStyles={[styles.dInline]} text={Str.isValidEmail(props.displayName) ? '' : props.href}> | |||
<Tooltip text={Str.isValidEmail(props.displayName) ? '' : props.href}> |
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.
Let's revert this change as it is not related to this PR.
src/components/DisplayNames/index.js
Outdated
@@ -95,7 +95,6 @@ class DisplayNames extends PureComponent { | |||
<Tooltip | |||
key={index} | |||
text={tooltip} | |||
containerStyles={[styles.dInline]} |
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.
Please revert..
src/components/Tooltip/index.js
Outdated
/** | ||
* Returns true if children is a focusable component. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
isFocusable() { | ||
const name = this.props.children.type.displayName; | ||
const isPressableText = name === 'Text' && Boolean(this.props.children.props.onPress); | ||
return isPressableText || ['TouchableOpacity', 'Pressable', 'TouchableWithoutFeedback'].includes(name); | ||
} | ||
|
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 you didn't see my comments during the proposal approval.
add the wrapper only when children do not fit into the criteria for the absolute prop.
Here is what I think should be done.
- Keep the Wrapper View.
- First check for a valid single child. If yes, apply the absolute Tooltip logic.
- otherwise, render the wrapper View as before.
We don't want an explicit dependency for using tooltips. That the child has to be a validELement
. The Wrapper View logic is working great all over the app. It does not have to be changed. For example, #16052 (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.
Once this is done, I will re-review it.
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 can't just simply check if it's a single child and decide to not wrap it with a view as I explained here #16052 (comment). So, if we have this component.
<Tooltip>
<Avatar />
</Tooltip>
it will become like this
<Context.Provider onBlur onMouseEnter onMouseLeave etc..>
<Avatar />
</Context.Provider>
and the callback does not work for context provider, so the tooltip won't show.
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.
How about this
if (this.props.absolute && React.isValidElement(this.props.children)) {
return React.cloneElement(React.Children.only(this.props.children),
For singular, we can use https://legacy.reactjs.org/docs/react-api.html#reactchildrencount
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.
Wait, so we still use the absolute
props? Sorry if I misunderstood your intention. Btw, I will continue to look at this tomorrow as it's EOD for me.
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.
No, I just posted the code for reference. I think the condition might be something like
React.Children.count(children) === 1 && React.isValidElement(this.props.children)
But I just noticed that a Custom component will also be true for valid elements and that was the reason I had to create an absolute prop instead of applying it by default in the initial implementation. Damn, back to basics.
But I don't like the isFocusable
technique. It is very hacky.
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.
May be check for specific elements such as View, Text...
<View> | ||
<TextLink | ||
style={[styles.footerRow, hovered ? styles.textBlue : {}]} | ||
href={row.link} | ||
> | ||
{props.translate(row.translationPath)} | ||
</TextLink> | ||
</View> |
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.
Example link.
Why does it have to be like that?
How is it better? The more your put decisional choices to the developers the more errors they will make. Currently, you don't have to even think about anything for using Tooltips. The only confusion was around absolute but it had a clear purpose. Now we are removing that confusion of |
Actually, current Tooltip will always have one child because we will wrap the children with a View at the end. But with this PR, we can't do this anymore.
We need to wrap both component 1&2 with a View.
It will not become the Tooltip responsibility to add the View wrapper anymore.
My initial thought is that we can pass all view related props to the view wrapper we put manually instead of passing it all to the Tooltip. I have no problem to do the wrapping inside the Tooltip, but my main concern is at above #16052 (comment). EDIT: I just realized you already comment there after refreshing 😄 |
Ok. It might be needed in the future but as of now, I haven't seen any such need.
Which one is better? The user manually puts Views and decides if it is needed or if Tooltip automatically applies it where needed. For example, if I create a modal viewer lib for you and ask you to use it. What will be your preference.
Let me know if that does not work. |
Both approaches have its pro and con, I just think it's more customizable with putting the View wrapper manually and also the concerns that we've been discussing 😄.
Yeah, it does not work because context provider is still a valid element.
That's actually what |
Yup but I don't see a need for customizing the wrapper currently in the app. That's why I don't want to put extra handling for Tooltip at this stage. I think the focusable part is hacky. You are calling the function Let's try to find a way to do #16052 (comment). |
I think this is the hardest thing to achieve. I am thinking that we should check the available props of the children, but I don't find a way to do that. I have printed the children object and does not find any property indicating the available props. You're right we should check the I also just realized. Whatever approaches we take, we still need to know whether the children should be focusable or not. |
Thanks for looking into this. But these focusable checks do not seem very robust. OK, at this stage, I will say we just apply the focusable part of the proposal which is to add focusable={False} to the Tooltip wrapper. |
Do you mean do it like this? let child = (
<View
ref={el => this.wrapperView = el}
onBlur={this.hideTooltip}
+ focusable={false}
- focusable={this.props.focusable}
style={this.props.containerStyles}
>
{this.props.children}
</View>
); But what about the focusable for non-wrapper?
Is there any case for the condition to "fails"? The case that I thought would fail is when the children have The alternative I could think is to pass |
By this, I mean the Is there an advantage of binding the focusable to the prop? |
Ah, I see. But if the children is a normal Text for example, it will become focusable. Is that okay? With the focusable props, we can prevent that to happen, but we need to pass that props to every Tooltip that uses Edit: okay, I just realized again, what if the tooltip children already have its own View wrapper and we apply the absolute logic to it with focusable set to true. The double highlight will still happen. At the end, we need the
Case 1: Pressable
Case 2: Non-focusable Pressable
Case 3: Component that is not
Case 4: Component with
Case 5: Component that does not support
Case 6: Component that does support
Both case 5 & 6 assuming custom component accepts |
I will pull down the branch and try to see what is best. |
Can you please merge main into this? |
Merged with main. |
Hmm, It is hard to find a proper way of detecting a focusable element statically not counting your
This list of components can not be fixed. A slightly smart component that manages focusable prop will break it. |
@bernhardoj Once conflicts are resolved, please ping us so that we can review them asap. This PR is likely to get conflicts very frequently. |
@pecanoro @parasharrajat Solved. I will try to check this regularly to see if there is any conflict. |
Reviewing now before we get more conflicts! |
Ok! Tested! Merging! 🤞 |
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
Just noticed a weird bug with tooltip on main while testing another PR. There is a potential regression from this PR, for small view on desktop or web , the tooltip doesn’t not hide on navigation : cc @bernhardoj, @parasharrajat let’s fix it before it reach staging bugTooltip.mov |
Let me check. |
Oh, it does seem like the issue originated from this PR. |
That is the expected issue we discussed here #16052 (comment) before which will be gone after navigation reboot. cc: @parasharrajat I will look into other linked issue |
Found the issue of emoji picker. Trying to find a solution. |
Here is the cause of the issue: App/src/components/Reactions/AddReactionBubble.js Lines 48 to 71 in 0925c25
The emoji picker requires App/src/components/Tooltip/index.js Lines 124 to 132 in 0925c25
In our case, the emoji add reaction It works fine before because we have a View wrapper which won't get conflicted with it's children. Solution:
ref: (el) => {
this.wrapperView = el;
// Call the original ref, if any
const {ref} = child;
if (_.isFunction(ref)) {
ref(el);
+ return;
}
+
+ if (_.isObject(ref)) {
+ ref.current = el;
+ }
}, No need to check if Let me know if you agree @parasharrajat @pecanoro and I will open the PR. |
Please raise the PR for theses issues. This is more urgent so let's get This fixed first. |
@parasharrajat @pecanoro PR is ready #19659. Please have a review 🙇 |
🚀 Deployed to staging by https://github.com/pecanoro in version: 1.3.19-0 🚀
|
@bernhardoj Let's fix both in the same PR, but let's be proactive about it since it will cause a deploy blocker. |
Added tooltip wrong position fix in the PR. Here is what the PR includes:
|
🚀 Deployed to production by https://github.com/luacmartins in version: 1.3.19-7 🚀
|
Details
We want to fix double highlight of component with tooltip.
Fixed Issues
$ #15796
PROPOSAL: #15796 (comment)
Tests
Same as QA Steps
Offline tests
Same as QA Steps
QA Steps
Web & Desktop only
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)src/languages/*
files and using the translation methodWaiting for Copy
label for a copy review on the original GH to get the correct copy.STYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)/** comment above it */
this
properly so there are no scoping issues (i.e. foronClick={this.submit}
the methodthis.submit
should be bound tothis
in the constructor)this
are necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);
ifthis.submit
is never passed to a component event handler likeonClick
)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Web
Screen.Recording.2023-03-16.at.03.14.34.mov
Mobile Web - Chrome
Mobile Web - Safari
Desktop
Screen.Recording.2023-03-16.at.03.39.56.mov
iOS
Android