basicSteps.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import React, { cloneElement, Children, useMemo, isValidElement, ReactElement } 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. export type Direction = 'horizontal' | 'vertical';
  6. export type Status = 'wait' | 'process' | 'finish' | 'error' | 'warning';
  7. export type Size = 'default' | 'small';
  8. export interface BasicStepsProps {
  9. prefixCls?: string;
  10. className?: string;
  11. direction?: Direction;
  12. current?: number;
  13. initial?: number;
  14. status?: Status;
  15. style?: React.CSSProperties;
  16. size?: Size;
  17. hasLine?: boolean;
  18. children?: React.ReactNode;
  19. onChange?: (current: number) => void;
  20. "aria-label"?: string;
  21. }
  22. const Steps = (props: BasicStepsProps) => {
  23. const {
  24. size,
  25. current,
  26. status,
  27. children,
  28. prefixCls,
  29. initial,
  30. direction,
  31. className,
  32. style,
  33. hasLine,
  34. onChange,
  35. } = props;
  36. const inner = useMemo(() => {
  37. const filteredChildren = Children.toArray(children).filter(c => isValidElement(c)) as Array<ReactElement>;
  38. const content = Children.map(filteredChildren, (child: React.ReactElement, index) => {
  39. if (!child) {
  40. return null;
  41. }
  42. const stepNumber = initial + index;
  43. const childProps = {
  44. stepNumber: `${stepNumber + 1}`,
  45. size,
  46. ...child.props,
  47. };
  48. if (status === 'error' && index === current - 1) {
  49. childProps.className = `${prefixCls}-next-error`;
  50. }
  51. if (!child.props.status) {
  52. if (stepNumber === current) {
  53. childProps.status = status;
  54. } else if (stepNumber < current) {
  55. childProps.status = 'finish';
  56. } else {
  57. childProps.status = 'wait';
  58. }
  59. }
  60. childProps.active = stepNumber === current;
  61. childProps.done = stepNumber < current;
  62. childProps.onChange = () => {
  63. if (index !== current) {
  64. onChange(index + initial);
  65. }
  66. };
  67. return cloneElement(child, { ...childProps });
  68. });
  69. return content;
  70. }, [children, initial, prefixCls, direction, status, current, size, onChange]);
  71. const wrapperCls = cls(className, {
  72. [`${prefixCls}-basic`]: true,
  73. [`${prefixCls}-${direction}`]: true,
  74. [`${prefixCls}-${size}`]: size !== 'default',
  75. [`${prefixCls}-hasline`]: hasLine,
  76. });
  77. return (
  78. <div aria-label={props["aria-label"]} className={wrapperCls} style={style}>
  79. {inner}
  80. </div>
  81. );
  82. };
  83. Steps.propTypes = {
  84. prefixCls: PropTypes.string,
  85. className: PropTypes.string,
  86. style: PropTypes.object,
  87. current: PropTypes.number,
  88. initial: PropTypes.number,
  89. direction: PropTypes.oneOf(['horizontal', 'vertical']),
  90. status: PropTypes.oneOf(['wait', 'process', 'finish', 'error', 'warning']),
  91. hasLine: PropTypes.bool,
  92. };
  93. Steps.defaultProps = {
  94. prefixCls: css.PREFIX,
  95. current: 0,
  96. direction: 'horizontal',
  97. size: '',
  98. initial: 0,
  99. hasLine: true,
  100. status: 'process',
  101. };
  102. export default Steps;