Part of ENG-843
Separates color in Button so it can be used somewhere where the color is dynamic (depends on a color of a chat).
Details
- Reviewers
tomek atul • abosh ashoat - Commits
- rCOMM305c6a1ccaea: [web] Separate color in Button
- Run flow
- Check if all buttons look correct
Diff Detail
- Repository
- rCOMM Comm
- Lint
No Lint Coverage - Unit
No Test Coverage
Event Timeline
Looks good, some minor questions/suggestions inline.
Do you think it would be clearer if we renamed primary to filled and secondary to outline to help distinguish the two variants?
web/components/button.react.js | ||
---|---|---|
21 ↗ | (On Diff #17433) | Maybe name this prop buttonColor so it's clear that it's a style object and not value for color property? |
32 ↗ | (On Diff #17433) | Should we set a default color the way we set a default variant? Should we make the color prop required instead of optional? |
I think it makes sense. We might want to have success and danger buttons side by side in the future, and having them both be filled instead of primary makes more sense. Also, we now use this component in more places and not just as "Cancel", or "Continue" buttons.
web/components/button.react.js | ||
---|---|---|
21 ↗ | (On Diff #17433) | Makes sense, I will change it |
32 ↗ | (On Diff #17433) | The preset color schemes (success, danger) really only apply to the primary variant, and different variants have different "defaults". Also in a future diff (that I haven't yet submitted), the default variant is changed to a plain variant, which has only minimal CSS properties, and the color may not apply to it. So I don't think it makes sense to make it required or set a default. |
web/components/button.react.js | ||
---|---|---|
32 ↗ | (On Diff #17433) | That makes sense, thanks for explaining! |
web/components/button.react.js | ||
---|---|---|
10–15 ↗ | (On Diff #17433) | Can we avoid mixing the types here? Maybe we can expose some objects with predefined colors instead? |
Rename variants and buttonColor prop.
web/components/button.react.js | ||
---|---|---|
10–15 ↗ | (On Diff #17433) | The problem is that success and danger colors have :hover and :disabled properties in css. I don't think it's possible to specify them in inline styles. We could try to mimic them with onMouseEnter/onMouseLeave/disabled prop but I don't know if that's any better. |
web/components/button.react.js | ||
---|---|---|
50 ↗ | (On Diff #17485) | One thing to note is that when buttonColor is undefined, this condition is met and style={undefined} - which is ok, but probably not obvious |
After talking with @ashoat it seems like the previous changes are not the best solution. In particular they don't support hover animation for all colors and only for the predefined success and danger.
The problem with implementing it all for all colors is that we can't specify :hover and :disabled in inline styles in javascript. There are a few solutions:
- Use onMouseEnter and similar. It's less debuggable than css :hover and to me it's more verbose and less readable. We also lose the animation.
- Use opacity or transform: brightness() or similar in :hover. There are two problems with this solution:
- Our outline (secondary) button brightens instead of darkening on hover
- The children are also transformed (so the text becomes darker). There are ways to handle this with :before or :after but they are kind of complicated
- Setting css variables in javascript (scoped to the button) and then using them inside .css files in :hover
The third solution is a bit unconventional but I think it't the cleanest and simplest and it's implemented in this revision.
Back to you with questions :)
The problem with implementing it all for all colors is that we can't specify :hover and :disabled in inline styles in javascript.
Are you sure about this? Can you perhaps link some StackOverflow or GitHub discussions that talk about this? I wonder what would happen if you dynamically (in JS) render a <style> tag
- Use onMouseEnter and similar. It's less debuggable than css :hover and to me it's more verbose and less readable. We also lose the animation.
I agree this is not great...
- Use opacity or transform: brightness() or similar in :hover. There are two problems with this solution:
- Our outline (secondary) button brightens instead of darkening on hover
- The children are also transformed (so the text becomes darker). There are ways to handle this with :before or :after but they are kind of complicated
Hmm... for the first, I strongly suspect there is a way to darken instead of brighten via a transform, no? For the second, I'm curious to know more about how complicated these are.
- Setting css variables in javascript (scoped to the button) and then using them inside .css files in :hover
The third solution is a bit unconventional but I think it't the cleanest and simplest and it's implemented in this revision.
This is cool!! I think the transform solution would be ideal, but this is a good fallback.
Are you sure about this? Can you perhaps link some StackOverflow or GitHub discussions that talk about this? I wonder what would happen if you dynamically (in JS) render a <style> tag
Here and here. I couldn't find anything that explicitly states that it's impossible but it seems like it. I tried just including ':hover': {color: 'red'} in the inline styles but it didn't work for me.
I think rendering <style> is one of the workarounds mentioned in the linked posts but it doesn't seem that great to me.
Hmm... for the first, I strongly suspect there is a way to darken instead of brighten via a transform, no? For the second, I'm curious to know more about how complicated these are.
Yeah, the first point was a minor complication. You can just use values higher than 1.
For the second, I couldn't figure out how to implement it before, but I tried it once again after your comment and I have something that works (although I haven't tested it for all cases yet). It would look something like this (button.css):
.btn { position: relative; border-radius: 4px; font-size: var(--m-font-16); padding: 12px 24px; color: var(--fg); cursor: pointer; } .btn > * { z-index: 2; } .btn::before { content: ""; border-radius: inherit; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: inherit; z-index: 1; } /* === These classes would be exclusive */ .btn-light:hover::before { filter: brightness(0.85); } .btn-dark:hover::before { filter: brightness(1.15); } /* === */
There is at least one problem with this. You can't use plain text in buttons (<Button> Cancel </Button>) because of the z-index manipulation. You have to first wrap the text in <div> or something else which might be unexpected for someone using this component. Also, I wonder if changing z-index could break some other thing, I can see we are changing it in a few other places in the code but I'm not familiar with that property very much.
For the second, I couldn't figure out how to implement it before, but I tried it once again after your comment and I have something that works (although I haven't tested it for all cases yet). It would look something like this (button.css):
.btn { position: relative; border-radius: 4px; font-size: var(--m-font-16); padding: 12px 24px; color: var(--fg); cursor: pointer; } .btn > * { z-index: 2; } .btn::before { content: ""; border-radius: inherit; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: inherit; z-index: 1; } /* === These classes would be exclusive */ .btn-light:hover::before { filter: brightness(0.85); } .btn-dark:hover::before { filter: brightness(1.15); } /* === */
Can you explain more why the z-index is necessary? What are you trying to achieve with it?
There is at least one problem with this. You can't use plain text in buttons (<Button> Cancel </Button>) because of the z-index manipulation. You have to first wrap the text in <div> or something else which might be unexpected for someone using this component. Also, I wonder if changing z-index could break some other thing, I can see we are changing it in a few other places in the code but I'm not familiar with that property very much.
I think it would be pretty easy to check in the Button component if the children prop is a string, and if so to wrap it with a div. However I do agree with you that z-index is probably best to avoid if possible... still confused why it's necessary
Without the z-index the ::before pseudo-element covers the Button children and I couldn't find a way to make it work without it.
What's the ::before for? I think I need to grok the problem and solution much better before I can be an effective reviewer here, unfortunately... if you can provide a detailed explanation that would be super helpful. Alternately, I can resign from this revision and you can pair with somebody else on the team, and later they could explain to me what's going on here.
Sorry, I should have explained more and not just dropped the code in the comment.
The problem is that transform also applies to the Button children (text and icons) and it looks bad so we don’t want that. We need to create another element that will be the background of the button. We can do that in css with ::before pseudo-element that works as if there was an additional child of the <button>. It needs to fill the entire parent so that’s why I added the position, left, width etc. But because of that, it displays above the other siblings and that’s why we need to change the z-index.
I hope that makes it clearer.
I have talked with @tomek about this and we have figured out a solution without the z-index. I will update this diff shortly.
Updated to the new method. There's a slight visual change because now instead of using predefined colors for :hover colors we are using the filter property. I have tried to select the brightness values so that the changes are minimal (before/after):
{F202020}
{F202021}
Additionally, the filled buttons previously had a transparent 1px border which was a bit problematic with this method, but from what I could see they don't have it on figma so I have removed it.
Update. Changed typing and re-added the transparent border. To keep all the properties dealing with border in sync I have added two css variables. Otherwise if someone wanted to change the border width or radius there would subtle visual bugs.
We're really close - just a couple of questions inline.
web/components/button.css | ||
---|---|---|
18 ↗ | (On Diff #17635) | What happens when we have an element with .outline class but without .btn - what would be the value of var(--border-width)? Would modifying this selector to check if both classes are there will make it safer? .outline.btn |
web/components/button.react.js | ||
52–60 ↗ | (On Diff #17635) | I don't think it is beneficial to memoize it. Usually memoization makes sense when it avoids expensive recomputation or / and when it provides referential equality. In this case the computation isn't expensive and we already have referential equality. |
64 ↗ | (On Diff #17635) | Is it safe to not provide keys when using Children.map? |
Update
web/components/button.css | ||
---|---|---|
18 ↗ | (On Diff #17635) | If the css variable isn't defined the property will be skipped (if there isn't --border-width set higher in the hierarchy). We could also specify a fallback var(--border-width, 1px). But having a selector with both classes is probably the best idea because we are sure that we use the variable from the current button. |
web/components/button.react.js | ||
52–60 ↗ | (On Diff #17635) | Makes sense. In previous revisions of this diff, there was more code here and I forgot to change it. |
64 ↗ | (On Diff #17635) | According to this yes. I have also checked the newer versions and I think that's still the case. |
web/components/button.css | ||
---|---|---|
31–32 ↗ | (On Diff #17654) | Can we flip this to avoid using calc()? Something like: bottom: var(--border-width); right: var(--border-width); ? |
web/components/button.css | ||
---|---|---|
31–32 ↗ | (On Diff #17654) | Unfortunately, this doesn't look correct. We still have to use calc(-1*) even with bottom and right: bottom: calc(-1 * var(--border-width)); right: calc(-1 * var(--border-width)); |