diff --git a/docs/data/material/components/button-group/LoadingButtonGroup.js b/docs/data/material/components/button-group/LoadingButtonGroup.js
index 989f028daf7a56..fd146a90620d10 100644
--- a/docs/data/material/components/button-group/LoadingButtonGroup.js
+++ b/docs/data/material/components/button-group/LoadingButtonGroup.js
@@ -1,17 +1,16 @@
import * as React from 'react';
import ButtonGroup from '@mui/material/ButtonGroup';
import Button from '@mui/material/Button';
-import LoadingButton from '@mui/lab/LoadingButton';
import SaveIcon from '@mui/icons-material/Save';
export default function LoadingButtonGroup() {
return (
- Fetch data
- }>
+
+ }>
Save
-
+
);
}
diff --git a/docs/data/material/components/button-group/LoadingButtonGroup.tsx b/docs/data/material/components/button-group/LoadingButtonGroup.tsx
index 989f028daf7a56..fd146a90620d10 100644
--- a/docs/data/material/components/button-group/LoadingButtonGroup.tsx
+++ b/docs/data/material/components/button-group/LoadingButtonGroup.tsx
@@ -1,17 +1,16 @@
import * as React from 'react';
import ButtonGroup from '@mui/material/ButtonGroup';
import Button from '@mui/material/Button';
-import LoadingButton from '@mui/lab/LoadingButton';
import SaveIcon from '@mui/icons-material/Save';
export default function LoadingButtonGroup() {
return (
- Fetch data
- }>
+
+ }>
Save
-
+
);
}
diff --git a/docs/data/material/components/button-group/LoadingButtonGroup.tsx.preview b/docs/data/material/components/button-group/LoadingButtonGroup.tsx.preview
index 51360c91557385..a69903f1fca35c 100644
--- a/docs/data/material/components/button-group/LoadingButtonGroup.tsx.preview
+++ b/docs/data/material/components/button-group/LoadingButtonGroup.tsx.preview
@@ -1,7 +1,7 @@
- Fetch data
- }>
+
+ }>
Save
-
+
\ No newline at end of file
diff --git a/docs/data/material/components/button-group/button-group.md b/docs/data/material/components/button-group/button-group.md
index 6cc23f74925aec..5bceb0b6ab282b 100644
--- a/docs/data/material/components/button-group/button-group.md
+++ b/docs/data/material/components/button-group/button-group.md
@@ -1,7 +1,7 @@
---
productId: material-ui
title: React Button Group component
-components: Button, ButtonGroup, LoadingButton
+components: Button, ButtonGroup
githubLabel: 'component: ButtonGroup'
githubSource: packages/mui-material/src/ButtonGroup
---
@@ -49,10 +49,8 @@ You can remove the elevation with the `disableElevation` prop.
{{"demo": "DisableElevation.js"}}
-## Experimental APIs
+## Loading
-### Loading button
-
-You can use the [``](/material-ui/react-button/#loading-button) from [`@mui/lab`](/material-ui/about-the-lab/) in the button group.
+Use the `loading` prop from `Button` to set buttons in a loading state and disable interactions.
{{"demo": "LoadingButtonGroup.js"}}
diff --git a/docs/data/material/components/buttons/IconButtonWithBadge.js b/docs/data/material/components/buttons/IconButtonWithBadge.js
new file mode 100644
index 00000000000000..9a0e8f533420aa
--- /dev/null
+++ b/docs/data/material/components/buttons/IconButtonWithBadge.js
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import IconButton from '@mui/material/IconButton';
+import Badge, { badgeClasses } from '@mui/material/Badge';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCartOutlined';
+
+const CartBadge = styled(Badge)`
+ .${badgeClasses.badge} {
+ top: -12px;
+ right: -6px;
+ }
+`;
+
+export default function IconButtonWithBadge() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/material/components/buttons/IconButtonWithBadge.tsx b/docs/data/material/components/buttons/IconButtonWithBadge.tsx
new file mode 100644
index 00000000000000..9a0e8f533420aa
--- /dev/null
+++ b/docs/data/material/components/buttons/IconButtonWithBadge.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import IconButton from '@mui/material/IconButton';
+import Badge, { badgeClasses } from '@mui/material/Badge';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCartOutlined';
+
+const CartBadge = styled(Badge)`
+ .${badgeClasses.badge} {
+ top: -12px;
+ right: -6px;
+ }
+`;
+
+export default function IconButtonWithBadge() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/data/material/components/buttons/IconButtonWithBadge.tsx.preview b/docs/data/material/components/buttons/IconButtonWithBadge.tsx.preview
new file mode 100644
index 00000000000000..aa71bce96117c6
--- /dev/null
+++ b/docs/data/material/components/buttons/IconButtonWithBadge.tsx.preview
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/material/components/buttons/LoadingButtons.js b/docs/data/material/components/buttons/LoadingButtons.js
index f2d71b178ab98d..33bdada801bbb2 100644
--- a/docs/data/material/components/buttons/LoadingButtons.js
+++ b/docs/data/material/components/buttons/LoadingButtons.js
@@ -1,25 +1,61 @@
import * as React from 'react';
-import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
import SaveIcon from '@mui/icons-material/Save';
import Stack from '@mui/material/Stack';
export default function LoadingButtons() {
return (
-
-
- Submit
-
-
- Fetch data
-
-
+
+
+
+ }
+ variant="outlined"
+ >
+ Save
+
+
+ }
variant="outlined"
>
- Save
-
+ Full width
+
+ }
+ variant="outlined"
+ >
+ Full width
+
+
+
+
+ }
+ >
+ Save
+
+
);
}
diff --git a/docs/data/material/components/buttons/LoadingButtons.tsx b/docs/data/material/components/buttons/LoadingButtons.tsx
index f2d71b178ab98d..33bdada801bbb2 100644
--- a/docs/data/material/components/buttons/LoadingButtons.tsx
+++ b/docs/data/material/components/buttons/LoadingButtons.tsx
@@ -1,25 +1,61 @@
import * as React from 'react';
-import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
import SaveIcon from '@mui/icons-material/Save';
import Stack from '@mui/material/Stack';
export default function LoadingButtons() {
return (
-
-
- Submit
-
-
- Fetch data
-
-
+
+
+
+ }
+ variant="outlined"
+ >
+ Save
+
+
+ }
variant="outlined"
>
- Save
-
+ Full width
+
+ }
+ variant="outlined"
+ >
+ Full width
+
+
+
+
+ }
+ >
+ Save
+
+
);
}
diff --git a/docs/data/material/components/buttons/LoadingButtons.tsx.preview b/docs/data/material/components/buttons/LoadingButtons.tsx.preview
deleted file mode 100644
index 9578d91a245686..00000000000000
--- a/docs/data/material/components/buttons/LoadingButtons.tsx.preview
+++ /dev/null
@@ -1,14 +0,0 @@
-
- Submit
-
-
- Fetch data
-
-}
- variant="outlined"
->
- Save
-
\ No newline at end of file
diff --git a/docs/data/material/components/buttons/LoadingButtonsTransition.js b/docs/data/material/components/buttons/LoadingButtonsTransition.js
index 21b0f2bd331d26..2278b2684fe7b5 100644
--- a/docs/data/material/components/buttons/LoadingButtonsTransition.js
+++ b/docs/data/material/components/buttons/LoadingButtonsTransition.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
@@ -27,7 +27,7 @@ export default function LoadingButtonsTransition() {
label="Loading"
/>
button': { m: 1 } }}>
-
Disabled
-
-
+
-
+ }
@@ -54,8 +54,8 @@ export default function LoadingButtonsTransition() {
variant="contained"
>
Send
-
-
+
+
button': { m: 1 } }}>
-
+
-
+
-
+ }
loading={loading}
@@ -92,8 +87,8 @@ export default function LoadingButtonsTransition() {
variant="contained"
>
Send
-
-
+
+
);
diff --git a/docs/data/material/components/buttons/LoadingButtonsTransition.tsx b/docs/data/material/components/buttons/LoadingButtonsTransition.tsx
index 21b0f2bd331d26..2278b2684fe7b5 100644
--- a/docs/data/material/components/buttons/LoadingButtonsTransition.tsx
+++ b/docs/data/material/components/buttons/LoadingButtonsTransition.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
@@ -27,7 +27,7 @@ export default function LoadingButtonsTransition() {
label="Loading"
/>
button': { m: 1 } }}>
-
Disabled
-
-
+
-
+ }
@@ -54,8 +54,8 @@ export default function LoadingButtonsTransition() {
variant="contained"
>
Send
-
-
+
+
button': { m: 1 } }}>
-
+
-
+
-
+ }
loading={loading}
@@ -92,8 +87,8 @@ export default function LoadingButtonsTransition() {
variant="contained"
>
Send
-
-
+
+
);
diff --git a/docs/data/material/components/buttons/LoadingIconButton.js b/docs/data/material/components/buttons/LoadingIconButton.js
new file mode 100644
index 00000000000000..6778d7281d47d7
--- /dev/null
+++ b/docs/data/material/components/buttons/LoadingIconButton.js
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import Tooltip from '@mui/material/Tooltip';
+import IconButton from '@mui/material/IconButton';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+
+export default function LoadingIconButton() {
+ const [loading, setLoading] = React.useState(false);
+ React.useEffect(() => {
+ const timeout = setTimeout(() => {
+ setLoading(false);
+ }, 2000);
+ return () => clearTimeout(timeout);
+ });
+ return (
+
+ setLoading(true)} loading={loading}>
+
+
+
+ );
+}
diff --git a/docs/data/material/components/buttons/LoadingIconButton.tsx b/docs/data/material/components/buttons/LoadingIconButton.tsx
new file mode 100644
index 00000000000000..6778d7281d47d7
--- /dev/null
+++ b/docs/data/material/components/buttons/LoadingIconButton.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import Tooltip from '@mui/material/Tooltip';
+import IconButton from '@mui/material/IconButton';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+
+export default function LoadingIconButton() {
+ const [loading, setLoading] = React.useState(false);
+ React.useEffect(() => {
+ const timeout = setTimeout(() => {
+ setLoading(false);
+ }, 2000);
+ return () => clearTimeout(timeout);
+ });
+ return (
+
+ setLoading(true)} loading={loading}>
+
+
+
+ );
+}
diff --git a/docs/data/material/components/buttons/LoadingIconButton.tsx.preview b/docs/data/material/components/buttons/LoadingIconButton.tsx.preview
new file mode 100644
index 00000000000000..9c9a8b0cbf868a
--- /dev/null
+++ b/docs/data/material/components/buttons/LoadingIconButton.tsx.preview
@@ -0,0 +1,5 @@
+
+ setLoading(true)} loading={loading}>
+
+
+
\ No newline at end of file
diff --git a/docs/data/material/components/buttons/buttons.md b/docs/data/material/components/buttons/buttons.md
index 4da25aa7e55f5f..d1253527444996 100644
--- a/docs/data/material/components/buttons/buttons.md
+++ b/docs/data/material/components/buttons/buttons.md
@@ -1,7 +1,7 @@
---
productId: material-ui
title: React Button component
-components: Button, IconButton, ButtonBase, LoadingButton
+components: Button, IconButton, ButtonBase
materialDesign: https://m2.material.io/components/buttons
githubLabel: 'component: button'
waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/button/
@@ -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.
+
+{{"demo": "LoadingIconButton.js"}}
+
+### Badge
+
+You can use the [`Badge`](/material-ui/react-badge/) component to add a badge to an `IconButton`.
+
+{{"demo": "IconButtonWithBadge.js"}}
+
## File upload
To create a file upload button, turn the button into a label using `component="label"` and then create a visually-hidden input with type `file`.
{{"demo": "InputFileUpload.js"}}
+## Loading
+
+Starting from [`v6.2.0`](https://github.com/mui/material-ui/releases/tag/v6.2.0), use the `loading` prop to set buttons in a loading state and disable interactions.
+
+{{"demo": "LoadingButtons.js"}}
+
+Toggle the loading switch to see the transition between the different states.
+
+{{"demo": "LoadingButtonsTransition.js"}}
+
+:::warning
+When the `loading` prop is set to `boolean`, the loading wrapper is always present in the DOM to prevent a [Google Translation Crash](https://github.com/mui/material-ui/issues/27853).
+
+The `loading` value should always be `null` or `boolean`. The pattern below is not recommended as it can cause the Google Translation Crash:
+
+```jsx
+
);
};
+
+function ClassesTest() {
+ return (
+
+ Button
+
+ );
+}
diff --git a/packages/mui-material/src/Button/Button.test.js b/packages/mui-material/src/Button/Button.test.js
index 5d57fa23d002a0..80b9dc94b01b54 100644
--- a/packages/mui-material/src/Button/Button.test.js
+++ b/packages/mui-material/src/Button/Button.test.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, screen, simulateKeyboardDevice } from '@mui/internal-test-utils';
+import { createRenderer, screen, simulateKeyboardDevice, within } from '@mui/internal-test-utils';
import { ClassNames } from '@emotion/react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Button, { buttonClasses as classes } from '@mui/material/Button';
@@ -753,4 +753,62 @@ describe('', () => {
expect(getComputedStyle(button).color).to.equal(color);
});
});
+
+ describe('prop: loading', () => {
+ it('should not have a loading wrapper by default', () => {
+ const { container } = render(Test);
+
+ expect(container.querySelector(`.${classes.loadingWrapper}`)).to.equal(null);
+ });
+
+ it('should have a loading wrapper when loading is boolean', () => {
+ const { container, rerender } = render(Test);
+
+ expect(container.querySelector(`.${classes.loadingWrapper}`)).not.to.equal(null);
+
+ rerender(Test);
+
+ expect(container.querySelector(`.${classes.loadingWrapper}`)).not.to.equal(null);
+ });
+
+ it('disables the button', () => {
+ render();
+
+ const button = screen.getByRole('button');
+ expect(button).to.have.property('tabIndex', -1);
+ expect(button).to.have.property('disabled', true);
+ });
+
+ it('cannot be enabled while `loading`', () => {
+ render();
+
+ expect(screen.getByRole('button')).to.have.property('disabled', true);
+ });
+
+ it('renders a progressbar that is labelled by the button', () => {
+ render(Submit);
+
+ const button = screen.getByRole('button');
+ const progressbar = within(button).getByRole('progressbar');
+ expect(progressbar).toHaveAccessibleName('Submit');
+ });
+ });
+
+ describe('prop: loadingIndicator', () => {
+ it('is not rendered by default', () => {
+ render(Test);
+
+ expect(screen.getByRole('button')).to.have.text('Test');
+ });
+
+ it('is rendered before the children when `loading`', () => {
+ render(
+
+ Test
+ ,
+ );
+
+ expect(screen.getByRole('button')).to.have.text('loading…Test');
+ });
+ });
});
diff --git a/packages/mui-material/src/Button/buttonClasses.ts b/packages/mui-material/src/Button/buttonClasses.ts
index e6abe7643e4736..7a15757d65e071 100644
--- a/packages/mui-material/src/Button/buttonClasses.ts
+++ b/packages/mui-material/src/Button/buttonClasses.ts
@@ -176,6 +176,20 @@ export interface ButtonClasses {
colorInfo: string;
/** Styles applied to the root element if `color="warning"`. */
colorWarning: string;
+ /** Styles applied to the root element if `loading={true}`. */
+ loading: string;
+ /** Styles applied to the loadingWrapper element. */
+ loadingWrapper: string;
+ /** Styles applied to the loadingIconPlaceholder element. */
+ loadingIconPlaceholder: string;
+ /** Styles applied to the loadingIndicator element. */
+ loadingIndicator: string;
+ /** 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;
}
export type ButtonClassKey = keyof ButtonClasses;
@@ -239,6 +253,13 @@ const buttonClasses: ButtonClasses = generateUtilityClasses('MuiButton', [
'iconSizeSmall',
'iconSizeMedium',
'iconSizeLarge',
+ 'loading',
+ 'loadingWrapper',
+ 'loadingIconPlaceholder',
+ 'loadingIndicator',
+ 'loadingIndicatorCenter',
+ 'loadingIndicatorStart',
+ 'loadingIndicatorEnd',
]);
export default buttonClasses;
diff --git a/packages/mui-material/src/IconButton/IconButton.d.ts b/packages/mui-material/src/IconButton/IconButton.d.ts
index 108d72d09ac0c3..775df23cc68f8c 100644
--- a/packages/mui-material/src/IconButton/IconButton.d.ts
+++ b/packages/mui-material/src/IconButton/IconButton.d.ts
@@ -47,6 +47,18 @@ export interface IconButtonOwnProps {
* @default false
*/
edge?: 'start' | 'end' | false;
+ /**
+ * If `true`, the loading indicator is visible and the button is disabled.
+ * @default false
+ */
+ loading?: boolean;
+ /**
+ * Element placed before the children if the button is in loading state.
+ * The node should contain an element with `role="progressbar"` with an accessible name.
+ * By default, it renders a `CircularProgress` that is labeled by the button itself.
+ * @default
+ */
+ loadingIndicator?: React.ReactNode;
/**
* The size of the component.
* `small` is equivalent to the dense button styling.
diff --git a/packages/mui-material/src/IconButton/IconButton.js b/packages/mui-material/src/IconButton/IconButton.js
index 780a3ff72d3d19..ac2776275f0aa1 100644
--- a/packages/mui-material/src/IconButton/IconButton.js
+++ b/packages/mui-material/src/IconButton/IconButton.js
@@ -4,26 +4,30 @@ import PropTypes from 'prop-types';
import clsx from 'clsx';
import chainPropTypes from '@mui/utils/chainPropTypes';
import composeClasses from '@mui/utils/composeClasses';
+import { unstable_useId as useId } from '@mui/material/utils';
import { alpha } from '@mui/system/colorManipulator';
import { styled } from '../zero-styled';
import memoTheme from '../utils/memoTheme';
import createSimplePaletteValueFilter from '../utils/createSimplePaletteValueFilter';
import { useDefaultProps } from '../DefaultPropsProvider';
import ButtonBase from '../ButtonBase';
+import CircularProgress from '../CircularProgress';
import capitalize from '../utils/capitalize';
import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses';
const useUtilityClasses = (ownerState) => {
- const { classes, disabled, color, edge, size } = ownerState;
+ const { classes, disabled, color, edge, size, loading } = ownerState;
const slots = {
root: [
'root',
+ loading && 'loading',
disabled && 'disabled',
color !== 'default' && `color${capitalize(color)}`,
edge && `edge${capitalize(edge)}`,
`size${capitalize(size)}`,
],
+ loadingIndicator: ['loadingIndicator'],
};
return composeClasses(slots, getIconButtonUtilityClass, classes);
@@ -37,6 +41,7 @@ const IconButtonRoot = styled(ButtonBase, {
return [
styles.root,
+ ownerState.loading && styles.loading,
ownerState.color !== 'default' && styles[`color${capitalize(ownerState.color)}`],
ownerState.edge && styles[`edge${capitalize(ownerState.edge)}`],
styles[`size${capitalize(ownerState.size)}`],
@@ -140,9 +145,27 @@ const IconButtonRoot = styled(ButtonBase, {
backgroundColor: 'transparent',
color: (theme.vars || theme).palette.action.disabled,
},
+ [`&.${iconButtonClasses.loading}`]: {
+ color: 'transparent',
+ },
})),
);
+const IconButtonLoadingIndicator = styled('span', {
+ name: 'MuiIconButton',
+ slot: 'LoadingIndicator',
+ overridesResolver: (props, styles) => styles.loadingIndicator,
+})(({ theme }) => ({
+ display: 'none',
+ position: 'absolute',
+ visibility: 'visible',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ color: (theme.vars || theme).palette.action.disabled,
+ variants: [{ props: { loading: true }, style: { display: 'flex' } }],
+}));
+
/**
* Refer to the [Icons](/material-ui/icons/) section of the documentation
* regarding the available icon options.
@@ -157,15 +180,25 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) {
disabled = false,
disableFocusRipple = false,
size = 'medium',
+ id: idProp,
+ loading = false,
+ loadingIndicator: loadingIndicatorProp,
...other
} = props;
+ const id = useId(idProp);
+ const loadingIndicator = loadingIndicatorProp ?? (
+
+ );
+
const ownerState = {
...props,
edge,
color,
disabled,
disableFocusRipple,
+ loading,
+ loadingIndicator,
size,
};
@@ -173,14 +206,18 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) {
return (
+
+ {loading && loadingIndicator}
+
{children}
);
@@ -264,6 +301,22 @@ IconButton.propTypes /* remove-proptypes */ = {
* @default false
*/
edge: PropTypes.oneOf(['end', 'start', false]),
+ /**
+ * @ignore
+ */
+ id: PropTypes.string,
+ /**
+ * If `true`, the loading indicator is visible and the button is disabled.
+ * @default false
+ */
+ loading: PropTypes.bool,
+ /**
+ * Element placed before the children if the button is in loading state.
+ * The node should contain an element with `role="progressbar"` with an accessible name.
+ * By default, it renders a `CircularProgress` that is labeled by the button itself.
+ * @default
+ */
+ loadingIndicator: PropTypes.node,
/**
* The size of the component.
* `small` is equivalent to the dense button styling.
diff --git a/packages/mui-material/src/IconButton/IconButton.test.js b/packages/mui-material/src/IconButton/IconButton.test.js
index 827d2c91fb560d..e72515a6b0b309 100644
--- a/packages/mui-material/src/IconButton/IconButton.test.js
+++ b/packages/mui-material/src/IconButton/IconButton.test.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import PropTypes from 'prop-types';
-import { createRenderer, reactMajor } from '@mui/internal-test-utils';
+import { createRenderer, reactMajor, screen, within } from '@mui/internal-test-utils';
import capitalize from '@mui/utils/capitalize';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import IconButton, { iconButtonClasses as classes } from '@mui/material/IconButton';
@@ -161,4 +161,46 @@ describe('', () => {
await ripple.startTouch(getByRole('button'));
expect(container.querySelector('.touch-ripple')).to.equal(null);
});
+
+ describe('prop: loading', () => {
+ it('disables the button', () => {
+ render();
+
+ const button = screen.getByRole('button');
+ expect(button).to.have.property('tabIndex', -1);
+ expect(button).to.have.property('disabled', true);
+ });
+
+ it('cannot be enabled while `loading`', () => {
+ render();
+
+ expect(screen.getByRole('button')).to.have.property('disabled', true);
+ });
+
+ it('renders a progressbar that is labelled by the button', () => {
+ render(Submit);
+
+ const button = screen.getByRole('button');
+ const progressbar = within(button).getByRole('progressbar');
+ expect(progressbar).toHaveAccessibleName('Submit');
+ });
+ });
+
+ describe('prop: loadingIndicator', () => {
+ it('is not rendered by default', () => {
+ render(Test);
+
+ expect(screen.getByRole('button')).to.have.text('Test');
+ });
+
+ it('is rendered before the children when `loading`', () => {
+ render(
+
+ Test
+ ,
+ );
+
+ expect(screen.getByRole('button')).to.have.text('loading…Test');
+ });
+ });
});
diff --git a/packages/mui-material/src/IconButton/iconButtonClasses.ts b/packages/mui-material/src/IconButton/iconButtonClasses.ts
index 72eb0e109c497f..b65ac3b0d85264 100644
--- a/packages/mui-material/src/IconButton/iconButtonClasses.ts
+++ b/packages/mui-material/src/IconButton/iconButtonClasses.ts
@@ -30,6 +30,10 @@ export interface IconButtonClasses {
sizeMedium: string;
/** Styles applied to the root element if `size="large"`. */
sizeLarge: string;
+ /** Styles applied to the root element if `loading={true}`. */
+ loading: string;
+ /** Styles applied to the loadingIndicator element. */
+ loadingIndicator: string;
}
export type IconButtonClassKey = keyof IconButtonClasses;
@@ -53,6 +57,8 @@ const iconButtonClasses: IconButtonClasses = generateUtilityClasses('MuiIconButt
'sizeSmall',
'sizeMedium',
'sizeLarge',
+ 'loading',
+ 'loadingIndicator',
]);
export default iconButtonClasses;
diff --git a/test/regressions/fixtures/Button/FullWidthLoadingButtons.js b/test/regressions/fixtures/Button/FullWidthLoadingButtons.js
index e9f544630ef187..038f027e6b4b6a 100644
--- a/test/regressions/fixtures/Button/FullWidthLoadingButtons.js
+++ b/test/regressions/fixtures/Button/FullWidthLoadingButtons.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import LoadingButton from '@mui/lab/LoadingButton';
+import Button from '@mui/material/Button';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import SaveIcon from '@mui/icons-material/Save';
@@ -27,10 +27,10 @@ export default function FullWidthLoadingButtonsTransition() {
}
label="Loading"
/>
-
+
Fetch data
-
-
+ }
loading={loading}
@@ -39,8 +39,8 @@ export default function FullWidthLoadingButtonsTransition() {
fullWidth
>
Send
-
-
+
Save
-
+
);
}