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
fix(Button): render disabled button as span #1540
Conversation
bfe7c1e
to
9d6f5ba
Compare
@toptal-bot run package:alpha-release |
@toptal-bot run all |
@s0ber Or maybe we can adjust the |
@@ -186,7 +187,7 @@ export const Button = forwardRef<HTMLButtonElement, Props>(function Button( | |||
title={title} | |||
value={value} | |||
type={type} | |||
component={as!} | |||
component={disabled && as === 'button' ? 'span' : as!} |
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 would need aria-role='button'
to render a button with a span tag
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.
Will add it, thanks!
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.
@ertrzyiks It seems aria-role="button"
is not valid, React complains to it. There is role="button"
that is added by MUI, it seems that should be enough.
@hweeTan Additional markup coming from the tooltip is not good, IMHO. It'll again bring unexpected styling issues. It's a leaking abstraction — you'll now have to account to the fact that everything can be wrapped into additional span. Basically it'll affect every possible component. |
title: 'Disabled', | ||
description: 'The button shows that currently unable to be interacted with' | ||
}) | ||
.addExample('Button/story/DisabledWithEvents.example.tsx', { | ||
title: 'Disabled with mouse events', | ||
description: 'The button is disabled, but event handlers can be added' |
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.
This seems counter-intuive. If the button is disabled so should event handlers. If events are happening then the button is not disabled.
IMO we should just integrate a tooltip in the button.
<Button
disabled
disabledMessage="No bueno" // automatically renders a tooltip with the specified content
/>
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.
Sounds good to me, but the problem is that with disabled button there won't be a way to add the tooltip. If we add the tooltip then the button stops being disabled (and any other event handler can be added).
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.
@s0ber What if we set the tooltip as a child of the button?
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.
@borisyordanov I just checked if events are fired for child nodes and it seems they are, at least in Chrome. The only problem I see here is that we'll need to change styles a bit. I'll try playing with this and prepare a complete solution to see how good will it look like in the end.
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.
@borisyordanov I decided not to examine the option of adding the tooltip prop inside the Button
because Tooltip
is much more than just a single prop. And it doesn't bring many benefits really — just couples Button
and Tooltip
, also brings a lot of complicated logic inside the Button
, requires significant styling refactoring, pollutes the markup with additional span for every button and so on 🙂
copied from Slack for the historical reasons
Be aware it needs compatibility with v5 also. Check all variants here https://share.goabstract.com/f9b77f8f-544b-4ced-a6a3-76b5a32f2934?sha=2717bd241694d688a866dfab6dddc3f75edd671c |
@vedrani I'll help merging it into v5 branch! |
import { Button } from '@toptal/picasso' | ||
import { useNotifications } from '@toptal/picasso/utils' | ||
|
||
const Example = () => { |
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 don't understand what value this example brings us. I'd rather see examples that cover
- a disabled button that shows a tooltip on hover
- a button group where one or more buttons are disabled and show a tooltip
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.
Will add proper examples!
@@ -188,10 +188,14 @@ page | |||
.addExample('Button/story/Basic.example.jsx', 'Basic') | |||
.addExample('Button/story/Variants.example.jsx', 'Variants') | |||
.addExample('Button/story/States.example.jsx', 'States') | |||
.addExample('Button/story/Disabled.example.jsx', { | |||
.addExample('Button/story/Disabled.example.tsx', { |
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.
❤️
81d6c83
to
487e44c
Compare
@@ -84,7 +84,7 @@ export interface StaticProps { | |||
} | |||
|
|||
const getVariantType = (variant: VariantType) => { | |||
const [type] = variant!.split('-') | |||
const [type] = variant!.split('-') // eslint-disable-line @typescript-eslint/no-non-null-assertion |
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.
is this eslint disable necessary?
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.
For some reason precommit hook started complaining to me yesterday, maybe it started happening when Davinci dependency version was bumped recently, not sure TBH.
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.
this is pretty strange, can you please figure out why this error started to happen?
className={className} | ||
style={style} | ||
disabled={disabled} | ||
title={title} | ||
value={value} | ||
type={type} | ||
component={as!} | ||
component={disabled && as === 'button' ? 'span' : as!} // eslint-disable-line @typescript-eslint/no-non-null-assertion |
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 move these conditionals into variables?
</Tooltip> | ||
</Container> | ||
|
||
<Container top='small'> |
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.
Add some text above each of these examples so it's more obvious that the top example is two buttons and the bottom is a button group
}) | ||
: configClasses | ||
? React.cloneElement(childNode, { classes: configClasses }) | ||
: childNode |
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.
@s0ber It'll take me a week and 100 espresso's to understand this code. Can we try to make it more readable? Maybe add some comments explaining what's going on
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.
Yeap, it's tricky and I don't like it but I was not able to come up with something better. This code allows to inject classes
and childrenClasses
props into the chidren react nodes. Previously it was just classes
. childrenClasses
is a prop that you have to pass to Tooltip
and this way they will be forwarded to the component that is wrapped by the Tooltip
. We use this to pass classes from Button.Group
to Tooltip
to Button
.
It technically may be useful in other cases where there are components that break the direct relationship between parent and children. E.g. previously there was just Button.Group
-> Button
relationship and Button.Group
was passing some classes to Button
for the proper styling. No when there is Tooltip
between them things become more complicated.
I think we can proceed there and fix those things later because it is kind of internal technical issue, it only affects styling and it doesn't introduce anything in the public API.
2ecba2b
to
6e1496e
Compare
6e1496e
to
ce28ca2
Compare
🎉 Last commit is successfully deployed 🎉 Demo is available on: Your davinci-bot 🚀 |
isn't it a bad idea in terms of accessibility to render a button as span? |
I guess it is, but considering we have been living with bad accessibility for over a year I think we can let this slide. I haven't seen any numbers, but I'm guessing the number of disabled Picasso users is low. When we start using Picasso for public pages we'll definitely have to revisit this. |
I don't know the issue deep enough, but isn't there some better solution that using |
@denieler If you have specific ideas I'll be very happy if you'll provide them. Maybes are not enough. I've posted multiple times why using As for ARIA, we use |
I can provide solutions after closely checking possible options. I don't have any now, but I see we are fixing the consequences instead of the root of the problem |
@denieler those buttons are not disabled |
Looks like this is a suggested solution - mui/material-ui#8416 (comment) You can add an additional |
@denieler please read the PR comments, this option was discussed |
if you can give me a link - it would be great |
I don't see an issue here. We can merge this as a breaking change into |
@denieler I'm not sure how random |
the breaking change is not an issue, as I said. It's just 1-time work. But we can ground a new way of using Tooltip if needed, a new behaviour of the Tooltip markdown and styles, if needed as well. |
@denieler It's not just a breaking change. You'll have to test every possible component and a combination of components and all new components that will ever appear just to figure out that additional |
yes, and this is true for every breaking change in Picasso. We shouldn't be afraid of making them still. Of course, they are unlikely, but sometimes we need to choose between 2 evils |
SPT-880
Description
Right now we render disabled button as
<button disabled />
with no pointer events. And this stands in our way when we want to render the tooltip (which is a very common case in Staff Portal). And we have to wrap the button in additionalspan
just to render this tooltip. In many cases it breaks the styling (please take a look at this bug for example: https://toptal-core.atlassian.net/browse/SPT-880). There are also problems with button groups styling when one of the buttons is disabled.The solution is simple. Let's not block all pointer events for disabled buttons and just render them as spans in the first place like we do in legacy platform.
Downsides: some tests may break in various projects and in general it's not always good when DOM element changes. But in the longterm it'll allow to avoid many hacks related to disabled buttons.
How to test
Review
js/jsx
file intots/tsx
in your PRprops
in component with documentationexamples
for componentyarn test
yarn test:visual
. If not - check the documentation how to fix visual testsPR commands
List of available commands:
@toptal-bot run all
- Run whole pipeline@toptal-bot run danger
- Danger checks@toptal-bot run lint
- Run linter@toptal-bot run test
- Run jest@toptal-bot run build
- Check build@toptal-bot run test:visual
or@toptal-bot run visual
- Run visual tests@toptal-bot run deploy:documentation
- Deploy documentation