Skip to content

Commit

Permalink
Updates Button component loading API (#1865)
Browse files Browse the repository at this point in the history
* with changeset

* update readme

* uncommit loading story

* user can pass props to spinner
  • Loading branch information
bruugey authored Jul 26, 2023
1 parent 82b0bc1 commit cfba537
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-lies-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/button': major
---

Changes API that supports `isLoading` in Button components. Consuming applications now must pass their own Spinner components through the `loadingIndicator` slot prop
33 changes: 17 additions & 16 deletions packages/button/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,22 @@ npm install @leafygreen-ui/button

## Properties

| Prop | Type | Description | Default |
| -------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `variant` | `'default'`, `'primary'`, `'primaryOutline'`, `'danger'`, `'dangerOutline'`, `'baseGreen'` | Sets the style variant of the button. | `'default'` |
| `darkMode` | `boolean` | Determines if the component renders in dark mode | `false` |
| `size` | `'xsmall'`, `'small'`, `'default'`, `'large'` | Sets the size variant of the button. | `'default'` |
| `children` | `node` | The content that will appear inside of the `<Button />` component. | `null` |
| `className` | `string` | Adds a className to the class attribute. | `''` |
| `disabled` | `boolean` | Disabled the button | `false` |
| `as` | `'a' \| 'button'` | Determines the root element. An `a` tags can be supplied to replace `button` from being the DOM element that wraps the component. | `button` |
| `href` | `string` | If a href is supplied it will change the `as` value, such that the component renders inside of an `a` tag instead of inside of a `button` tag. | |
| `leftGlyph` | `React.ReactElement` | Glyph that will appear to the left of text, if there is text provided via the children prop. If no children are supplied to the component, passing an Icon here will render the button as an icon-only button. | |
| `rightGlyph` | `React.ReactElement` | Glyph that will appear to the right of text, if there is text provided via the children prop. If no children are supplied to the component, passing an Icon here will render the button as an icon-only button. |
| `isLoading` | `boolean` | Indicates whether the Button is in a loading state | `false` |
| `loadingText` | `string` | String displayed in place of `children` while the button is in a loading state | |
| `baseFontSize` | `14`, `16` | Determines the base font-size of the component | `14` |
| ... | native attributes of component passed to as prop | Any other properties will be spread on the root element | |
| Prop | Type | Description | Default |
| ------------------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `variant` | `'default'`, `'primary'`, `'primaryOutline'`, `'danger'`, `'dangerOutline'`, `'baseGreen'` | Sets the style variant of the button. | `'default'` |
| `darkMode` | `boolean` | Determines if the component renders in dark mode | `false` |
| `size` | `'xsmall'`, `'small'`, `'default'`, `'large'` | Sets the size variant of the button. | `'default'` |
| `children` | `node` | The content that will appear inside of the `<Button />` component. | `null` |
| `className` | `string` | Adds a className to the class attribute. | `''` |
| `disabled` | `boolean` | Disabled the button | `false` |
| `as` | `'a' \| 'button'` | Determines the root element. An `a` tags can be supplied to replace `button` from being the DOM element that wraps the component. | `button` |
| `href` | `string` | If a href is supplied it will change the `as` value, such that the component renders inside of an `a` tag instead of inside of a `button` tag. | |
| `leftGlyph` | `React.ReactElement` | Glyph that will appear to the left of text, if there is text provided via the children prop. If no children are supplied to the component, passing an Icon here will render the button as an icon-only button. | |
| `rightGlyph` | `React.ReactElement` | Glyph that will appear to the right of text, if there is text provided via the children prop. If no children are supplied to the component, passing an Icon here will render the button as an icon-only button. |
| `isLoading` | `boolean` | Indicates whether the Button is in a loading state | `false` |
| `loadingText` | `string` | String displayed in place of `children` while the button is in a loading state | |
| `baseFontSize` | `14`, `16` | Determines the base font-size of the component | `14` |
| `loadingIndicator` | `React.ReactElement` | Element to be rendered as loading indicator | |
| ... | native attributes of component passed to as prop | Any other properties will be spread on the root element | |

_Note: In order to make this Component act as a submit button, the recommended approach is to pass `submit` as the `type` prop. Note it is also valid to pass `input` to the `as` prop, and the button's content's to the `value` prop -- in this case, do not supply children to the component._
16 changes: 8 additions & 8 deletions packages/button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@
"access": "public"
},
"dependencies": {
"@leafygreen-ui/box": "^3.1.5",
"@leafygreen-ui/emotion": "^4.0.5",
"@leafygreen-ui/lib": "^10.4.1",
"@leafygreen-ui/loading-indicator": "^2.0.1",
"@leafygreen-ui/palette": "^4.0.5",
"@leafygreen-ui/ripple": "^1.1.10",
"@leafygreen-ui/tokens": "^2.1.2",
"@leafygreen-ui/box": "^3.1.4",
"@leafygreen-ui/emotion": "^4.0.4",
"@leafygreen-ui/lib": "^10.4.0",
"@leafygreen-ui/palette": "^4.0.4",
"@leafygreen-ui/ripple": "^1.1.9",
"@leafygreen-ui/tokens": "^2.1.1",
"polished": "^4.2.2"
},
"devDependencies": {
"next": "^13.0.5",
"@leafygreen-ui/icon": "^11.20.1"
"@leafygreen-ui/icon": "^11.20.0",
"@leafygreen-ui/loading-indicator": "^2.0.0"
},
"peerDependencies": {
"@leafygreen-ui/leafygreen-provider": "^3.1.4"
Expand Down
7 changes: 4 additions & 3 deletions packages/button/src/Button.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type StoryMetaType,
type StoryType,
} from '@leafygreen-ui/lib';
import { Spinner } from '@leafygreen-ui/loading-indicator';

import { Size } from './types';
import Button, { ButtonProps, Variant } from '.';
Expand Down Expand Up @@ -44,6 +45,7 @@ const meta: StoryMetaType<typeof Button> = {
},
args: {
children: 'MongoDB',
loadingIndicator: <Spinner />,
leftGlyph: undefined,
rightGlyph: undefined,
},
Expand Down Expand Up @@ -159,11 +161,10 @@ Loading.parameters = {
variant: Variant.Default,
rightGlyph: undefined,
leftGlyph: undefined,
loadingIndicator: <Spinner />,
},
},
};
// Avoid flaky visual diff tests with Spinner
Loading.parameters = {
// Avoids flakey Chromatic tests
chromatic: {
disableSnapshots: true,
},
Expand Down
3 changes: 3 additions & 0 deletions packages/button/src/Button/Button.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { axe } from 'jest-axe';
import NextLink from 'next/link';

import { BoxProps } from '@leafygreen-ui/box';
import { Spinner } from '@leafygreen-ui/loading-indicator';

import { ButtonProps } from '../types';
import Button from '..';
Expand Down Expand Up @@ -57,6 +58,7 @@ describe('packages/button', () => {
test(`renders spinner when isLoading is true`, () => {
const { getByTestId } = renderButton({
isLoading: true,
loadingIndicator: <Spinner />,
});
expect(getByTestId('lg-button-spinner')).toBeVisible();
});
Expand All @@ -74,6 +76,7 @@ describe('packages/button', () => {
const loadingText = 'loading text';
const { getByText } = renderButton({
isLoading: true,
loadingIndicator: <Spinner />,
loadingText,
});
expect(getByText(loadingText)).toBeVisible();
Expand Down
7 changes: 6 additions & 1 deletion packages/button/src/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ export const Button = React.forwardRef(function Button(
darkMode: darkModeProp,
baseFontSize = BaseFontSize.Body1,
disabled = false,
isLoading = false,
onClick,
leftGlyph,
rightGlyph,
children,
className,
as,
type,
isLoading = false,
loadingIndicator,
loadingText,
...rest
}: BoxProps<'button', ButtonProps>,
Expand Down Expand Up @@ -71,6 +72,7 @@ export const Button = React.forwardRef(function Button(
variant,
size,
isLoading,
loadingIndicator,
loadingText,
} as const;

Expand All @@ -92,4 +94,7 @@ Button.propTypes = {
leftGlyph: PropTypes.element,
rightGlyph: PropTypes.element,
href: PropTypes.string,
isLoading: PropTypes.bool,
loadingText: PropTypes.string,
loadingIndicator: PropTypes.element,
};
27 changes: 18 additions & 9 deletions packages/button/src/ButtonContent/ButtonContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useEffect, useRef } from 'react';

import { cx } from '@leafygreen-ui/emotion';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import { Spinner } from '@leafygreen-ui/loading-indicator';
import { registerRipple } from '@leafygreen-ui/ripple';

import {
Expand Down Expand Up @@ -31,6 +30,7 @@ export const ButtonContent = (props: ButtonContentProps) => {
size,
isLoading,
loadingText,
loadingIndicator,
className,
} = props;

Expand All @@ -50,6 +50,21 @@ export const ButtonContent = (props: ButtonContentProps) => {
return unregisterRipple;
}, [rippleRef, variant, darkMode, disabled, theme]);

const spinner =
loadingIndicator &&
React.cloneElement(loadingIndicator, {
...loadingIndicator.props,
className: cx(
{
[centeredSpinnerStyles]: !loadingText,
},
loadingIndicator.props?.className,
),
sizeOverride: buttonSpinnerSize[size],
colorOverride: spinnerColor[theme],
['data-testid']: 'lg-button-spinner',
});

if (isLoading) {
return (
<>
Expand All @@ -58,14 +73,8 @@ export const ButtonContent = (props: ButtonContentProps) => {
[centeredSpinnerContainerStyles]: !loadingText,
})}
>
<Spinner
className={cx({
[centeredSpinnerStyles]: !loadingText,
})}
sizeOverride={buttonSpinnerSize[size]}
colorOverride={spinnerColor[theme]}
data-testid="lg-button-spinner"
/>
{spinner}

{loadingText}
</div>
{!loadingText && (
Expand Down
5 changes: 5 additions & 0 deletions packages/button/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ export interface ButtonProps {
*/
loadingText?: string;

/**
* Visual indicator display to convey that component is loading.
*/
loadingIndicator?: React.ReactElement;

/**
* The component or HTML Element that the button is rendered as.
*
Expand Down

0 comments on commit cfba537

Please sign in to comment.