fillSteps.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import React, { cloneElement, Children, useMemo, ReactElement, isValidElement } from 'react';
  2. import PropTypes from 'prop-types';
  3. import cls from 'classnames';
  4. import { stepsClasses as css } from '@douyinfe/semi-foundation/steps/constants';
  5. import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
  6. import { Row, Col } from '../grid';
  7. export type Status = 'wait' | 'process' | 'finish' | 'error' | 'warning';
  8. export type Direction = 'horizontal' | 'vertical';
  9. export interface FillStepsProps {
  10. prefixCls?: string;
  11. className?: string;
  12. current?: number;
  13. direction?: Direction;
  14. initial?: number;
  15. status?: Status;
  16. style?: React.CSSProperties;
  17. children?: React.ReactNode;
  18. onChange?: (current: number) => void;
  19. "aria-label"?: string
  20. }
  21. const Steps = (props: FillStepsProps) => {
  22. const { current, status, children, prefixCls, initial, direction, className, style, onChange, ...rest } = props;
  23. const inner = useMemo(() => {
  24. const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement>;
  25. const colStyle = direction === 'vertical' ? null : { width: `${100 / filteredChildren.length}%` };
  26. const content = Children.map(filteredChildren, (child: ReactElement, index) => {
  27. if (!child) {
  28. return null;
  29. }
  30. const stepNumber = initial + index;
  31. const childProps = {
  32. stepNumber: `${stepNumber + 1}`,
  33. direction,
  34. ...child.props,
  35. };
  36. if (status === 'error' && index === current - 1) {
  37. childProps.className = `${prefixCls}-next-error`;
  38. }
  39. if (!child.props.status) {
  40. if (stepNumber === current) {
  41. childProps.status = status;
  42. } else if (stepNumber < current) {
  43. childProps.status = 'finish';
  44. } else {
  45. childProps.status = 'wait';
  46. }
  47. }
  48. childProps.onChange = onChange ? () => {
  49. if (index !== current) {
  50. onChange(index + initial);
  51. }
  52. } : undefined;
  53. return <Col style={colStyle}>{cloneElement(child, { ...childProps })}</Col>;
  54. });
  55. return content;
  56. }, [children, initial, prefixCls, direction, status, current, onChange]);
  57. const wrapperCls = cls(className, {
  58. [prefixCls]: true,
  59. [`${prefixCls}-${direction}`]: true
  60. });
  61. return (
  62. <div
  63. className={wrapperCls}
  64. style={style}
  65. aria-label={props["aria-label"]}
  66. {...getDataAttr(rest)}
  67. >
  68. <Row type="flex" justify="start">
  69. {inner}
  70. </Row>
  71. </div>
  72. );
  73. };
  74. Steps.propTypes = {
  75. prefixCls: PropTypes.string,
  76. className: PropTypes.string,
  77. style: PropTypes.object,
  78. current: PropTypes.number,
  79. initial: PropTypes.number,
  80. direction: PropTypes.oneOf(['horizontal', 'vertical']),
  81. status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning'])
  82. };
  83. Steps.defaultProps = {
  84. prefixCls: css.PREFIX,
  85. current: 0,
  86. direction: 'horizontal',
  87. initial: 0,
  88. status: 'process',
  89. };
  90. export default Steps;