Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';

import StatefulButton from './index';
import StatefulButton from '.';
import Icon from '../Icon';

describe('StatefulButton', () => {
Expand All @@ -13,7 +13,7 @@ describe('StatefulButton', () => {
complete: 'Saved',
},
className: 'mr-2',
variant: 'primary',
variant: 'primary' as const,
};
const tree = renderer.create((
<div>
Expand All @@ -39,7 +39,7 @@ describe('StatefulButton', () => {
},
disabledStates: ['pending'],
className: 'mr-2',
variant: 'primary',
variant: 'primary' as const,
};
const tree = renderer.create((
<>
Expand All @@ -63,7 +63,7 @@ describe('StatefulButton', () => {
},
disabledStates: ['unedited'],
className: 'mr-2',
variant: 'primary',
variant: 'primary' as const,
};
const tree = renderer.create((
<>
Expand Down
104 changes: 47 additions & 57 deletions src/StatefulButton/index.jsx → src/StatefulButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Cancel, CheckCircleOutline, SpinnerSimple } from '../../icons';
import Button from '../Button';
import Icon from '../Icon';

export interface StatefulButtonProps extends React.ComponentPropsWithoutRef<typeof Button> {
/** Optionally specify additional CSS classes to give this button. */
className?: string;
/**
* Each state has:
* - A label (required)
* - An icon
* - an option to be disabled
*
* Control the state with the `state` prop. Example usage:
*
* ```jsx
* <StatefulButton
* state="pending"
* labels={{
* default: 'Download',
* pending: 'Downloading',
* complete: 'Downloaded',
* }}
* icons={{
* default: <Icon className="fa fa-download" />,
* pending: <Icon className="fa fa-spinner fa-spin" />,
* complete: <Icon className="fa fa-check" />,
* }}
* disabledStates=['pending']
* className='btn-primary mr-2'
* />
* ```
*/
state?: string;
/** Each state has a `label`. Required. */
labels: { [key: string]: React.ReactNode };
/** Each state has an `icon`. */
icons?: { [key: string]: React.ReactNode };
/** Each state has a `disabledState` */
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/** Each state has a `disabledState` */
/** States listed in `disabledStates` will not be clickable */

disabledStates?: string[];
}

function StatefulButton({
className,
state,
state = 'default',
labels,
icons,
disabledStates,
icons = {
default: undefined,
pending: <Icon src={SpinnerSimple} className={classNames('icon-spin')} />,
complete: <Icon src={CheckCircleOutline} />,
error: <Icon src={Cancel} />,
},
disabledStates = ['pending', 'complete'],
onClick,
...attributes
}) {
}: StatefulButtonProps) {
const isDisabled = disabledStates.indexOf(state) !== -1;
const icon = icons[state] !== undefined ? icons[state] : icons.default;
const label = labels[state] !== undefined ? labels[state] : labels.default;
Expand Down Expand Up @@ -51,56 +93,4 @@ function StatefulButton({
);
}

StatefulButton.propTypes = {
className: PropTypes.string,
/**
* Each state has:
* - A label (required)
* - An icon
* - an option to be disabled
*
* Control the state with the `state` prop. Example usage:
*
* ```jsx
* <StatefulButton
* state="pending"
* labels={{
* default: 'Download',
* pending: 'Downloading',
* complete: 'Downloaded',
* }}
* icons={{
* default: <Icon className="fa fa-download" />,
* pending: <Icon className="fa fa-spinner fa-spin" />,
* complete: <Icon className="fa fa-check" />,
* }}
* disabledStates=['pending']
* className='btn-primary mr-2'
* />
* ```
*/
state: PropTypes.string,
/** Required. Each state has a `label`. */
labels: PropTypes.objectOf(PropTypes.node).isRequired,
/** Required. Each state has an `icon`. */
icons: PropTypes.objectOf(PropTypes.node),
/** Required. Each state has a `disabledState` */
disabledStates: PropTypes.arrayOf(PropTypes.string),
/** Specifies the callback function when the button is clicked */
onClick: PropTypes.func,
};

StatefulButton.defaultProps = {
className: undefined,
state: 'default',
icons: {
default: undefined,
pending: <Icon src={SpinnerSimple} className={classNames('icon-spin')} />,
complete: <Icon src={CheckCircleOutline} />,
error: <Icon src={Cancel} />,
},
disabledStates: ['pending', 'complete'],
onClick: undefined,
};

export default StatefulButton;
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export { default as Overlay, OverlayTrigger } from './Overlay';
export { default as Portal } from './Modal/Portal';
export { default as Spinner } from './Spinner';
export { default as Stack } from './Stack';
export { default as StatefulButton } from './StatefulButton';
export { default as Toast, TOAST_CLOSE_LABEL_TEXT, TOAST_DELAY } from './Toast';
export { default as Tooltip } from './Tooltip';
export { default as useWindowSize, type WindowSizeData } from './hooks/useWindowSizeHook';
Expand Down Expand Up @@ -165,8 +166,6 @@ export {
export { default as Sheet } from './Sheet';
// @ts-ignore: has yet to be converted to TypeScript
export { default as Stepper } from './Stepper';
// @ts-ignore: has yet to be converted to TypeScript
export { default as StatefulButton } from './StatefulButton';
export {
default as Tabs,
Tab,
Expand Down