import * as React from 'react'; import CheckOutlined from '@ant-design/icons/CheckOutlined'; import CloseOutlined from '@ant-design/icons/CloseOutlined'; import RcSteps from '@rc-component/steps'; import type { StepsProps as RcStepsProps } from '@rc-component/steps/lib/Steps'; import cls from 'classnames'; import useMergeSemantic from '../_util/hooks/useMergeSemantic'; import { devUseWarning } from '../_util/warning'; import Wave from '../_util/wave'; import { TARGET_CLS } from '../_util/wave/interface'; import { useComponentConfig } from '../config-provider/context'; import useSize from '../config-provider/hooks/useSize'; import useBreakpoint from '../grid/hooks/useBreakpoint'; import Tooltip from '../tooltip'; import PanelArrow from './PanelArrow'; import ProgressIcon from './ProgressIcon'; import useStyle from './style'; export type IconRenderType = ( oriNode: React.ReactNode, info: { index: number; active: boolean; item: StepItem; }, ) => React.ReactNode; interface StepItem { className?: string; /** @deprecated Please use `content` instead */ description?: React.ReactNode; content?: React.ReactNode; icon?: React.ReactNode; onClick?: React.MouseEventHandler; status?: 'wait' | 'process' | 'finish' | 'error'; disabled?: boolean; title?: React.ReactNode; subTitle?: React.ReactNode; style?: React.CSSProperties; } export type ProgressDotRender = ( iconDot: React.ReactNode, info: { index: number; status: NonNullable; title: React.ReactNode; /** @deprecated Please use `content` instead. */ description: React.ReactNode; content: React.ReactNode; }, ) => React.ReactNode; export interface StepsProps { // Style prefixCls?: string; className?: string; style?: React.CSSProperties; rootClassName?: string; classNames?: RcStepsProps['classNames']; styles?: RcStepsProps['styles']; variant?: 'filled' | 'outlined'; size?: 'default' | 'small'; // Layout type?: 'default' | 'navigation' | 'inline' | 'panel' | 'dot'; /** @deprecated Please use `orientation` instead. */ direction?: 'horizontal' | 'vertical'; orientation?: 'horizontal' | 'vertical'; /** @deprecated Please use `titlePlacement` instead. */ labelPlacement?: 'horizontal' | 'vertical'; titlePlacement?: 'horizontal' | 'vertical'; /** @deprecated Please use `type` and `iconRender` instead. */ progressDot?: boolean | ProgressDotRender; responsive?: boolean; ellipsis?: boolean; /** * Set offset cell, only work when `type` is `inline`. */ offset?: number; // Data current?: number; initial?: number; items?: StepItem[]; percent?: number; status?: 'wait' | 'process' | 'finish' | 'error'; // Render iconRender?: IconRenderType; // Events onChange?: (current: number) => void; } const waveEffectClassNames: StepsProps['classNames'] = { itemIcon: TARGET_CLS, }; const Steps = (props: StepsProps) => { const { // Style size, className, rootClassName, style, variant = 'filled', type, classNames, styles, // Layout direction, orientation, responsive = true, progressDot, labelPlacement, titlePlacement, ellipsis, offset = 0, // Data items, percent, current = 0, onChange, // Render iconRender, // MISC ...restProps } = props; const { getPrefixCls, direction: rtlDirection, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles, } = useComponentConfig('steps'); const prefixCls = getPrefixCls('steps', props.prefixCls); const itemIconCls = `${prefixCls}-item-icon`; const [hashId, cssVarCls] = useStyle(prefixCls); // ============================= Size ============================= const mergedSize = useSize(size); // ============================= Item ============================= const mergedItems = React.useMemo(() => (items || []).filter(Boolean), [items]); // ============================ Styles ============================ const [mergedClassNames, mergedStyles] = useMergeSemantic( [waveEffectClassNames, contextClassNames, classNames], [contextStyles, styles], ); // ============================ Layout ============================ const { xs } = useBreakpoint(responsive); // Type const mergedType = React.useMemo(() => { if (type && type !== 'default') { return type; } if (progressDot) { return 'dot'; } return type; }, []); const isInline = mergedType === 'inline'; const isDot = mergedType === 'dot' || mergedType === 'inline'; // Progress Dot Render function const legacyProgressDotRender = React.useMemo(() => { return mergedType === 'dot' && typeof progressDot === 'function' ? progressDot : undefined; }, [progressDot]); const mergedOrientation = React.useMemo(() => { const nextOrientation = orientation || direction; if (mergedType === 'panel') { return 'horizontal'; } return (responsive && xs) || nextOrientation === 'vertical' ? 'vertical' : 'horizontal'; }, [xs, direction]); const mergedTitlePlacement = React.useMemo(() => { if (isDot || mergedOrientation === 'vertical') { return mergedOrientation === 'vertical' ? 'horizontal' : 'vertical'; } if (type === 'navigation') { return 'horizontal'; } return titlePlacement || labelPlacement || 'horizontal'; }, []); // ========================== Percentage ========================== const mergedPercent = isInline ? undefined : percent; // ============================= Icon ============================= const internalIconRender: RcStepsProps['iconRender'] = (info) => { const { item, index, active } = info; const { status, icon } = item; let iconNode: React.ReactNode = null; if (isDot) { iconNode = ; } else if (icon) { iconNode = icon; } else { switch (status) { case 'finish': iconNode = ; break; case 'error': iconNode = ; break; default: { let numNode = {info.index + 1}; if (status === 'process' && mergedPercent !== undefined) { numNode = ( {numNode} ); } iconNode = numNode; } } } // Custom Render Props if (iconRender) { iconNode = iconRender(iconNode, { index, active, item, }); } else if (typeof legacyProgressDotRender === 'function') { iconNode = legacyProgressDotRender(iconNode, { index, ...(item as Required), }); } return iconNode; }; // ============================ Custom ============================ const itemRender: RcStepsProps['itemRender'] = (itemNode, itemInfo) => { let content = itemNode; if (isInline && itemInfo.item.content) { content = ( {itemNode} ); } return ( {content} ); }; const itemWrapperRender: RcStepsProps['itemWrapperRender'] = mergedType === 'panel' ? (itemNode) => { return ( <> {itemNode} ); } : undefined; // ============================ Styles ============================ const mergedStyle: React.CSSProperties = { '--steps-items-offset': `${offset}`, ...contextStyle, ...style, }; const stepsClassName = cls( contextClassName, `${prefixCls}-${variant}`, { [`${prefixCls}-${mergedType}`]: mergedType !== 'dot' ? mergedType : false, [`${prefixCls}-rtl`]: rtlDirection === 'rtl', [`${prefixCls}-dot`]: isDot, [`${prefixCls}-ellipsis`]: ellipsis, [`${prefixCls}-with-progress`]: mergedPercent !== undefined, [`${prefixCls}-${mergedSize}`]: mergedSize, }, className, rootClassName, hashId, cssVarCls, ); // =========================== Warning ============================ if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Steps'); warning.deprecated(!labelPlacement, 'labelPlacement', 'titlePlacement'); warning.deprecated(!progressDot, 'progressDot', 'type="dot"'); warning.deprecated(!direction, 'direction', 'orientation'); warning.deprecated( mergedItems.every((item) => !item.description), 'items.description', 'items.content', ); } // ============================ Render ============================ return ( ); }; if (process.env.NODE_ENV !== 'production') { Steps.displayName = 'Steps'; } export default Steps;