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

[Button] Add loading prop #44637

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

siriwatknp
Copy link
Member

@siriwatknp siriwatknp commented Dec 3, 2024

Preview: https://deploy-preview-44637--material-ui.netlify.app/material-ui/react-button/#loading-2

Closes #42684
Closes #31235

Context

This PR build on top of the reverted #42987 by making the default value of loading to be null to fix the problem with Google Translation Crash without introducing overhead of the loading wrapper to the existing projects.

When the prop is boolean, a span always present as a wrapper regardless of the loading state:

<button>
  <span style={{ display: 'contents' }}></span> // still present even though loading is false
  text
</button>

The reasons to go with this approach instead of wrapping children with a span are:

  • No breaking change
  • No overhead for existing projects
  • Small overhead when loading because the loading wrapper is not build with styled.

Note

When this PR is merged, the next release should be v6.2.0


@siriwatknp siriwatknp added new feature New feature or request component: button This is the name of the generic UI component, not the React module! labels Dec 3, 2024
@siriwatknp
Copy link
Member Author

siriwatknp commented Dec 3, 2024

@oliviertassinari Based on your comment, are you okay with the new prop? Please see this comment instead. I will update this PR if you are okay with @mnajdova suggestion.

@mui-bot
Copy link

mui-bot commented Dec 3, 2024

Netlify deploy preview

IconButton: parsed: +4.52% , gzip: +3.56%
Alert: parsed: +3.99% , gzip: +3.03%
Autocomplete: parsed: +2.44% , gzip: +1.83%
@material-ui/core: parsed: +0.66% , gzip: +0.46%
LoadingButton: parsed: -0.71% 😍, gzip: -0.11% 😍
@material-ui/lab: parsed: +0.01% , gzip: +0.13%

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against d3cf66a

@mnajdova
Copy link
Member

mnajdova commented Dec 3, 2024

This PR build on top of the reverted #42987 by adding a new prop enableLoadingWrapper to specifically fix the problem with Google Translation Crash.

Wouldn't it be better to automatically add the span if the loading prop has a boolean value (true/false), and don't add it if it has a nullish value. We can explain in the demos the usage.

@siriwatknp
Copy link
Member Author

This PR build on top of the reverted #42987 by adding a new prop enableLoadingWrapper to specifically fix the problem with Google Translation Crash.

Wouldn't it be better to automatically add the span if the loading prop has a boolean value (true/false), and don't add it if it has a nullish value. We can explain in the demos the usage.

That's a nice suggestion. If I understand correctly, there is no need for the new prop, right. The default value of loading is null which will not render the extra span by default. When loading={boolean}, the span is always there.

@mnajdova
Copy link
Member

mnajdova commented Dec 5, 2024

That's a nice suggestion. If I understand correctly, there is no need for the new prop, right. The default value of loading is null which will not render the extra span by default. When loading={boolean}, the span is always there.

Yes, the downside is that a component cannot alternative between supporting loading or not (at least in terms of working with Google translate). I think that's nice compromise.

@DiegoAndai
Copy link
Member

The default value of loading is null which will not render the extra span by default. When loading={boolean}, the span is always there.

I like this approach 👍🏼 let's add a short explanation in the docs as well.

@siriwatknp siriwatknp requested a review from mnajdova December 6, 2024 03:22
@siriwatknp
Copy link
Member Author

@mnajdova @DiegoAndai Ready for review.

@ZeeshanTamboli
Copy link
Member

@siriwatknp I’ve linked two issues in the description that should be closed when this PR merges, the same as in #44637.

{...other}
classes={classes}
>
{startIcon}
{enableLoadingWrapper ? (
// use plain HTML span to minimize the runtime overhead
<span className={classes.loadingWrapper} style={{ display: 'contents' }}>
Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Copy link
Member

@DiegoAndai DiegoAndai left a comment

Choose a reason for hiding this comment

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

Hey Jun! Thanks for pickling this one back up again.

I think the loadingPosition needs some work, see these examples: https://codesandbox.io/p/sandbox/44637-feedback-1-wtf45k

Screenshot 2024-12-09 at 17 04 09

In these examples users would expect the loading indicator to position itself correctly.

packages/mui-material/src/Button/Button.js Show resolved Hide resolved
Comment on lines 186 to 194
loadingIndicatorCenter: string;
/** Styles applied to the loadingIndicator element if `loadingPosition="start"`. */
loadingIndicatorStart: string;
/** Styles applied to the loadingIndicator element if `loadingPosition="end"`. */
loadingIndicatorEnd: string;
/** Styles applied to the endIcon element if `loading={true}` and `loadingPosition="end"`. */
endIconLoadingEnd: string;
/** Styles applied to the startIcon element if `loading={true}` and `loadingPosition="start"`. */
startIconLoadingStart: string;
Copy link
Member

Choose a reason for hiding this comment

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

This goes against having atomic classes. I think a better approach would be having:

loadingPositionCenter, loadingPositionStart, loadingPositionEnd classes applied to the root depending on the loadingPosition prop.

Which can be combined with other classes, for example

  • .loadingPositionStart .loadingIndicator { ... } instead of loadingIndicatorStart
  • .loadingPositionStart .startIcon { ... } instead of startIconLoadingStart

Reducing these 5 classes into 3.

We should try to follow the rule of having one class per prop-value combination.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point. Removed.

@siriwatknp
Copy link
Member Author

think the loadingPosition needs some work, see these examples: https://codesandbox.io/p/sandbox/44637-feedback-1-wtf45k

The root cause is that there is no icon to provide the space for the loading. I fixed this by render a loadingIconPlaceholder slot when there is no icon on that position.

image

Comment on lines 187 to 192
/** Styles applied to the loadingIndicator element if `loadingPosition="center"`. */
loadingIndicatorCenter: string;
/** Styles applied to the loadingIndicator element if `loadingPosition="start"`. */
loadingIndicatorStart: string;
/** Styles applied to the loadingIndicator element if `loadingPosition="end"`. */
loadingIndicatorEnd: string;
Copy link
Member

Choose a reason for hiding this comment

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

These should be removed and replaced by

loadingPositionCenter, loadingPositionStart, loadingPositionEnd, applied to the root. This would allow to do

.loadingPositionCenter .loadingIndicator { ... } // instead of loadingIndicatorCenter

// but also
.loadingPositionCenter .loadingWrapper { ... }

// or
.loadingPositionCenter .icon { ... }

This follows the prop-value class combination.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, fixed.

Copy link
Member

Choose a reason for hiding this comment

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

@siriwatknp the loadingPositionCenter, loadingPositionStart, and loadingPositionEnd classes should be applied to the root. Otherwise, it won't be possible to compose it with the icon, loadingWrapper, or loadingIconPlaceholder classes.

{...other}
classes={classes}
>
{startIcon}
{enableLoadingWrapper ? (
// use plain HTML span to minimize the runtime overhead
<span className={classes.loadingWrapper} style={{ display: 'contents' }}>
Copy link
Member

Choose a reason for hiding this comment

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

Should we move style={{ display: 'contents' }} as well?

packages/mui-material/src/Button/Button.js Outdated Show resolved Hide resolved
packages/mui-material/src/Button/Button.js Show resolved Hide resolved
@mui mui deleted a comment from mnajdova Dec 16, 2024
@siriwatknp
Copy link
Member Author

Should we move style={{ display: 'contents' }} as well?

@DiegoAndai If you meant to move it to be styled components, I added a comment above it to keep that element as plain HTML for least overhead. Its purpose is to prevent google translate error, not for customization because it has display: contents.

import ShoppingCartIcon from '@mui/icons-material/ShoppingCartOutlined';

const CartBadge = styled(Badge)`
.${badgeClasses.badge} {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.${badgeClasses.badge} {
&.${badgeClasses.badge} {

@@ -113,12 +113,45 @@ Use `color` prop to apply theme color palette to component.

{{"demo": "IconButtonColors.js"}}

### Loading

Starting from [`v6.2.0`](https://github.com/mui/material-ui/releases/tag/v6.2.0), use `loading` prop to set icon buttons in a loading state and disable interactions.
Copy link
Member

Choose a reason for hiding this comment

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

Just a note to update the version here and in other places before merging.

Comment on lines +423 to +437
{
props: {
loadingPosition: 'start',
size: 'small',
},
style: {
left: 10,
},
},
{
props: ({ loadingPosition, size }) => loadingPosition === 'start' && size !== 'small',
style: {
left: 14,
},
},
Copy link
Member

Choose a reason for hiding this comment

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

Would applying the base styles first work?

    {
      props: { loadingPosition: 'start' },
      style: {
        left: 14,
      },
    },
    {
      props: {
        loadingPosition: 'start',
        size: 'small',
      },
      style: {
        left: 10,
      },
    },

I think this would make it more readable and maintainable. What do you think?

(The same applies to the other 'small' size styles applied elsewhere in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: button This is the name of the generic UI component, not the React module! new feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[material-ui][Button] Button doesn't have the loading props [LoadingButton] Displaying incorrect warning
5 participants