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

Update AdderToolbar components and styling #4753

Merged
merged 3 commits into from
Aug 31, 2022
Merged

Conversation

lyzadanger
Copy link
Contributor

@lyzadanger lyzadanger commented Aug 30, 2022

This PR updates the shared components used by AdderToolbar, and updates its Tailwind styling to eliminate the need for external CSS. I'm focusing on this component because I expect we'll make some adjustments in this area soon for accessibility reasons.

This component has some of the most fiddly styling of any of our components. It was also one of the first I converted to Tailwind. Much has been learned since February, but it doesn't eliminate the reality that the hover behavior for the AdderToolbar is rather nuanced.

These changes:

  • Bump frontend-shared to 5.4.1 to get the correct AnnotateIcon
  • Use updated shared components and icons
  • Refactor styles out of external SASS
    • Add a simple Tailwind utility style to apply all: initial, which is something we do for AdderToolbar (it's in a shadow DOM) and isn't available in stock Tailwind
    • Add a more complex Tailwind variant to address the very specific hover-styling needs of this component.
  • Comment the living daylights out of AdderToolbar's styling

// its SVG dimensions dictate
'h-auto w-auto z-2',
'text-grey-3 fill-white',
<div
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's an example of me adding an additional parent component to handle the positioning, and not applying that styling directly to the icon component. This is less of a real concern with icons, which have no classes applied to them other than what you set on className, but it's more hygienic IMHO.

Comment on lines 88 to 103
<ButtonBase
classes={classnames(
'flex-col gap-y-1 py-2.5 px-2',
'text-annotator-sm leading-none',
// Default color when the toolbar is not hovered
'text-grey-7',
// When the parent .group element is hovered (but this element itself is
// not), dim this button's text. This has the effect of dimming inactive
// buttons.
'group-hover:text-grey-5',
// When the parent .group element is hovered AND this element is
// hovered, this is the "active" button. Intensify the text color, which
// will also darken any descendant Icon
'hover:group-hover:text-grey-9',
// Set the .hover-group class for use by `NumberIcon`
'hover-group'
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 use case is a good example of both the pros and cons of ButtonBase. On the "pros" side, we automatically get consistent transitions and focus rings and whatnot, so those don't have to be managed manually, nor risk being inconsistent. On the other hand, I just kind of have to "know" that ButtonBase already sets a flex layout, so I just need to set flex-col to go columnar. But I can feel assured that padding and colors won't conflict, as the coalescing convention for base classes is never to style dimensions or colors...

)}
onClick={onClick}
title={title}
>
{icon && <Icon classes="text-annotator-lg" name={icon} title={title} />}
{Icon && <Icon className="text-annotator-lg" title={title} />}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

At least className gives a hint that your classes are the only classes...

dir="ltr"
style={{
visibility: isVisible ? 'visible' : 'hidden',
}}
>
<div className="flex dim-items-on-hover">
<div className="flex group">
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 wish Tailwind's group class had a better name. It's so...vague.

// Custom variant for a specific use case in which an element needs to
// style itself based on the hover states of TWO parents (.group and
// .hover-group). See `src/annotator/components/AdderToolbar`
addVariant('hover-group-hover', '.group:hover .hover-group:hover &');
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'm not gonna mince words: this is a tad fugly. I hope the comments everywhere help. I've just never quite run into a situation like this before, where the hover state of two ancestors has to be assessed.

Copy link
Member

Choose a reason for hiding this comment

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

A few searches online finds that plenty of folks have found use cases for nested groups, similar to what we have here. It doesn't seem that unusual to me. The basic solutions I've seen boil down to adding variants to create additional groups that are named by semantic function or level.

Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

Regarding NumberIcon colors, it looks to me like this particular use case can be solved without needing to deal with nested groups using currentcolor. The need for styling which depends on the states of multiple ancestors doesn't seem that unusual to me (eg. see Tailwind feature request I linked in the comments), and I think it is something we should be able to accomplish without needing a ton of documentation each time. A high-level description of what the user should see is always useful. The solutions I can think of / find online boil down to one of:

  • Adding some support for labeled groups, with varying degrees of generality, which is basically what you did here. If we're doing this, I'd suggest giving the groups names that reflect their semantic role or level in the hierarchy.
  • Setting CSS variables at the top of the group, which children can then consume. This allows the ancestors to set the context ("the current foreground color is $X") while individual components encapsulate how it is applied ("set the color property to $X"). currentcolor is a special case of this.

Other than NumberIcon, I suggested we stick with with icon as the prop name to match the pattern library, but continue to accept components as values.

The rest of the changes look good to me.

);
}

/**
* @param {object} props
* @param {number} [props.badgeCount]
* @param {string} [props.icon]
* @param {IconComponent} [props.Icon]
Copy link
Member

Choose a reason for hiding this comment

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

We settled on continuing to use the lower-case icon for the prop name in the frontend-shared package. I think we should do the same here.

// when its parent button is hovered, even if this badge's span is not
// directly hovered.
// This is a custom tailwind variant
'hover-group-hover:bg-grey-9'
Copy link
Member

Choose a reason for hiding this comment

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

Another way of doing this in plain CSS might be to pass down a custom variable indicating the foreground color. The toolbar would set this to either the unhovered medium gray or dimmed gray depending on its hover state, and the active button could override it. The various elements inside the button would then consume this foreground color variable in different properties (color, background-color etc.). I don't know off-hand how easy that is to translate to Tailwind.

Copy link
Member

Choose a reason for hiding this comment

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

currentcolor could also work here, although we need to introduce a separate element for the background and the text to use the inherited color for the background instead of the inverted color:

function NumberIcon({ badgeCount }) {
  return (
    <span className="rounded px-1 py-0.5 bg-current">
      <span className="text-color-text-inverted font-bold">{badgeCount}</span>
    </span>
  );
}

'text-color-text-inverted font-bold',
// Default background color when there is no hovering going on
'bg-grey-7',
// When the div.group element is hovered, but the .hover-group
Copy link
Member

Choose a reason for hiding this comment

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

The hover-group naming seems ambiguous to me, since both groups (toolbar and button) are being hovered here. The main distinction is either the relative level of the group (more generic) or the semantic meaning of the group (more specific). If the groups had semantic names (eg. toolbar-group or group-toolbar) then you could apply toolbar-group-hover:bg-grey-5, button-group-hover:bg-grey-9 and it would be a bit more obvious what is going on.

You may have already come across this, but I found some ideas in tailwindlabs/tailwindcss#1192.

@@ -110,6 +136,36 @@ function ToolbarButton({ badgeCount, icon, label, onClick, shortcut }) {
* The toolbar that is displayed above or below selected text in the document,
* providing options to create annotations or highlights.
*
* The toolbar has nuanced styling for hover. The component structure is:
Copy link
Member

Choose a reason for hiding this comment

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

While this comment is useful, the requirements here don't seem that crazy or weird to me and it is going to be a pain if we need to write this much detail each time. Also there is a risk that the comments get out of date if we revise the implementation to use a simpler method in future.

// Custom variant for a specific use case in which an element needs to
// style itself based on the hover states of TWO parents (.group and
// .hover-group). See `src/annotator/components/AdderToolbar`
addVariant('hover-group-hover', '.group:hover .hover-group:hover &');
Copy link
Member

Choose a reason for hiding this comment

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

A few searches online finds that plenty of folks have found use cases for nested groups, similar to what we have here. It doesn't seem that unusual to me. The basic solutions I've seen boil down to adding variants to create additional groups that are named by semantic function or level.

@lyzadanger
Copy link
Contributor Author

@robertknight currentColor: keys to the kingdom here! Didn't think of it. Should reduce all of this complexity to minimal. Thanks.

* Use updated components and icons
* Refactor to eliminate external styles
  - Add tailwind utility class for `all: initial`
@lyzadanger
Copy link
Contributor Author

OK, "reboot" here, much simpler, using currentColor, no need for any real cleverness. A single group will do the trick overall.

Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

Revised version LGTM

@lyzadanger lyzadanger merged commit aaee569 into main Aug 31, 2022
@lyzadanger lyzadanger deleted the update-adder-toolbar branch August 31, 2022 13:31
@lyzadanger lyzadanger restored the update-adder-toolbar branch August 31, 2022 13:32
@lyzadanger lyzadanger deleted the update-adder-toolbar branch January 20, 2023 16:01
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.

2 participants