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

Expensify chat - web/desktop, when hovering over a user's name anywhere, show their primary login details #1490

Closed
laurenreidexpensify opened this issue Feb 17, 2021 · 48 comments · Fixed by #1632, #2044 or #2056
Assignees

Comments

@laurenreidexpensify
Copy link
Contributor

laurenreidexpensify commented Feb 17, 2021

If you haven’t already, check out our contributing guidelines for onboarding!


Deliverable:
On web/desktop, when hovering over a user's name anywhere, show their primary login. This includes:

  • In the header of a DM (whether single or group)
  • In the chat itself (ie, the header of a new comment line)
  • The chat row in the LHN (whether single or group)
  • The chat row in the search results (whether single or group)

image

Couple of other notes:

When the full list of names is hidden behind the ... ellipsis for Group DMs in the LHN/search results, I'd suggest we show a grouped list of all the participants' primaryLogins on hover of the ellipsis
for users that have phone numbers as primaryLogins, let's drop the @expensify.sms portion, so it's displayed on hover as: +447814266906 and not +447814266906@expensify.sms

https://www.upwork.com/jobs/~0146d36b55605d9064

@parasharrajat
Copy link
Member

parasharrajat commented Feb 17, 2021

Proposal

  1. We will use the Tooltip component to wrap the report Titles.
  2. Due to the header text is taking the full width of its parent we need to wrap such text into a wrapper view with flex-direction: column to adjust the Tooltip position.
 <View style={{flexDirection: 'column', alignItems: 'flex-start'}}>
            <Tooltip text={props.title}>
                <Text numberOfLines={2} style={[styles.headerText, props.textSize === 'large' && styles.textLarge]}>
                    {props.title}
                </Text>
            </Tooltip>
        </View>

for demo purposes I have added styling here.

  1. Now the issue is that Tooltip adds two wrappers on the nodes which block text-clipping.

    There are two things we need to care of.

    a) Remove the Hoverable component's wrapper view in this way

  const child = _.isFunction(this.props.children)
           ? this.props.children(this.state.isHovered)
           : this.props.children;

       return React.cloneElement(React.Children.only(child), {
           onMouseEnter: this.toggleHoverState,
           onMouseLeave: this.toggleHoverState,
       });

b) Next, we need to update the tooltip component to take style props so that we can give max-width: 100% to allow clipping of text.


  1. We need to allow attaching the tooltip to the body of the page so that It's not hidden by other page elements with some standard z-index.
    we use a container prop which decides the placement of the Tooltip Node to the Body element.

Screenshot_20210217_200244

Part 2

we modify the function here and add another property into the options object with the value of the report name without the @expensify.sms text. Now we will use this for Tooltip text.
https://github.com/Expensify/Expensify.cash/blob/88a0c5e8f137b08c273aa7926e5326a621dd8eca/src/libs/OptionsListUtils.js#L78-L109

@roryabraham
Copy link
Contributor

Due to the header text is taking the full width of its parent we need to wrap such text into a wrapper view with flex-direction: column to adjust the Tooltip position.

I'm not sure I follow why this is necessary. The tooltip should be centered on its children, so if the child is a text element it should be centered on the text.

Now the issue is that Tooltip adds two wrappers on the nodes which block text-clipping

Again, could you explain this problem in a bit more detail?

Next, we need to update the tooltip component to take style props so that we can give max-width: 100% to allow clipping of text.

Why not just give the tooltip a default style of max-width: 100%?

We use a container prop which decides the placement of the Tooltip Node to the Body element.

I've experienced the z-index problem you've describe before (and come up with a different solution), but I'm generally unclear on what you're proposing here.

@roryabraham
Copy link
Contributor

Generally it looks like you've thought out your proposal pretty thoroughly, but you maybe just need to do a better job explaining the base solution. Then for each additional change, make it more clear the problem you're trying to solve, and we can then discuss the best solution.

@parasharrajat
Copy link
Member

parasharrajat commented Feb 19, 2021

@roryabraham . My bad explaining it.

I'm not sure I follow why this is necessary. The tooltip should be centered on its children, so if the child is a text element it should be centered on the text.

https://github.com/Expensify/Expensify.cash/blob/380a148fdd47ce4c37e07ce647e1d9d31af8eb88/src/components/Header.js#L18-L24
the Text element is rendered as a div on the web which takes the full available width in flex parent. so my bad here. we can simply set the parent View as flex-direction: row and set the tooltip max-width: 100%.

 <View style={[styles.flex1, styles.flexRow]}>
        <Tooltip text={props.title}>
            <Text numberOfLines={2} style={[styles.headerText, props.textSize === 'large' && styles.textLarge]}>
                {props.title}
            </Text>
        </Tooltip>
   </View>

Now the issue is that Tooltip adds two wrappers on the nodes which block text-clipping

it adds two wrapper divs, for the text to clip we need to set max-width on the parent wrapper. Max-width should be given to the immediate child of the flex container, it would be the wrapper by Hoverable component. I don't think it's a good idea to give the Hoverable component any styling as it can be used in other places as well. Check the Inspected HTML around the Text(Tooltip is added here)
Screenshot_20210220_031042

And I think we can remove the Hoverable component's View in the following way.

  const child = _.isFunction(this.props.children)
           ? this.props.children(this.state.isHovered)
           : this.props.children;

       return React.cloneElement(React.Children.only(child), {
           onMouseEnter: this.toggleHoverState,
           onMouseLeave: this.toggleHoverState,
       });

We use a container prop that decides the placement of the Tooltip Node to the Body element.

I meant that we need to attach the node of the tooltip to the body element in the DOM otherwise it will be hidden from other elements on the page so we use a prop container(standard name among frameworks) that decides whether we need to attach the Tooltip inline or to the Body of Page.

@roryabraham
Copy link
Contributor

Okay, thanks for the explanation, proposal looks good. Just a couple things:

And I think we can remove the Hoverable component's View in the following way.

For what its worth, this was the original implementation I used for the Hoverable component, but I was convinced to not do it that way in order to reduce complexity and because there's no real reason to enforce that it has only one child. Now that there's a clear reason to not add an additional view, I might suggest you do it like this (so it can still have multiple children if desired):

return React.children.map(children, child => {
    const childWithHoverState = _.isFunction(child)
        ? child(this.state.isHovered)
        : child;
    return React.cloneElement(childWithHoverState, {
        onMouseEnter: this.toggleHoverState,
        onMouseLeave: this.toggleHoverState,
    });
});

Not certain that's 100% correct, but you get the idea hopefully.

I meant that we need to attach the node of the tooltip to the body element in the DOM otherwise it will be hidden from other elements on the page so we use a prop container(standard name among frameworks) that decides whether we need to attach the Tooltip inline or to the Body of Page.

I'm a bit conflicted about this because, while it will work on web, I don't think it will work on native platforms. We want to keep this app 99.9999% cross-platform where possible. So I'd prefer if you just try to work around this by giving a higher z-index to the parent elements that contain a tooltip that's covered up by neighboring elements, rather than trying to attach the tooltip to the root.

If that really can't work for some reason, then I think I'd prefer to see this approach:

  1. Split up the tooltip into a web and native implementation,
  2. On web, always attach it to the root element, rather than adding a new prop
  3. On native, just have it do nothing for now (because we don't have a Hoverable that does anything on native yet anyways).

@parasharrajat
Copy link
Member

parasharrajat commented Feb 19, 2021

because there's no real reason to enforce that it has only one child

I don't think that will there be a case where we need to add two direct children to the Hoverable. Then it would mean that hovering over two at a time, Not Good in Tree-based Dom(Hover state should link to only one node in DOM context https://html.spec.whatwg.org/multipage/semantics-other.html#selector-hover). There will always be a parent who gets hover if we need both children to have the hover effect. Or, Add hoverable to both children separately. CC: @marcaaron

If that really can't work for some reason, then I think I'd prefer to see this approach:

I agree. There will be two implementations one for native (Empty wrapper)and one for the web.

@marcaaron
Copy link
Contributor

marcaaron commented Feb 20, 2021

Sorry just catching up but I'm still stuck on this statement and wondering if we are overthinking the problem to compensate for this assumption here...

I don't think it's a good idea to give the Hoverable component any styling as it can be used in other places as well

It makes sense to not give it a style by default, but if you need to set some styles on the View to achieve some result it seems reasonable to allow. If we pass a container style as a prop to Tooltip can this solve the issue?

@parasharrajat
Copy link
Member

parasharrajat commented Feb 20, 2021

Yeah. @marcaaron. We just need to set some styling for the text to clip in most of the cases.
So it's better to pass props for optional styling. Needs a green signal here.

@laurenreidexpensify
Copy link
Contributor Author

@marcaaron is @parasharrajat good to go here?

@laurenreidexpensify
Copy link
Contributor Author

@marcaaron @roryabraham bump when you get a moment today thanks

@marcaaron
Copy link
Contributor

LGTM. We can hash the rest out in reviews in any case.

@laurenreidexpensify
Copy link
Contributor Author

hi @parasharrajat is there a PR for this one yet?

@parasharrajat
Copy link
Member

Soon, I will add one. @laurenreidexpensify

@parasharrajat
Copy link
Member

Few Questions:

  1. How do we need to show the Logins in the Group DMs in LHN.
    Screenshot_20210304_200601

@laurenreidexpensify
Copy link
Contributor Author

@trjExpensify @shawnborton

@trjExpensify
Copy link
Contributor

👋

  • We should show the primaryLogin of the user in the Group DM you're hovering over

  • In the case where there are many participants and so truncated with the ... ellipsis, when hovering over that ellipsis, we should show a list of all the participants of the Group DM.

@parasharrajat
Copy link
Member

@trjExpensify Thanks. But let me be clear so that we are on the same page.

primaryLogin => Email and not the Report Title.

So

  1. Show the tooltip for the single user Chats while hovering the title.
  2. Show the tooltip on the Ellipse for Group chats only if the text is truncated.
  3. Show the Tooltip with Points 1 and 2 on the search Page.
  4. Show the Tooltip with Points 1 and 2 on the Report Header.
  5. Show the Tooltip for the comment author.

Is the above behavior Correct?

@trjExpensify
Copy link
Contributor

That's almost it! In addition to showing the full list of participants on a Group DM when hovering over an ... - we were also looking to achieve the following for Group DMs in the LHN:

image

image

Is that possible?

@parasharrajat
Copy link
Member

Yup, it is.

@trjExpensify
Copy link
Contributor

Awesome, thanks!

@parasharrajat
Copy link
Member

parasharrajat commented Mar 5, 2021

@marcaaron
So, I have tried two ways to show the user's name tooltip on the Report Title but both of them have their limitations.
First:

(
                <Text style={[styles.optionDisplayName, style]} numberOfLines={1} ref={this.ref}>
                    {_.map(option.participantsList, (participant, index) => (
                        <Fragment key={index}>
                            <Tooltip key={index} text={participant.login} containerStyle={styles.dInline}>
                                <Text>
                                    {participant.displayName}
                                </Text>
                            </Tooltip>
                            {index < option.participantsList.length - 1 ? <Text>{', '}</Text> : null}
                        </Fragment>
                    ))}
                </Text>
            );

Second:

<Tooltip text={activeParticipant}>
                   <Text style={[styles.optionDisplayName, style]} numberOfLines={1}>
                       {_.map(option.participantsList, (participant, index) => (
                           <>
                               <Hoverable
                                   onHoverIn={() => this.selectParticipant(index)}
                                   containerStyle={styles.dInline}
                               >
                                   <Text>
                                       {participant.displayName}
                                   </Text>
                               </Hoverable>
                               {index < option.participantsList.length - 1 ? <Text>{', '}</Text> : null}
                           </>
                       ))}
                   </Text>
               </Tooltip>
               

The first one shows a separate tooltip for each name but we have to put display: inline to the containers added by tooltip for the ellipsis to be shown which causes the Tooltip to show at the start of the user name but not in the middle. On the other hand, the second one has one tooltip whose text change based on the user name we are hovering over which causes static change in the text but no animation when moving from one user name to another on Group DM
I have added some logic to detect the ellipsis and show the Tooltip over it which is as follows:

 {
                       isEllipsisActive
                           ? (
                           // visually hidden but it is slightly right from the original ellipsis location
                           
                               <View style={styles.optionDisplayNameTooltipEllipsis}>
                                   <Tooltip text={option.tooltipText}>
                                       <Text>...</Text>
                                   </Tooltip>
                               </View>
                           ) : null
                   }

Now, I need some help to determine if I am on the right path or not.

@roryabraham
Copy link
Contributor

So just so I understand the effect of each of these approaches:

  1. The Tooltip appears left-aligned with individual words, rather than centered in the middle of the word.
  2. The Tooltip is centered on the full title, but does not move to be centered on individual words.

Is that correct?

we have to put display: inline to the containers added by tooltip for the ellipsis to be shown

Have you dug into why this is true? Would it be possible to create a Text wrapper that supports numberOfLines correctly but also uses allows flex children? Also, maybe check out this article because it may be related / lead you to another workaround!

@parasharrajat
Copy link
Member

Thanks for the article, I am going dig more on it.

@laurenreidexpensify
Copy link
Contributor Author

Hi @parasharrajat any luck yet? Can you post a little update of where you're at with the research? Thanks

@parasharrajat
Copy link
Member

Yeah. @laurenreidexpensify I am going to make a screencast of all the approaches. I think we have to pick one which best suited or We have to make changes to the Tooltip.

@roryabraham
Copy link
Contributor

@parasharrajat Did min-width on the flex children help with the text wrapping issue?

@parasharrajat
Copy link
Member

parasharrajat commented Mar 9, 2021

@roryabraham so putting min-width:0; is fine. But the issue is that if there are multiple items, ellipsis show on multiple names, I guess due to the flex container try to shoe all the children and every child is shrinkable.

Another shrinking behavior was that it shrinks the complete item. and just show ... instead for a particular child even if there is space left.

So we just need to find a way to shrink the least children from the last. We need to put flex-shrink only on the last children that should be shrunk.

I am recording this So we have a clear picture.


With Flex Items and setting the Tooltip containers as

flexDirection: 'row',
                                    flexBasis: 'auto',
                                    flexShrink: 1,

Screenshot_20210310_041359


With Inline Items and setting the Tooltip containers as display Inline.

screen-.2.mp4

As you clearly see that we get the desired text wrapping with display inline but the tooltip position is still calculated based on the full width of the content wrapper. On the other hand, Flex items does has text wrapping but multiple items are shrunk at a time to keep all the items visible(default behaviour by browsers) check the note.

Any input would help.

What I think at the moment?

Can we use a Grid layout? I think it's possible with grid layout. check here

@parasharrajat
Copy link
Member

parasharrajat commented Mar 11, 2021

@roryabraham Any input for the comment above.
I think what I can do is

  1. Use inline elements, as I mentioned above, and then shift the tooltip to the left using
  // Any additional amount to manually adjust the horizontal position of the tooltip.
    // A positive value shifts the tooltip to the right, and a negative value shifts it to the left.
shiftHorizontal: PropTypes.number,

    // Any additional amount to manually adjust the vertical position of the tooltip.
    // A positive value shifts the tooltip down, and a negative value shifts it up.
 shiftVertical: PropTypes.number,

But I need to know how to calculate these exactly.

@roryabraham
Copy link
Contributor

roryabraham commented Mar 11, 2021

Sorry for just getting back to this @parasharrajat, but looking at the video in the last comment, it looks like the tooltip is centered on the word, and the text-shrinking is happening as expected? What is the problem with this approach?

@parasharrajat
Copy link
Member

parasharrajat commented Mar 11, 2021

@roryabraham In the video, you see that the tooltip should be rendered over the shrunk title (over the Sidebar) but it is being rendered on the center of the whole text underneath (which goes beyond the sidebar). But I have managed to show it at the correct location using the horizontalShift. Just preparing the PR for review.

@roryabraham
Copy link
Contributor

Okay sounds good, please feel free to add me as a reviewer 👍

@laurenreidexpensify
Copy link
Contributor Author

@parasharrajat is there a PR yet for this one? Thanks

@parasharrajat
Copy link
Member

@laurenreidexpensify Yeah, I made one couple days back which is under review. #1632

@laurenreidexpensify
Copy link
Contributor Author

Awesome! Thanks!!

@roryabraham
Copy link
Contributor

We've had a minor issue with the solution here, in that tooltips are appearing on mobile web.

@parasharrajat
Copy link
Member

Sure. I will pick this one. Let me think about the solution and get back to you, here.

@roryabraham
Copy link
Contributor

@parasharrajat One potential solution is to:

  1. set up a listener in Hoverable/index.js for the touchstart event which sets a property on the class called hoverDisabled
  2. Update setIsHovered so that it doesn't set the hover state to true if hover is disabled:
setIsHovered(isHovered) {
    if (isHovered !== this.state.isHovered && !(isHovered && this.hoverDisabled)) {
        this.setState({isHovered}, isHovered ? this.props.onHoverIn : this.props.onHoverOut);
    }
}
  1. Set up a listener in for the touchend event to set hoverDisabled back to false. That way it's future-proof and we can still offer hover capabilities on hybrid devices with mouse and touch interfaces.

@roryabraham
Copy link
Contributor

Another potential solution (less fleshed out) would be to:

  1. Give the tooltip display animation a delay, so that it only displays after an element has been hovered for 500ms.
  2. onHoverOut, cancel the animation
  3. Set up some global function like hideAllTooltips that we can use to hide any & all tooltips whenever there is a navigation change. That will hide tooltips when modals appear, a new screen is navigated to, etc...

This solution comes with the added bonus that the animation delay (in my opinion) would improve the UX of tooltips, because right now they feel a bit over-zealous.

@roryabraham
Copy link
Contributor

We could also do both 😃

@parasharrajat
Copy link
Member

Do you want the tooltips to be shown on the mobile web?

If no, then I would say just disable the Tooltips for Mobile Web.
Otherwise, I would pick the first solution over the second. It would save a lot of computation.
I like the Idea of animation delay. I guess we will cover this in #1778.

@roryabraham
Copy link
Contributor

Do you want the tooltips to be shown on the mobile web

I think I prefer the first solution I outlined, because it allows us to have tooltips on hybrid devices (which are increasingly common).

@marcaaron
Copy link
Contributor

This solution comes with the added bonus that the animation delay (in my opinion) would improve the UX of tooltips, because right now they feel a bit over-zealous.

Agree. And while we are going back in can we also please tone down the scaling effect here? I think a simple fade would look a lot cleaner, but curious to get @shawnborton's thoughts.

@parasharrajat
Copy link
Member

@roryabraham Unfortunately. First Solution is not working as expected.Read here why #1 - Clicking and Tapping - the "Natural" Order of Things. Touchend fires before mouseEnter thus it resets the state we want.

But I have adjusted it.
We have to replace touchend with touchmove and set the hoverDisabled to false in the setIsHovered when isHovered is false

@shawnborton
Copy link
Contributor

Agree. And while we are going back in can we also please tone down the scaling effect here? I think a simple fade would look a lot cleaner, but curious to get @shawnborton's thoughts.

That works for me. Something nice and clean.

@parasharrajat
Copy link
Member

Okay, @shawnborton @marcaaron. I will refer this to a new PR shortly.

@roryabraham
Copy link
Contributor

I personally think that just the delay will be enough to alleviate the "busyness" that the tooltips have right now. I kind of like the scaling effect.

@roryabraham
Copy link
Contributor

Reopening since @parasharrajat has another PR coming for the tooltip delay

@laurenreidexpensify
Copy link
Contributor Author

Okay - because of the regression with tooltips are appearing on mobile web, payment will now be 7 days from yesterday's deploy - so 31 March.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment