basicSteps.tsx 3.4 KB

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