fk-react-ui-components
Version:
Step 1 : Create a file in [ Seeds / Plants / Trees ] <br> Step 2 : It should export an Object with component name and story Component [Refer other components] <br> Step 3 : Story Component should return a react component <br> Step 3 : Created file should
324 lines (286 loc) • 9.35 kB
JavaScript
import React from 'react';
import {
StepperContainer,
StepsWrapper,
ContentWrapper,
StepItem,
DoneIconContainer,
ActiveIconContainer,
InactiveIconContainer
} from './styles';
import { PropTypes } from 'prop-types';
/**
* @class{Step} Should be a child of Stepper component
*/
export class Step extends React.PureComponent {
render() {
return React.isValidElement(this.props.children) ?
this.props.children : null;
}
}
/**
* @class{Stepper} Parent class controlling the logic and rendering of steps
*/
export class Stepper extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
stepIndex: props.stepIndex || 0,
doneMapping: props.doneMapping ||
Array(React.Children.count(props.children)).fill(false)
};
}
/**
* Update local state when props change
* @param {object} nextProps
*/
componentWillReceiveProps(nextProps) {
if (this.props.stepIndex !== nextProps.stepIndex) {
this.setState({stepIndex: nextProps.stepIndex});
}
if (this.props.doneMapping !== nextProps.doneMapping) {
this.setState({doneMapping: nextProps.doneMapping});
}
}
/**
* Returns the icon to be rendered for any step
* @param {object} childProps
* @param {number} index
* @return {element}
*/
getIcon = (childProps, index) => {
if (childProps.disabled) {
return childProps.disabledIcon || childProps.icon
|| this.props.disabledIcon || this.props.icon
|| <InactiveIconContainer />;
} else if (index === this.state.stepIndex) {
return childProps.activeIcon || childProps.icon
|| this.props.activeIcon || this.props.icon
|| (
<ActiveIconContainer>
<i className='fa fa-chevron-right'/>
</ActiveIconContainer>
);
} else if (this.state.doneMapping[index]) {
return childProps.doneIcon || childProps.icon
|| this.props.doneIcon || this.props.icon
|| (
<DoneIconContainer>
<i className='fa fa-check'/>
</DoneIconContainer>
);
} else {
return childProps.inactiveIcon || childProps.icon
|| this.props.inactiveIcon || this.props.icon
|| <InactiveIconContainer/>;
}
}
/**
* Handles click on any step.
* Calls onClick prop if provided else goes to that step if current
* stepIndex is greater than index and step is not disabled
* @param {number} index
*/
handleClick = (index) => {
const childrenArray = React.Children.toArray(this.props.children);
if (!childrenArray[index].props.disabled &&
this.state.doneMapping[index]
) {
if (this.props.onChange && typeof this.props.stepIndex !== 'undefined') {
this.props.onChange(index, 'click');
} else {
this.setState({stepIndex: index});
}
}
}
/**
* Function to move to the next step
*/
next = () => {
let index = this.state.stepIndex;
const childrenArray = React.Children.toArray(this.props.children);
/**
* Mark the current step as done in case of un-controlled component
*/
if (!this.props.doneMapping) {
const doneMapping = [...this.state.doneMapping];
doneMapping[index] = true;
this.setState({doneMapping});
}
/**
* Call onComplete handler if all steps are complete
*/
if (index === childrenArray.length - 1) {
if (this.props.onComplete) {
this.props.onComplete();
}
return;
}
/**
* Calculate the next step index
*/
while (index < childrenArray.length &&
(index === this.state.stepIndex || childrenArray[index].props.disabled)
) {
index += 1;
}
/**
* Update the step index in the local state in case of un-controlled component.
* For controlled component simply call the onChange handler
*/
if (!childrenArray[index].props.disabled) {
if (this.props.onChange && this.props.stepIndex !== 'undefined') {
this.props.onChange(index, 'next');
} else {
this.setState({
stepIndex: index
});
}
}
}
/**
* Function to move to the previous step
*/
previous = () => {
let index = this.state.stepIndex;
const childrenArray = React.Children.toArray(this.props.children);
/**
* Calculate the previous index
*/
while (index > 0 && (index === this.state.stepIndex ||
childrenArray[index].props.disabled)
) {
index -= 1;
}
/**
* Update the step index in the local state in case of un-controlled component.
* For controlled component simply call the onChange handler
*/
if (!childrenArray[index].props.disabled) {
if (this.props.onChange && typeof this.props.stepIndex !== 'undefined') {
this.props.onChange(index, 'previous');
} else {
this.setState({
stepIndex: index
});
}
}
}
render() {
return (
<StepperContainer
orientation={this.props.orientation}
styles={this.props.styles.container || {}}
>
<StepsWrapper
orientation={this.props.orientation}
styles={this.props.styles.stepper || {}}
>
{ React.Children.map(this.props.children, (step, index) => {
return (
<StepItem
orientation={this.props.orientation}
styles={this.props.styles.step || {}}
onClick={() => this.handleClick(index)}
active={index === this.state.stepIndex}
done={this.state.doneMapping[index]}
>
{ this.getIcon(step.props, index) }
{ step.props.label}
</StepItem>
);
})}
</StepsWrapper>
<ContentWrapper>
{ this.props.header ? this.props.header : null }
{ React.Children.toArray(this.props.children)[this.state.stepIndex] }
{ this.props.footer ? this.props.footer : null }
</ContentWrapper>
</StepperContainer>
);
}
}
Stepper.propTypes = {
/**
* Orientation of the stepper. May be horizontal or vertical
*/
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
/**
* Styles of the stepper.
*/
styles: PropTypes.object,
/**
* Current step index ( to be passes for controlled component)
*/
stepIndex: PropTypes.number,
/**
* Header element
*/
header: PropTypes.element,
/**
* Footer element
*/
footer: PropTypes.element,
/**
* Mapping of steps vs there done state (true / false)
* e.g. [true, false, false, false] (First step is done, rest are not)
* To be passed for controlled component
*/
doneMapping: PropTypes.arrayOf(PropTypes.number),
/**
* Invoked when the step changes
*/
onChange: PropTypes.func,
/**
* Invoked when all the steps are completed
*/
onComplete: PropTypes.func,
/**
* Default icon JSX element
*/
icon: PropTypes.element,
/**
* Inactive icon. Ovverrides icon prop for inactive state
*/
inactiveIcon: PropTypes.element,
/**
* Active icon. Overrides icon prop for active state
*/
activeIcon: PropTypes.element,
/**
* Disabled icon. Overrides icon prop for disabled state
*/
disabledIcon: PropTypes.element
};
Step.propTypes = {
/**
* Label for each step
*/
label: PropTypes.string,
/**
* Set disabled to true to disable a step
*/
disabled: PropTypes.bool,
/**
* Icon for a specific step. If provided, it ovverides the icon provided
* in the parent Stepper Component
*/
icon: PropTypes.element,
/**
* Inactive icon. Ovverrides icon prop for inactive state
*/
inactiveIcon: PropTypes.element,
/**
* Active icon. Ovverrides icon prop for inactive state
*/
activeIcon: PropTypes.element,
/**
* Disabled icon. Ovverrides icon prop for inactive state
*/
disabledIcon: PropTypes.element
};
Stepper.defaultProps = {
orientation: 'vertical',
styles: {},
disabled: false
};