Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

Commit

Permalink
feat(ProgressIndicator): add optional interactivity (#1945)
Browse files Browse the repository at this point in the history
* fix(DatePicker): protect flatpickr in shallow render tests

* fix(DatePicker): add comment to describe the Null check

* fix(DatePicker): protect callbacks against this.cal NPE

* test(DatePicker): add testcase to cover shallow behavior

* feat(progressindicator): add interactivity if onChange prop is passed

* fix(progressindicator): few small style fixes to the focus outline

* fix(progress): support experimental mode

* fix(progress): typoed the key to check, fix proptypes errors

* fix(progress): current step should not be interactive

* fix(progress): added reference to the new styles and additional tests

* chore(package): update peer dep to latest carbon

* fix(progressindicator): remove unnecessary index check
  • Loading branch information
scottdickerson authored and asudoh committed Mar 17, 2019
1 parent a8e80d2 commit b5023ee
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
},
"peerDependencies": {
"carbon-components": "^9.84.3",
"carbon-icons": "^7.0.5",
"carbon-icons": "^7.0.7",
"react": "^16.4.0",
"react-dom": "^16.4.0"
},
Expand Down
46 changes: 45 additions & 1 deletion src/components/ProgressIndicator/ProgressIndicator-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, number } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { ProgressIndicator, ProgressStep } from '../ProgressIndicator';
import ProgressIndicatorSkeleton from '../ProgressIndicator/ProgressIndicator.Skeleton';
import Tooltip from '../Tooltip';
Expand Down Expand Up @@ -74,13 +75,56 @@ storiesOf('Progress Indicator', module)
{
info: {
text: `
For React usage, ProgressIndicator holds the currentIndex state to indicate which ProgerssStep is the current step. The ProgressIndicator component should always be used with ProgressStep components as its children. Changing currentIndex prop will automatically set the ProgressStep components props (complete, incomplete, current).
For React usage, ProgressIndicator holds the currentIndex state to indicate which ProgressStep is the current step. The ProgressIndicator component should always be used with ProgressStep components as its children. Changing currentIndex prop will automatically set the ProgressStep components props (complete, incomplete, current).
For general usage, Progress Indicators display steps in a process. It should indicate when steps have been complete, the active step,
and the steps to come.
`,
},
}
)
.add(
'interactive',
() => (
<ProgressIndicator
currentIndex={number('Current progress (currentIndex)', 1)}
onChange={action('onChange')}>
<ProgressStep
label="Click me"
description="Step 1: Register a onChange event"
/>
<ProgressStep
label="Really long label"
description="The progress indicator will listen for clicks on the steps"
/>
<ProgressStep
label="Tooltip and really long label"
description="The progress indicator will listen for clicks on the steps"
renderLabel={() => (
<Tooltip
direction="bottom"
showIcon={false}
triggerClassName="bx--progress-label"
triggerText="Tooltip and really long label"
tooltipId="tooltipId-1">
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Animi
consequuntur hic ratione aliquid cupiditate, nesciunt saepe iste
blanditiis cumque maxime tenetur veniam est illo deserunt sint
quae pariatur. Laboriosam, consequatur.
</p>
</Tooltip>
)}
/>
</ProgressIndicator>
),
{
info: {
text: `
If you register an onChange handler, the Progress Indicator will become interactive. Your parent component should update the currentIndex prop within the onChange handler.
`,
},
}
)
.add('skeleton', () => <ProgressIndicatorSkeleton />, {
info: {
text: `
Expand Down
20 changes: 20 additions & 0 deletions src/components/ProgressIndicator/ProgressIndicator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ describe('ProgressIndicator', () => {
expect(list.state().currentIndex).toEqual(2);
});

it('should trigger onChange if clicked', () => {
const mockOnChange = jest.fn();

mountedList.setProps({ onChange: mockOnChange });
mountedList
.find(ProgressStep)
.at(0)
.find('[role="button"]')
.simulate('click');
expect(mockOnChange).toHaveBeenCalledWith(0);
});

describe('ProgressStep', () => {
it('should render with correct base className', () => {
expect(
Expand Down Expand Up @@ -175,6 +187,14 @@ describe('ProgressIndicator', () => {
.prop('complete')
).toBe(false);
});

it('should render any clickable ProgressSteps with correct classname', () => {
mountedList.setProps({ onChange: jest.fn() });
expect(mountedList.find('.bx--progress-step-button')).toHaveLength(6); // one button for each div
expect(
mountedList.find('.bx--progress-step-button--unclickable')
).toHaveLength(1); // only the current step should be unclickable
});
});
});
});
Expand Down
67 changes: 55 additions & 12 deletions src/components/ProgressIndicator/ProgressIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { settings } from 'carbon-components';
import CheckmarkOutline16 from '@carbon/icons-react/lib/checkmark--outline/16';
import Warning16 from '@carbon/icons-react/lib/warning/16';
import { componentsX } from '../../internal/FeatureFlags';
import { keys, matches } from '../../tools/key';

const { prefix } = settings;
const defaultRenderLabel = props => <p {...props} />;
Expand All @@ -25,6 +26,7 @@ export const ProgressStep = ({ ...props }) => {
invalid,
secondaryLabel,
disabled,
onClick,
renderLabel: ProgressStepLabel,
} = props;

Expand All @@ -37,6 +39,12 @@ export const ProgressStep = ({ ...props }) => {
[className]: className,
});

const handleKeyDown = e => {
if (matches(e, [keys.ENTER, keys.SPACE])) {
onClick();
}
};

const currentSvg =
current &&
(componentsX ? (
Expand Down Expand Up @@ -88,21 +96,35 @@ export const ProgressStep = ({ ...props }) => {

return (
<li className={classes}>
{currentSvg || completeSvg || incompleteSvg}
<ProgressStepLabel className={`${prefix}--progress-label`}>
{label}
</ProgressStepLabel>
{componentsX &&
secondaryLabel !== null &&
secondaryLabel !== undefined ? (
<p className={`${prefix}--progress-optional`}>{secondaryLabel}</p>
) : null}
<span className={`${prefix}--progress-line`} />
<div
className={classnames(`${prefix}--progress-step-button`, {
[`${prefix}--progress-step-button--unclickable`]: !onClick || current,
})}
role="button"
tabIndex={!current && onClick ? 0 : -1}
onClick={!current ? onClick : undefined}
onKeyDown={handleKeyDown}>
{currentSvg || completeSvg || incompleteSvg}
<ProgressStepLabel className={`${prefix}--progress-label`}>
{label}
</ProgressStepLabel>
{componentsX &&
secondaryLabel !== null &&
secondaryLabel !== undefined ? (
<p className={`${prefix}--progress-optional`}>{secondaryLabel}</p>
) : null}
<span className={`${prefix}--progress-line`} />
</div>
</li>
);
};

ProgressStep.propTypes = {
/**
* Index of the current step within the ProgressIndicator
*/
index: PropTypes.number,

/**
* Provide the label for the <ProgressStep>
*/
Expand Down Expand Up @@ -158,6 +180,11 @@ ProgressStep.propTypes = {
* The ID of the tooltip content.
*/
tooltipId: PropTypes.string,

/**
* A callback called if the step is clicked or the enter key is pressed
*/
onClick: PropTypes.func,
};

ProgressStep.defaultProps = {
Expand All @@ -183,6 +210,11 @@ export class ProgressIndicator extends Component {
* Optionally specify the current step array index
*/
currentIndex: PropTypes.number,

/**
* Optional callback called if a ProgressStep is clicked on. Returns the index of the step.
*/
onChange: PropTypes.func,
};

static defaultProps = {
Expand All @@ -199,25 +231,36 @@ export class ProgressIndicator extends Component {
};
}

renderSteps = () =>
React.Children.map(this.props.children, (child, index) => {
renderSteps = () => {
const { onChange } = this.props;

return React.Children.map(this.props.children, (child, index) => {
// only setup click handlers if onChange event is passed
const onClick = onChange ? () => onChange(index) : undefined;
if (index === this.state.currentIndex) {
return React.cloneElement(child, {
current: true,
index,
onClick,
});
}
if (index < this.state.currentIndex) {
return React.cloneElement(child, {
complete: true,
index,
onClick,
});
}
if (index > this.state.currentIndex) {
return React.cloneElement(child, {
complete: false,
index,
onClick,
});
}
return null;
});
};

render() {
const { className, currentIndex, ...other } = this.props; // eslint-disable-line no-unused-vars
Expand Down

0 comments on commit b5023ee

Please sign in to comment.