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

[LoadingButton] Introduce new component #21389

Merged
merged 38 commits into from Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8361424
wip
mnajdova Jun 10, 2020
1e1de47
wip
mnajdova Jun 10, 2020
ec083e3
added position
mnajdova Jun 10, 2020
d9bea4c
renamed to LoadingButton
mnajdova Jun 10, 2020
6071393
fixed children
mnajdova Jun 10, 2020
fa7701d
fix width change when changing state
mnajdova Jun 10, 2020
375cf71
renamed props and component
mnajdova Jun 12, 2020
276167c
Update packages/material-ui/src/BusyButton/BusyButton.js
mnajdova Jun 12, 2020
e6f5a86
Update docs/src/pages/components/buttons/BusyButtons.js
mnajdova Jun 12, 2020
bc84de4
Update docs/src/pages/components/buttons/BusyButtons.js
mnajdova Jun 12, 2020
f8d4eb4
Update packages/material-ui/src/BusyButton/BusyButton.d.ts
mnajdova Jun 12, 2020
2ea5ad0
addressing comments
mnajdova Jun 12, 2020
a0a2370
fixed position
mnajdova Jun 12, 2020
e877474
comments
mnajdova Jun 12, 2020
5cf4b74
proptypes
mnajdova Jun 12, 2020
e1f983a
proptypes
mnajdova Jun 12, 2020
09c5321
fixed
mnajdova Jun 12, 2020
bc6edf8
Merge branch 'master' into feat/loading-button
mnajdova Jun 12, 2020
b4ff58c
Update docs/src/pages/components/buttons/buttons.md
oliviertassinari Jun 12, 2020
4b58e57
comments
mnajdova Jun 13, 2020
0a866c2
Merge branch 'feat/loading-button' of https://github.com/mnajdova/mat…
mnajdova Jun 13, 2020
7885994
Update packages/material-ui/src/BusyButton/BusyButton.js
mnajdova Jun 13, 2020
5e161ea
Update packages/material-ui/src/BusyButton/BusyButton.js
mnajdova Jun 13, 2020
a531d8c
proptypes
mnajdova Jun 13, 2020
b526739
prettier + docs:api
mnajdova Jun 13, 2020
ae84a9a
yarn proptypes --disable-cache
oliviertassinari Jun 13, 2020
0a8847d
switch label
oliviertassinari Jun 13, 2020
de31d86
Update packages/material-ui/src/BusyButton/BusyButton.js
mnajdova Jun 13, 2020
2e9162d
Update packages/material-ui/src/BusyButton/BusyButton.js
oliviertassinari Jun 13, 2020
22dc72c
comments
mnajdova Jun 13, 2020
f855cd2
empty commit to run argos twice and compare diff
oliviertassinari Jun 13, 2020
916d6a0
moved BusyButton to lab
mnajdova Jun 14, 2020
fc1e8ba
Merge branch 'feat/loading-button' of https://github.com/mnajdova/mat…
mnajdova Jun 14, 2020
6b259c8
docs:api
mnajdova Jun 14, 2020
60d7f8b
fixed description
mnajdova Jun 14, 2020
fde687e
renamed BusyButton to LoadingButton
mnajdova Jun 15, 2020
364b265
docs:api
mnajdova Jun 15, 2020
98b6c6c
added classes keys
mnajdova Jun 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/pages/api-docs/loading-button.js
@@ -0,0 +1,15 @@
import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'api/loading-button';
const requireRaw = require.context('!raw-loader!./', false, /\/loading-button\.md$/);

export default function Page({ docs }) {
return <MarkdownDocs docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
68 changes: 68 additions & 0 deletions docs/src/pages/components/buttons/LoadingButtons.js
@@ -0,0 +1,68 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import LoadingButton from '@material-ui/core/LoadingButton';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import SaveIcon from '@material-ui/icons/Save';
import SendIcon from '@material-ui/icons/Send';

const useStyles = makeStyles((theme) => ({
root: {
'& button': {
margin: theme.spacing(1),
},
},
switch: {
display: 'block',
},
}));

export default function LoadingButtons() {
const classes = useStyles();
const [loading, setLoading] = React.useState(false);

return (
<div className={classes.root}>
<FormControlLabel
control={
<Switch
checked={loading}
onChange={() => setLoading(!loading)}
name="loading"
color="primary"
/>
}
className={classes.switch}
label="Loading"
/>
<LoadingButton variant="outlined" loading={loading} loadingIndicatorPosition="center">
Fetch data
</LoadingButton>
<LoadingButton
variant="outlined"
loading={loading}
loadingIndicator="Loading..."
loadingIndicatorPosition="center"
>
Submit
</LoadingButton>
<LoadingButton
variant="contained"
color="primary"
loading={loading}
loadingIndicatorPosition="end"
endIcon={<SendIcon />}
>
Send
</LoadingButton>
<LoadingButton
variant="contained"
color="secondary"
loading={loading}
startIcon={<SaveIcon />}
>
Save
</LoadingButton>
</div>
);
}
68 changes: 68 additions & 0 deletions docs/src/pages/components/buttons/LoadingButtons.tsx
@@ -0,0 +1,68 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import LoadingButton from '@material-ui/core/LoadingButton';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import SaveIcon from '@material-ui/icons/Save';
import SendIcon from '@material-ui/icons/Send';

const useStyles = makeStyles((theme) => ({
root: {
'& button': {
margin: theme.spacing(1),
},
},
switch: {
display: 'block',
},
}));

export default function LoadingButtons() {
const classes = useStyles();
const [loading, setLoading] = React.useState(false);

return (
<div className={classes.root}>
<FormControlLabel
control={
<Switch
checked={loading}
onChange={() => setLoading(!loading)}
name="loading"
color="primary"
/>
}
className={classes.switch}
label="Loading"
/>
<LoadingButton variant="outlined" loading={loading} loadingIndicatorPosition="center">
Fetch data
</LoadingButton>
<LoadingButton
variant="outlined"
loading={loading}
loadingIndicator="Loading..."
loadingIndicatorPosition="center"
>
Submit
</LoadingButton>
<LoadingButton
variant="contained"
color="primary"
loading={loading}
loadingIndicatorPosition="end"
endIcon={<SendIcon />}
>
Send
</LoadingButton>
<LoadingButton
variant="contained"
color="secondary"
loading={loading}
startIcon={<SaveIcon />}
>
Save
</LoadingButton>
</div>
);
}
8 changes: 7 additions & 1 deletion docs/src/pages/components/buttons/buttons.md
@@ -1,6 +1,6 @@
---
title: Button React component
components: Button, IconButton, ButtonBase
components: Button, IconButton, ButtonBase, LoadingButton
---

# Button
Expand Down Expand Up @@ -94,6 +94,12 @@ Here are some examples of customizing the component. You can learn more about th

🎨 If you are looking for inspiration, you can check [MUI Treasury's customization examples](https://mui-treasury.com/styles/button).

## Loading Buttons

The loading buttons can show loading state and disable interactions.

{{"demo": "pages/components/buttons/LoadingButtons.js"}}

## Complex Buttons

The Text Buttons, Contained Buttons, Floating Action Buttons and Icon Buttons are built on top of the same component: the `ButtonBase`.
Expand Down
18 changes: 17 additions & 1 deletion packages/material-ui/src/Button/Button.d.ts
@@ -1,6 +1,6 @@
import { PropTypes } from '..';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '../ButtonBase';
import { OverrideProps } from '../OverridableComponent';
import { OverrideProps, OverridableComponent, OverridableTypeMap } from '../OverridableComponent';

export type ButtonTypeMap<
P = {},
Expand Down Expand Up @@ -58,6 +58,22 @@ export type ButtonTypeMap<
classKey: ButtonClassKey;
}>;

/**
* utility to create component types that inherit props from ButtonBase.
* This component has an additional overload if the `href` prop is set which
* can make extension quite tricky
*/
export interface ExtendButtonTypeMap<M extends OverridableTypeMap> {
props: M['props'] & ButtonTypeMap['props'];
defaultComponent: M['defaultComponent'];
classKey: M['classKey'];
}

export type ExtendButton<M extends OverridableTypeMap> = ((
props: { href: string } & OverrideProps<ExtendButtonBaseTypeMap<M>, 'a'>
) => JSX.Element) &
OverridableComponent<ExtendButtonBaseTypeMap<M>>;

/**
*
* Demos:
Expand Down
47 changes: 47 additions & 0 deletions packages/material-ui/src/LoadingButton/LoadingButton.d.ts
@@ -0,0 +1,47 @@
import { ExtendButton, ExtendButtonTypeMap } from '../Button';
import { OverrideProps } from '../OverridableComponent';

export type LoadingButtonTypeMap<
P = {},
D extends React.ElementType = 'button'
> = ExtendButtonTypeMap<{
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
props: P & {
/**
* The content of the button.
*/
loading?: boolean;
/**
* Element placed before the children if the button is in loading state.
*/
loadingIndicator?: React.ReactNode;
/**
* The loading indicator can be positioned on the start, end or the center of the Button.
*/
loadingIndicatorPosition?: 'start' | 'end' | 'center';
};
defaultComponent: D;
classKey: LoadingButtonClassKey;
}>;

/**
*
* Demos:
*
* - [Button Group](https://material-ui.com/components/button-group/)
* - [Buttons](https://material-ui.com/components/buttons/)
*
* API:
*
* - [LoadingButton API](https://material-ui.com/api/loading-button/)
* - inherits [Button API](https://material-ui.com/api/button/)
*/
declare const LoadingButton: ExtendButton<LoadingButtonTypeMap>;

export type LoadingButtonProps<
D extends React.ElementType = LoadingButtonTypeMap['defaultComponent'],
P = {}
> = OverrideProps<LoadingButtonTypeMap<P, D>, D>;

export type LoadingButtonClassKey = 'root' | 'loading';

export default LoadingButton;
81 changes: 81 additions & 0 deletions packages/material-ui/src/LoadingButton/LoadingButton.js
@@ -0,0 +1,81 @@
import * as React from 'react';
import clsx from 'clsx';
import withStyles from '../styles/withStyles';
import capitalize from '../utils/capitalize';
import Button from '../Button';
import CircularProgress from '../CircularProgress';

export const styles = () => ({
/* Styles applied to the root element. */
root: {},
/* Styles applied to the root element if `loading={true}`. */
loading: {},
/* Styles applied to the loadingIndicator element. */
loadingIndicator: {
position: 'absolute',
visibility: 'visible',
},
/* Styles applied to the loadingIndicator element if `loadingIndicatorPosition="center"`. */
loadingIndicatorCenter: {
left: '50%',
transform: 'translate(-50%)',
},
/* Styles applied to the loadingIndicator element if `loadingIndicatorPosition="start"`. */
loadingIndicatorStart: {
left: 10,
},
/* Styles applied to the loadingIndicator element if `loadingIndicatorPosition="end"`. */
loadingIndicatorEnd: {
right: 10,
},
/* Styles applied to the endIcon element if `loading={true}` and `loadingIndicatorPosition="end"`. */
endIconLoadingEnd: {
visibility: 'hidden'
},
/* Styles applied to the startIcon element if `loading={true}` and `loadingIndicatorPosition="start"`. */
startIconLoadingStart: {
visibility: 'hidden'
},
/* Styles applied to the label element if `loading={true}` and `loadingIndicatorPosition="center"`. */
labelLoadingCenter: {
visibility: 'hidden',
},
});

const LoadingButton = React.forwardRef(function LoadingButton(props, ref) {
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
const {
classes,
className,
disabled = false,
loading = false,
loadingIndicatorPosition = 'start',
children,
loadingIndicator = <CircularProgress color="inherit" size={16} />,
...other
} = props;

return (
<Button
className={clsx(
classes.root,
{
[classes.loading]: loading,
},
className,
)}
disabled={disabled || loading}
ref={ref}
classes={{
startIcon: classes[`startIcon${loading ? 'Loading' : ''}${capitalize(loadingIndicatorPosition)}`],
endIcon: classes[`endIcon${loading ? 'Loading' : ''}${capitalize(loadingIndicatorPosition)}`],
label: classes[`label${loading ? 'Loading' : ''}${capitalize(loadingIndicatorPosition)}`]
}}
{...other}
>
{loading && <div className={clsx(classes.loadingIndicator, classes[`loadingIndicator${capitalize(loadingIndicatorPosition)}`])}>{loadingIndicator}</div>}
{children}
</Button>
);
});

export default withStyles(styles, { name: 'MuiLoadingButton' })(LoadingButton);
23 changes: 23 additions & 0 deletions packages/material-ui/src/LoadingButton/LoadingButton.test.js
@@ -0,0 +1,23 @@
import * as React from 'react';
import { getClasses } from '@material-ui/core/test-utils';
import createMount from 'test/utils/createMount';
import describeConformance from '../test-utils/describeConformance';
import LoadingButton from './LoadingButton';
import Button from '../Button';

describe('<LoadingButton />', () => {
const mount = createMount();
let classes;

before(() => {
classes = getClasses(<LoadingButton>Hello World</LoadingButton>);
});

describeConformance(<LoadingButton>Conformance?</LoadingButton>, () => ({
classes,
inheritComponent: Button,
mount,
refInstanceof: window.HTMLButtonElement,
skip: ['componentProp'],
}));
});
2 changes: 2 additions & 0 deletions packages/material-ui/src/LoadingButton/index.d.ts
@@ -0,0 +1,2 @@
export { default } from './LoadingButton';
export * from './LoadingButton';
1 change: 1 addition & 0 deletions packages/material-ui/src/LoadingButton/index.js
@@ -0,0 +1 @@
export { default } from './LoadingButton';
3 changes: 3 additions & 0 deletions packages/material-ui/src/index.d.ts
Expand Up @@ -91,6 +91,9 @@ export * from './ButtonBase';
export { default as ButtonGroup } from './ButtonGroup';
export * from './ButtonGroup';

export { default as LoadingButton } from './LoadingButton';
export * from './LoadingButton';

export { default as Card } from './Card';
export * from './Card';

Expand Down
3 changes: 3 additions & 0 deletions packages/material-ui/src/index.js
Expand Up @@ -39,6 +39,9 @@ export * from './ButtonBase';
export { default as ButtonGroup } from './ButtonGroup';
export * from './ButtonGroup';

export { default as LoadingButton } from './LoadingButton';
export * from './LoadingButton';

export { default as Card } from './Card';
export * from './Card';

Expand Down