diff --git a/assets/index.less b/assets/index.less
index 4f8ebd83..3a3284bc 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -17,37 +17,24 @@
display: inline-block;
vertical-align: top;
flex: 1;
- overflow: hidden;
-
- &-container[role='button'] {
- cursor: pointer;
- transition: opacity .3s;
-
- &:hover {
- opacity: 0.7;
- }
- }
+ // overflow: hidden;
&:last-child {
flex: none;
}
- &:last-child &-tail,
&:last-child &-title:after {
display: none;
}
- &-container {
- display: inline-block;
- }
-
&-icon,
- &-content {
+ &-section {
display: inline-block;
vertical-align: top;
}
&-icon {
+ flex: none;
border: 1px solid @wait-icon-color;
width: 26px;
height: 26px;
@@ -55,8 +42,9 @@
text-align: center;
border-radius: 26px;
font-size: 14px;
- margin-right: 8px;
- transition: background-color .3s, border-color .3s;
+ transition:
+ background-color 0.3s,
+ border-color 0.3s;
> .@{stepsPrefixClass}-icon {
line-height: 1;
@@ -72,49 +60,20 @@
}
}
- &-tail {
- position: absolute;
- left: 0;
- width: 100%;
- top: 12px;
- padding: 0 10px;
- &:after {
- content: '';
- display: inline-block;
- background: @wait-tail-color;
- height: 1px;
- border-radius: 1px;
- width: 100%;
- transition: background .3s;
- }
- }
- &-content {
+ &-section {
margin-top: 3px;
}
&-title {
font-size: 14px;
- margin-bottom: 4px;
color: #666;
font-weight: bold;
display: inline-block;
- padding-right: 10px;
position: relative;
- &:after {
- content: '';
- height: 1px;
- width: 1000px;
- background: @wait-tail-color;
- display: block;
- position: absolute;
- top: 0.55em;
- left: 100%;
- }
}
&-subtitle {
font-size: 12px;
display: inline-block;
color: #999;
- margin-left: 8px;
}
&-description {
font-size: 12px;
@@ -138,13 +97,6 @@
.@{stepsPrefixClass}-horizontal:not(.@{stepsPrefixClass}-label-vertical) {
.@{stepsPrefixClass}-item {
- margin-right: 10px;
- &:last-child {
- margin-right: 0;
- }
- &-tail {
- display: none;
- }
&-description {
max-width: @stepDescriptionMaxWidth;
}
@@ -152,10 +104,10 @@
}
.step-item-status(@status) {
- @icon-color: "@{status}-icon-color";
- @title-color: "@{status}-title-color";
- @description-color: "@{status}-description-color";
- @tail-color: "@{status}-tail-color";
+ @icon-color: '@{status}-icon-color';
+ @title-color: '@{status}-title-color';
+ @description-color: '@{status}-description-color';
+ @tail-color: '@{status}-tail-color';
&-@{status} &-icon {
border-color: @@icon-color;
background-color: #fff;
@@ -168,22 +120,102 @@
}
&-@{status} &-title {
color: @@title-color;
- &:after {
- background-color: @@tail-color;
- }
}
&-@{status} &-description {
color: @@description-color;
}
- &-@{status} &-tail:after {
- background-color: @@tail-color;
+}
+
+// @import 'custom-icon';
+// @import 'small';
+// @import 'vertical';
+// @import 'label-placement';
+// @import 'progress-dot';
+// @import 'nav';
+// @import 'inline';
+
+// ======================= Horizontal =======================
+.verticalFlex() {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.@{stepsPrefixClass} {
+ .@{stepsPrefixClass}-item {
+ &-section {
+ min-width: 0;
+ }
+
+ &-header {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ // Ellipsis
+ &-title,
+ &-subtitle,
+ &-description {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
}
-@import 'custom-icon';
-@import 'small';
-@import 'vertical';
-@import 'label-placement';
-@import 'progress-dot';
-@import 'nav';
-@import 'inline';
+.@{stepsPrefixClass}-horizontal {
+ .@{stepsPrefixClass}-item {
+ flex: 1;
+ position: relative;
+ min-width: 0;
+
+ &-rail {
+ height: 1px;
+ background: @process-tail-color;
+ }
+ }
+
+ // Label Vertical
+ &.@{stepsPrefixClass}-label-vertical {
+ .@{stepsPrefixClass}-item {
+ .verticalFlex();
+ padding-inline: 8px;
+
+ &-section {
+ .verticalFlex();
+ }
+
+ &-rail {
+ position: absolute;
+ top: 13px;
+ left: calc(50% + 13px);
+ width: 100%;
+ }
+ }
+ }
+
+ // Label Horizontal
+ &.@{stepsPrefixClass}-label-horizontal {
+ .@{stepsPrefixClass}-item {
+ display: flex;
+
+ &:last-child {
+ flex: none;
+ }
+
+ &-section {
+ flex: 1;
+ }
+
+ &-rail {
+ flex: 1;
+ min-width: 0;
+ }
+ }
+ }
+}
+
+// ======================== Vertical ========================
+.@{stepsPrefixClass}-vertical {
+}
diff --git a/assets/inline.less b/assets/inline.less
index 1bbe6a62..519e9907 100644
--- a/assets/inline.less
+++ b/assets/inline.less
@@ -7,18 +7,6 @@
.@{stepsPrefixClass}-item {
flex: none;
- &-container {
- padding: 9px 4px 0;
- margin: 0 2px;
- border-radius: 4px;
- cursor: pointer;
- &:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- &[role='button']:hover {
- opacity: 1;
- }
- }
&-icon {
width: 6px;
@@ -32,7 +20,7 @@
}
}
- &-content {
+ &-section {
width: auto;
margin-top: 7px;
}
@@ -47,26 +35,7 @@
display: none;
}
- &-tail {
- margin-left: 0;
- top: 11px;
- &:after {
- height: 1px;
- }
- }
- &:first-child .@{stepsPrefixClass}-item-tail{
- width: 50%;
- margin-left: 50%;
- }
- &:last-child .@{stepsPrefixClass}-item-tail{
- display: block;
- width: 50%;
- }
-
&-finish {
- .@{stepsPrefixClass}-item-tail:after {
- background-color: @process-tail-color;
- }
.@{stepsPrefixClass}-item-icon .@{stepsPrefixClass}-icon .@{stepsPrefixClass}-icon-dot {
background-color: @process-tail-color;
}
diff --git a/assets/label-placement.less b/assets/label-placement.less
index 76f07c92..6de6495b 100644
--- a/assets/label-placement.less
+++ b/assets/label-placement.less
@@ -3,11 +3,7 @@
.@{stepsPrefixClass}-label-vertical {
.@{stepsPrefixClass}-item {
overflow: visible;
- &-tail {
- padding: 0px 24px;
- margin-left: 48px;
- }
- &-content {
+ &-section {
display: block;
text-align: center;
margin-top: 8px;
diff --git a/assets/nav.less b/assets/nav.less
index d7fc9f4c..b5a257cb 100644
--- a/assets/nav.less
+++ b/assets/nav.less
@@ -13,11 +13,6 @@
text-align: center;
overflow: visible;
- &-container {
- text-align: left;
- padding-bottom: 8px;
- outline: none;
- }
&-title {
max-width: @stepNavContentMaxWidth;
@@ -53,11 +48,5 @@
margin-left: -8px;
}
- &-active {
- .@{stepsPrefixClass}-item-container {
- padding-bottom: 5px;
- border-bottom: 3px solid @primary-color;
- }
- }
}
}
diff --git a/assets/progress-dot.less b/assets/progress-dot.less
index f4a6a7b1..1c3dc208 100644
--- a/assets/progress-dot.less
+++ b/assets/progress-dot.less
@@ -2,16 +2,6 @@
.@{stepsPrefixClass}-dot {
.@{stepsPrefixClass}-item {
- &-tail {
- width: 100%;
- top: 1px;
- margin: 0 0 0 @stepDescriptionMaxWidth / 2;
- padding: 0;
-
- &:after {
- height: 3px;
- }
- }
&-icon {
padding-right: 0;
width: 5px;
diff --git a/assets/small.less b/assets/small.less
index d5b46617..95e281f3 100644
--- a/assets/small.less
+++ b/assets/small.less
@@ -16,7 +16,7 @@
top: -1px;
}
}
- .@{stepsPrefixClass}-item-content {
+ .@{stepsPrefixClass}-item-section {
margin-top: 0;
}
.@{stepsPrefixClass}-item-title {
@@ -29,15 +29,6 @@
font-size: 12px;
color: #999;
}
- .@{stepsPrefixClass}-item-tail {
- top: 8px;
- padding: 0 8px;
- &:after {
- height: 1px;
- border-radius: 1px;
- width: 100%;
- }
- }
.@{stepsPrefixClass}-item-custom .@{stepsPrefixClass}-item-icon {
width: inherit;
diff --git a/assets/vertical.less b/assets/vertical.less
index 6d669b8c..0c2e3ec1 100644
--- a/assets/vertical.less
+++ b/assets/vertical.less
@@ -11,7 +11,7 @@
margin-right: 16px;
}
}
- &-content {
+ &-section {
min-height: 48px;
overflow: hidden;
display: block;
@@ -25,27 +25,9 @@
&-description {
padding-bottom: 12px;
}
- &-tail {
- position: absolute;
- left: 13px;
- top: 0;
- height: 100%;
- width: 1px;
- padding: 30px 0 4px 0;
- &:after {
- height: 100%;
- width: 1px;
- }
- }
}
&.@{stepsPrefixClass}-small {
- .@{stepsPrefixClass}-item-tail {
- position: absolute;
- left: 9px;
- top: 0;
- padding: 22px 0 4px 0;
- }
.@{stepsPrefixClass}-item-title {
line-height: 18px;
}
diff --git a/package.json b/package.json
index 07e9f09b..d5fb883b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@rc-component/steps",
- "version": "0.0.0",
+ "version": "0.0.0-alpha.5",
"description": "steps ui component for react",
"keywords": [
"react",
@@ -80,8 +80,8 @@
"querystring": "^0.2.0",
"rc-dialog": "8.x",
"rc-test": "^7.0.9",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
diff --git a/src/Rail.tsx b/src/Rail.tsx
new file mode 100644
index 00000000..85503c33
--- /dev/null
+++ b/src/Rail.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import cls from 'classnames';
+import type { Status, StepsProps } from './Steps';
+
+export interface RailProps {
+ prefixCls: string;
+ classNames: StepsProps['classNames'];
+ styles: StepsProps['styles'];
+ status: Status;
+}
+
+export default function Rail(props: RailProps) {
+ const { prefixCls, classNames, styles, status } = props;
+ const railCls = `${prefixCls}-rail`;
+
+ // ============================= render =============================
+ return (
+
+ );
+}
diff --git a/src/Step.tsx b/src/Step.tsx
index 02cc9d92..966ce6df 100644
--- a/src/Step.tsx
+++ b/src/Step.tsx
@@ -1,66 +1,93 @@
/* eslint react/prop-types: 0 */
import * as React from 'react';
-import classNames from 'classnames';
+import cls from 'classnames';
import KeyCode from '@rc-component/util/lib/KeyCode';
-import type { Status, Icons } from './interface';
-import type { StepIconRender, ProgressDotRender } from './Steps';
-
-function isString(str: any): str is string {
- return typeof str === 'string';
-}
+import type { Status, StepItem, StepsProps } from './Steps';
+import Rail from './Rail';
export interface StepProps {
+ // style
prefixCls?: string;
- className?: string;
- style?: React.CSSProperties;
- wrapperStyle?: React.CSSProperties;
- iconPrefix?: string;
+ classNames: StepsProps['classNames'];
+ styles: StepsProps['styles'];
+
+ // data
+ data: StepItem;
+ nextStatus?: Status;
active?: boolean;
- disabled?: boolean;
- stepIndex?: number;
- stepNumber?: number;
- status?: Status;
- title?: React.ReactNode;
- subTitle?: React.ReactNode;
- description?: React.ReactNode;
- tailContent?: React.ReactNode;
+ index: number;
+ last: boolean;
+
+ // stepIndex?: number;
+ // stepNumber?: number;
+ // title?: React.ReactNode;
+ // subTitle?: React.ReactNode;
+ // description?: React.ReactNode;
+
+ // render
+ iconRender?: StepsProps['iconRender'];
icon?: React.ReactNode;
- icons?: Icons;
- onClick?: React.MouseEventHandler;
- onStepClick?: (index: number) => void;
- progressDot?: ProgressDotRender | boolean;
- stepIcon?: StepIconRender;
- render?: (stepItem: React.ReactElement) => React.ReactNode;
+ itemRender?: StepsProps['itemRender'];
+ itemWrapperRender?: StepsProps['itemWrapperRender'];
+
+ // Event
+ onClick: (index: number) => void;
}
-const Step: React.FC = (props) => {
+export default function Step(props: StepProps) {
const {
- className,
+ // style
prefixCls,
- style,
+ classNames,
+ styles,
+
+ // data
+ data,
+ last,
+ nextStatus,
active,
- status,
- iconPrefix,
- icon,
- wrapperStyle,
- stepNumber,
- disabled,
- description,
- title,
- subTitle,
- progressDot,
- stepIcon,
- tailContent,
- icons,
- stepIndex,
- onStepClick,
+ index,
+
+ // render
+ itemRender,
+ iconRender,
+ itemWrapperRender,
+
+ // events
onClick,
- render,
- ...restProps
} = props;
+ const itemCls = `${prefixCls}-item`;
+
+ // ========================== Data ==========================
+ const {
+ onClick: onItemClick,
+ title,
+ subTitle,
+ content,
+ description,
+ disabled,
+ icon,
+ status,
+
+ className,
+ style,
+ ...restItemProps
+ } = data;
+
+ const mergedContent = content ?? description;
+
+ const renderInfo = {
+ item: {
+ ...data,
+ content: mergedContent,
+ },
+ index,
+ active,
+ };
+
// ========================= Click ==========================
- const clickable = !!onStepClick && !disabled;
+ const clickable = !!(onClick || onItemClick) && !disabled;
const accessibilityProps: {
role?: string;
@@ -68,120 +95,94 @@ const Step: React.FC = (props) => {
onClick?: React.MouseEventHandler;
onKeyDown?: React.KeyboardEventHandler;
} = {};
+
if (clickable) {
accessibilityProps.role = 'button';
accessibilityProps.tabIndex = 0;
accessibilityProps.onClick = (e) => {
- onClick?.(e);
- onStepClick(stepIndex);
+ onItemClick?.(e);
+ onClick(index);
};
+
accessibilityProps.onKeyDown = (e) => {
const { which } = e;
if (which === KeyCode.ENTER || which === KeyCode.SPACE) {
- onStepClick(stepIndex);
+ onClick(index);
}
};
}
// ========================= Render =========================
- const renderIconNode = () => {
- let iconNode: React.ReactNode;
- const iconClassName = classNames(`${prefixCls}-icon`, `${iconPrefix}icon`, {
- [`${iconPrefix}icon-${icon}`]: icon && isString(icon),
- [`${iconPrefix}icon-check`]:
- !icon && status === 'finish' && ((icons && !icons.finish) || !icons),
- [`${iconPrefix}icon-cross`]:
- !icon && status === 'error' && ((icons && !icons.error) || !icons),
- });
- const iconDot = ;
- // `progressDot` enjoy the highest priority
- if (progressDot) {
- if (typeof progressDot === 'function') {
- iconNode = (
-
- {progressDot(iconDot, {
- index: stepNumber - 1,
- status,
- title,
- description,
- })}
-
- );
- } else {
- iconNode = {iconDot};
- }
- } else if (icon && !isString(icon)) {
- iconNode = {icon};
- } else if (icons && icons.finish && status === 'finish') {
- iconNode = {icons.finish};
- } else if (icons && icons.error && status === 'error') {
- iconNode = {icons.error};
- } else if (icon || status === 'finish' || status === 'error') {
- iconNode = ;
- } else {
- iconNode = {stepNumber};
- }
-
- if (stepIcon) {
- iconNode = stepIcon({
- index: stepNumber - 1,
- status,
- title,
- description,
- node: iconNode,
- });
- }
-
- return iconNode;
- };
-
const mergedStatus = status || 'wait';
- const classString = classNames(
- `${prefixCls}-item`,
- `${prefixCls}-item-${mergedStatus}`,
- className,
+ const classString = cls(
+ itemCls,
+ `${itemCls}-${mergedStatus}`,
{
- [`${prefixCls}-item-custom`]: icon,
- [`${prefixCls}-item-active`]: active,
- [`${prefixCls}-item-disabled`]: disabled === true,
+ [`${itemCls}-custom`]: icon,
+ [`${itemCls}-active`]: active,
+ [`${itemCls}-disabled`]: disabled === true,
},
+ className,
+ classNames.item,
);
- const stepItemStyle: React.CSSProperties = { ...style };
-
- let stepNode: React.ReactNode = (
-
-
-
{tailContent}
-
{renderIconNode()}
-
-
+ const wrapperNode = (
+
+
+ {iconRender?.(renderInfo)}
+
+
+
+
{title}
- {subTitle && (
-
- {subTitle}
-
- )}
- {description &&
{description}
}
+ {subTitle && (
+
+ {subTitle}
+
+ )}
+
+ {!last && (
+
+ )}
+ {mergedContent && (
+
+ {mergedContent}
+
+ )}
);
- if (render) {
- stepNode = (render(stepNode) || null) as React.ReactElement;
+ let stepNode: React.ReactNode = (
+
+ {itemWrapperRender ? itemWrapperRender(wrapperNode) : wrapperNode}
+
+ );
+
+ if (itemRender) {
+ stepNode = (itemRender(stepNode, renderInfo) || null) as React.ReactElement;
}
return stepNode;
-};
-
-if (process.env.NODE_ENV !== 'production') {
- Step.displayName = 'rc-step';
}
-
-export default Step;
diff --git a/src/Steps.tsx b/src/Steps.tsx
index 41f6bc66..756f55ef 100644
--- a/src/Steps.tsx
+++ b/src/Steps.tsx
@@ -1,153 +1,192 @@
/* eslint react/no-did-mount-set-state: 0, react/prop-types: 0 */
-import classNames from 'classnames';
+import cls from 'classnames';
import React from 'react';
-import type { Icons, Status } from './interface';
-import type { StepProps } from './Step';
import Step from './Step';
+export type Status = 'error' | 'process' | 'finish' | 'wait';
+
+export type SemanticName =
+ | 'root'
+ | 'item'
+ | 'itemWrapper'
+ | 'itemHeader'
+ | 'itemTitle'
+ | 'itemSubtitle'
+ | 'itemSection'
+ | 'itemContent'
+ | 'itemIcon'
+ | 'itemRail';
+
+export type StepItem = {
+ /** @deprecated Please use `content` instead. */
+ description?: React.ReactNode;
+ content?: React.ReactNode;
+ disabled?: boolean;
+ icon?: React.ReactNode;
+ status?: Status;
+ subTitle?: React.ReactNode;
+ title?: React.ReactNode;
+} & Pick
, 'onClick' | 'className' | 'style'>;
+
export type StepIconRender = (info: {
index: number;
status: Status;
title: React.ReactNode;
+ // @deprecated Please use `content` instead.
description: React.ReactNode;
+ content: React.ReactNode;
node: React.ReactNode;
}) => React.ReactNode;
-export type ProgressDotRender = (
- iconDot: React.ReactNode,
- info: {
- index: number;
- status: Status;
- title: React.ReactNode;
- description: React.ReactNode;
- },
-) => React.ReactNode;
+export type RenderInfo = {
+ index: number;
+ active: boolean;
+ item: StepItem;
+};
export interface StepsProps {
+ // style
prefixCls?: string;
style?: React.CSSProperties;
className?: string;
- children?: React.ReactNode;
- direction?: 'horizontal' | 'vertical';
- type?: 'default' | 'navigation' | 'inline';
+ classNames?: Partial>;
+ styles?: Partial>;
+ rootClassName?: string;
+
+ // layout
+ orientation?: 'horizontal' | 'vertical';
labelPlacement?: 'horizontal' | 'vertical';
- iconPrefix?: string;
+
+ // data
status?: Status;
- size?: 'default' | 'small';
current?: number;
- progressDot?: ProgressDotRender | boolean;
- stepIcon?: StepIconRender;
initial?: number;
- icons?: Icons;
- items?: StepProps[];
- itemRender?: (item: StepProps, stepItem: React.ReactElement) => React.ReactNode;
+ items?: StepItem[];
onChange?: (current: number) => void;
+
+ // render
+ iconRender?: (info: RenderInfo) => React.ReactNode;
+ itemRender?: (originNode: React.ReactElement, info: RenderInfo) => React.ReactNode;
+ itemWrapperRender?: (originNode: React.ReactElement) => React.ReactNode;
}
-const Steps: React.FC & {
- Step: typeof Step;
-} = (props) => {
+export default function Steps(props: StepsProps) {
const {
+ // style
prefixCls = 'rc-steps',
- style = {},
+ style,
className,
- children,
- direction = 'horizontal',
- type = 'default',
+ classNames = {},
+ styles = {},
+ rootClassName,
+
+ // layout
+ orientation = 'horizontal',
labelPlacement = 'horizontal',
- iconPrefix = 'rc',
+
+ // data
status = 'process',
- size,
current = 0,
- progressDot = false,
- stepIcon,
initial = 0,
- icons,
onChange,
+ items,
+
+ // render
+ iconRender,
itemRender,
- items = [],
+ itemWrapperRender,
+
...restProps
} = props;
- const isNav = type === 'navigation';
- const isInline = type === 'inline';
-
- // inline type requires fixed progressDot direction size.
- const mergedProgressDot = isInline || progressDot;
- const mergedDirection = isInline ? 'horizontal' : direction;
- const mergedSize = isInline ? undefined : size;
+ // ============================= layout =============================
+ const isVertical = orientation === 'vertical';
+ const mergedOrientation = isVertical ? 'vertical' : 'horizontal';
+ const mergeLabelPlacement =
+ !isVertical && labelPlacement === 'vertical' ? 'vertical' : 'horizontal';
+
+ // ============================= styles =============================
+ const classString = cls(
+ prefixCls,
+ `${prefixCls}-${mergedOrientation}`,
+ `${prefixCls}-label-${mergeLabelPlacement}`,
+ rootClassName,
+ className,
+ classNames.root,
+ );
- const adjustedLabelPlacement = mergedProgressDot ? 'vertical' : labelPlacement;
- const classString = classNames(prefixCls, `${prefixCls}-${mergedDirection}`, className, {
- [`${prefixCls}-${mergedSize}`]: mergedSize,
- [`${prefixCls}-label-${adjustedLabelPlacement}`]: mergedDirection === 'horizontal',
- [`${prefixCls}-dot`]: !!mergedProgressDot,
- [`${prefixCls}-navigation`]: isNav,
- [`${prefixCls}-inline`]: isInline,
- });
+ // ============================== Data ==============================
+ const mergedItems = React.useMemo(() => (items || []).filter(Boolean), [items]);
+ const statuses = React.useMemo(
+ () =>
+ mergedItems.map(({ status: itemStatus }, index) => {
+ const stepNumber = initial + index;
+
+ if (!itemStatus) {
+ if (stepNumber === current) {
+ return status;
+ } else if (stepNumber < current) {
+ return 'finish';
+ }
+ return 'wait';
+ }
+
+ return itemStatus;
+ }),
+ [mergedItems, status, current, initial],
+ );
+ // ============================= events =============================
const onStepClick = (next: number) => {
if (onChange && current !== next) {
onChange(next);
}
};
- const renderStep = (item: StepProps, index: number) => {
- const mergedItem: StepProps = { ...item };
- const stepNumber = initial + index;
- // fix tail color
- if (status === 'error' && index === current - 1) {
- mergedItem.className = `${prefixCls}-next-error`;
- }
-
- if (!mergedItem.status) {
- if (stepNumber === current) {
- mergedItem.status = status;
- } else if (stepNumber < current) {
- mergedItem.status = 'finish';
- } else {
- mergedItem.status = 'wait';
- }
- }
+ // ============================= render =============================
+ const renderStep = (item: StepItem, index: number) => {
+ const stepIndex = initial + index;
- if (isInline) {
- mergedItem.icon = undefined;
- mergedItem.subTitle = undefined;
- }
+ const itemStatus = statuses[index];
+ const nextStatus = statuses[index + 1];
- if (!mergedItem.render && itemRender) {
- mergedItem.render = (stepItem) => itemRender(mergedItem, stepItem);
- }
+ const data = {
+ ...item,
+ status: itemStatus,
+ };
return (
);
};
return (
-
- {items.filter(Boolean).map
(renderStep)}
+
+ {mergedItems.map(renderStep)}
);
-};
-
-Steps.Step = Step;
-
-if (process.env.NODE_ENV !== 'production') {
- Steps.displayName = 'rc-steps';
}
-
-export default Steps;
diff --git a/src/index.ts b/src/index.ts
index 738dae39..8c5e175c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,6 @@
-import Steps from './Steps';
+import Steps, { type StepsProps } from './Steps';
import Step from './Step';
export { Step };
+export type { StepsProps };
export default Steps;
diff --git a/src/interface.ts b/src/interface.ts
index 54bce1e7..8b137891 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -1,6 +1 @@
-export type Status = 'error' | 'process' | 'finish' | 'wait';
-export interface Icons {
- finish: React.ReactNode;
- error: React.ReactNode;
-}
diff --git a/tests/__snapshots__/index.test.tsx.snap b/tests/__snapshots__/index.test.tsx.snap
index b44aebb3..c7ac8a0f 100644
--- a/tests/__snapshots__/index.test.tsx.snap
+++ b/tests/__snapshots__/index.test.tsx.snap
@@ -8,27 +8,25 @@ exports[`Steps render renders correctly 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-active"
>
@@ -37,27 +35,25 @@ exports[`Steps render renders correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -66,27 +62,25 @@ exports[`Steps render renders correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -95,27 +89,22 @@ exports[`Steps render renders correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -131,25 +120,25 @@ exports[`Steps render renders current correctly 1`] = `
class="rc-steps-item rc-steps-item-finish"
>
@@ -158,25 +147,25 @@ exports[`Steps render renders current correctly 1`] = `
class="rc-steps-item rc-steps-item-finish"
>
@@ -185,27 +174,25 @@ exports[`Steps render renders current correctly 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-active"
>
@@ -214,27 +201,22 @@ exports[`Steps render renders current correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -250,27 +232,25 @@ exports[`Steps render renders labelPlacement correctly 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-active"
>
@@ -279,27 +259,25 @@ exports[`Steps render renders labelPlacement correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -308,27 +286,25 @@ exports[`Steps render renders labelPlacement correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -337,27 +313,22 @@ exports[`Steps render renders labelPlacement correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -367,35 +338,31 @@ exports[`Steps render renders labelPlacement correctly 1`] = `
exports[`Steps render renders progressDot correctly 1`] = `
@@ -404,29 +371,25 @@ exports[`Steps render renders progressDot correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -435,29 +398,25 @@ exports[`Steps render renders progressDot correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -466,29 +425,22 @@ exports[`Steps render renders progressDot correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -498,35 +450,31 @@ exports[`Steps render renders progressDot correctly 1`] = `
exports[`Steps render renders progressDot function correctly 1`] = `
@@ -535,29 +483,25 @@ exports[`Steps render renders progressDot function correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -566,29 +510,25 @@ exports[`Steps render renders progressDot function correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -597,29 +537,22 @@ exports[`Steps render renders progressDot function correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -635,52 +568,52 @@ exports[`Steps render renders status correctly 1`] = `
class="rc-steps-item rc-steps-item-finish"
>
@@ -689,25 +622,25 @@ exports[`Steps render renders status correctly 1`] = `
class="rc-steps-item rc-steps-item-error rc-steps-item-active"
>
@@ -716,27 +649,22 @@ exports[`Steps render renders status correctly 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -752,30 +680,28 @@ exports[`Steps render renders step with description 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-active"
>
-
- 1
-
-
-
xx
@@ -786,30 +712,28 @@ exports[`Steps render renders step with description 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
-
-
- 2
-
-
+ />
xx
@@ -820,30 +744,28 @@ exports[`Steps render renders step with description 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
-
-
- 3
-
-
+ />
xx
@@ -854,30 +776,25 @@ exports[`Steps render renders step with description 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
-
-
- 4
-
-
+ />
xx
@@ -895,30 +812,28 @@ exports[`Steps render renders step with description and status 1`] = `
class="rc-steps-item rc-steps-item-wait rc-steps-item-active"
>
-
-
- 1
-
-
+ />
xx
@@ -929,30 +844,28 @@ exports[`Steps render renders step with description and status 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
-
-
- 2
-
-
+ />
xx
@@ -963,30 +876,28 @@ exports[`Steps render renders step with description and status 1`] = `
class="rc-steps-item rc-steps-item-process"
>
-
-
- 3
-
-
+ />
xx
@@ -997,28 +908,25 @@ exports[`Steps render renders step with description and status 1`] = `
class="rc-steps-item rc-steps-item-finish"
>
-
-
-
+ />
xx
@@ -1028,7 +936,7 @@ exports[`Steps render renders step with description and status 1`] = `
`;
-exports[`Steps render renders step with tailContent 1`] = `
+exports[`Steps render renders stepIcon function correctly 1`] = `
@@ -1036,34 +944,25 @@ exports[`Steps render renders step with tailContent 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-active"
>
-
- text
-
-
- 1
-
-
+ />
@@ -1072,36 +971,25 @@ exports[`Steps render renders step with tailContent 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -1110,34 +998,25 @@ exports[`Steps render renders step with tailContent 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
@@ -1146,34 +1025,22 @@ exports[`Steps render renders step with tailContent 1`] = `
class="rc-steps-item rc-steps-item-wait"
>
-
- text
-
-
- 4
-
-
+ />
@@ -1181,121 +1048,112 @@ exports[`Steps render renders step with tailContent 1`] = `
`;
-exports[`Steps render renders step with type inline 1`] = `
+exports[`Steps render renders vertical correctly 1`] = `
-
-
-
-
-
+ />
- Step 1
-
-
- This is a description.
+
+ 已完成
+
+
-
-
-
-
-
+ />
- Step 2
-
-
- This is a description.
+
+ 进行中
+
+
+
+
+
+
+
- Step 3
-
-
- This is a description.
+
+ 待运行
+
@@ -1303,499 +1161,135 @@ exports[`Steps render renders step with type inline 1`] = `
`;
-exports[`Steps render renders step with type navigation 1`] = `
+exports[`Steps render renders with falsy children 1`] = `
-
-
-
+ />
- Step 1
- 剩余 00:00:05 超长隐藏
+ 已完成
+
- This is a description.
+ xx
-
-
- 2
-
-
+ />
- Step 2
+
+ 进行中
+
+
+ 剩余 00:00:07
+
+
- This is a description.
+ xx
-
-
- 3
-
-
+ />
- This is a description.
-
-
-
-
-
-`;
-
-exports[`Steps render renders stepIcon function correctly 1`] = `
-
-`;
-
-exports[`Steps render renders vertical correctly 1`] = `
-
-`;
-
-exports[`Steps render renders with falsy children 1`] = `
-
-
-
-
-
-
- 2
-
-
-
- 进行中
- 剩余 00:00:07
+ 待运行
- xx
-
-
-
-
-
-
-
-
-
-
-
-
-
- 待运行
-
-
xx
@@ -1805,138 +1299,6 @@ exports[`Steps render renders with falsy children 1`] = `
`;
-exports[`Steps render should render svg finishIcon and errorIcon correctly 1`] = `
-
-
-
-
-
-
-
- Finished
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
- In Process
-
-
- This is a description
-
-
-
-
-
-
-
-
-
- 3
-
-
-
-
- Waiting
-
-
- This is a description
-
-
-
-
-
-`;
-
exports[`Steps should render customIcon correctly 1`] = `
@@ -1976,25 +1334,25 @@ exports[`Steps should render customIcon correctly 1`] = `
class="rc-steps-item rc-steps-item-process rc-steps-item-custom rc-steps-item-active"
>
@@ -2003,25 +1361,22 @@ exports[`Steps should render customIcon correctly 1`] = `
class="rc-steps-item rc-steps-item-wait rc-steps-item-custom"
>
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
index 413c859f..f0a2d034 100644
--- a/tests/index.test.tsx
+++ b/tests/index.test.tsx
@@ -1,10 +1,11 @@
import React from 'react';
-import { render, fireEvent, createEvent } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import Steps from '../src';
describe('Steps', () => {
describe('render', () => {
- let description = 'xx';
+ const description = 'xx';
+
const setSteps = (props) => (
{
);
expect(container.firstChild).toMatchSnapshot();
});
-
- it('renders step with tailContent', () => {
- const { container } = render(
- content ,
- },
- {
- title: '待运行',
- description,
- tailContent: 3,
- },
- {
- title: '待运行',
- description,
- tailContent: 'text',
- },
- ]}
- />,
- );
- expect(container.firstChild).toMatchSnapshot();
- });
-
- it('renders step with type navigation', () => {
- description = 'This is a description.';
- const { container } = render(
-
{}}
- items={[
- {
- title: 'Step 1',
- subTitle: '剩余 00:00:05 超长隐藏',
- description,
- },
- {
- title: 'Step 2',
- description,
- },
- {
- title: 'Step 3',
- description,
- disabled: true,
- },
- ]}
- />,
- );
- expect(container.firstChild).toMatchSnapshot();
- });
-
- it('renders step with type inline', () => {
- description = 'This is a description.';
- const { container } = render(
- {}}
- items={[
- {
- title: 'Step 1',
- description,
- },
- {
- title: 'Step 2',
- description,
- },
- {
- title: 'Step 3',
- description,
- disabled: true,
- },
- ]}
- itemRender={(item, stepItem) => React.cloneElement(stepItem, { title: item.description })}
- />,
- );
- expect(container.firstChild).toMatchSnapshot();
- });
-
- function getFinishIcon() {
- const path =
- 'M923 283.6c-13.4-31.1-32.6-58.9-56.9-82.8-24.3-23.8-52.' +
- '5-42.4-84-55.5-32.5-13.5-66.9-20.3-102.4-20.3-49.3 0-97.4 13.5-139' +
- '.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5' +
- '-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 ' +
- '55.5-24.4 23.9-43.5 51.7-56.9 82.8-13.9 32.3-21 66.6-21 101.9 0 33' +
- '.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99' +
- '.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.' +
- '7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 10' +
- '1.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 ' +
- '20.3-103.3 0.1-35.3-7-69.6-20.9-101.9z';
- return (
-
- );
- }
-
- function getErrorIcon() {
- const path1 =
- 'M512 0C229.2 0 0 229.2 0 512s229.2 512 512 512 512-229' +
- '.2 512-512S794.8 0 512 0zm311.1 823.1c-40.4 40.4-87.5 72.2-139.9 9' +
- '4.3C629 940.4 571.4 952 512 952s-117-11.6-171.2-34.5c-52.4-22.2-99' +
- '.4-53.9-139.9-94.3-40.4-40.4-72.2-87.5-94.3-139.9C83.6 629 72 571.' +
- '4 72 512s11.6-117 34.5-171.2c22.2-52.4 53.9-99.4 94.3-139.9 40.4-4' +
- '0.4 87.5-72.2 139.9-94.3C395 83.6 452.6 72 512 72s117 11.6 171.2 3' +
- '4.5c52.4 22.2 99.4 53.9 139.9 94.3 40.4 40.4 72.2 87.5 94.3 139.9C' +
- '940.4 395 952 452.6 952 512s-11.6 117-34.5 171.2c-22.2 52.4-53.9 9' +
- '9.5-94.4 139.9z';
- const path2 =
- 'M640.3 765.5c-19.9 0-36-16.1-36-36 0-50.9-41.4-92.3-92' +
- '.3-92.3s-92.3 41.4-92.3 92.3c0 19.9-16.1 36-36 36s-36-16.1-36-36c0' +
- '-90.6 73.7-164.3 164.3-164.3s164.3 73.7 164.3 164.3c0 19.9-16.1 36' +
- '-36 36zM194.2 382.4a60 60 0 1 0 120 0 60 60 0 1 0-120 0zM709.5 382' +
- '.4a60 60 0 1 0 120 0 60 60 0 1 0-120 0z';
- return (
-
- );
- }
- it('should render svg finishIcon and errorIcon correctly', () => {
- const icons = {
- finish: getFinishIcon(),
- error: getErrorIcon(),
- };
- const { container } = render(
- ,
- );
- expect(container.firstChild).toMatchSnapshot();
- });
});
it('should render customIcon correctly', () => {
@@ -380,7 +210,7 @@ describe('Steps', () => {
]}
/>,
);
- const items = container.querySelectorAll('.rc-steps-item-container');
+ const items = container.querySelectorAll('.rc-steps-item-wrapper');
fireEvent.click(items[1]);
expect(onChange).toBeCalledWith(1);
});
@@ -403,7 +233,7 @@ describe('Steps', () => {
,
);
- const step = container.querySelectorAll('.rc-steps-item-container')[1];
+ const step = container.querySelectorAll('.rc-steps-item-wrapper')[1];
fireEvent.click(step);
rerender();
expect(container.querySelectorAll('.rc-steps-item')[1].classList).toContain(
@@ -435,7 +265,7 @@ describe('Steps', () => {
/>,
);
- const btn = container.querySelectorAll('.rc-steps-item-container')[0];
+ const btn = container.querySelectorAll('.rc-steps-item-wrapper')[0];
fireEvent.click(btn);
expect(onClick).toHaveBeenCalled();
});
@@ -446,7 +276,7 @@ describe('Steps', () => {
,
);
- const items = container.querySelectorAll('.rc-steps-item-container');
+ const items = container.querySelectorAll('.rc-steps-item-wrapper');
fireEvent.click(items[2]);
expect(onChange).not.toBeCalled();
});
@@ -475,4 +305,43 @@ describe('Steps', () => {
expect(onChange).toHaveBeenCalledWith(1);
});
+
+ it('itemRender', () => {
+ const { container } = render(
+ {
+ return {oriNode}
;
+ }}
+ />,
+ );
+
+ expect(container.querySelector('.bamboo')).toBeTruthy();
+ expect(container.querySelectorAll('.bamboo')).toHaveLength(1);
+ expect(container.querySelector('.rc-steps-item')).toBeTruthy();
+ });
+
+ it('itemWrapperRender', () => {
+ const { container } = render(
+ {
+ return {oriNode}
;
+ }}
+ />,
+ );
+
+ expect(container.querySelector('.bamboo')).toBeTruthy();
+ expect(container.querySelectorAll('.bamboo')).toHaveLength(1);
+ expect(container.querySelector('.rc-steps-item')).toBeTruthy();
+ expect(container.querySelector('.rc-steps-item > .bamboo')).toBeTruthy();
+ });
});
diff --git a/tests/semantic.test.tsx b/tests/semantic.test.tsx
new file mode 100644
index 00000000..d84b7355
--- /dev/null
+++ b/tests/semantic.test.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import Steps, { type StepsProps } from '../src';
+import type { SemanticName } from '../src/Steps';
+
+describe('Steps.Semantic', () => {
+ const renderSteps = (props: Partial) => (
+ ({
+ title: `Step ${index + 1}`,
+ subTitle: `SubTitle ${index + 1}`,
+ description: `Description ${index + 1}`,
+ }))}
+ {...props}
+ />
+ );
+
+ it('semantic structure', () => {
+ const classNames: Record = {
+ root: 'custom-root',
+ item: 'custom-item',
+ itemWrapper: 'custom-item-wrapper',
+ itemIcon: 'custom-item-icon',
+ itemSection: 'custom-item-section',
+ itemHeader: 'custom-item-header',
+ itemTitle: 'custom-item-title',
+ itemSubtitle: 'custom-item-subtitle',
+ itemContent: 'custom-item-content',
+ itemRail: 'custom-item-rail',
+ };
+
+ const classNamesTargets: Record = {
+ root: 'rc-steps',
+ item: 'rc-steps-item',
+ itemWrapper: 'rc-steps-item-wrapper',
+ itemIcon: 'rc-steps-item-icon',
+ itemSection: 'rc-steps-item-section',
+ itemHeader: 'rc-steps-item-header',
+ itemTitle: 'rc-steps-item-title',
+ itemSubtitle: 'rc-steps-item-subtitle',
+ itemContent: 'rc-steps-item-content',
+ itemRail: 'rc-steps-item-rail',
+ };
+
+ const styles: Record> = {
+ root: { color: 'red' },
+ item: { color: 'blue' },
+ itemWrapper: { color: 'green' },
+ itemIcon: { color: 'yellow' },
+ itemSection: { color: 'purple' },
+ itemHeader: { color: 'orange' },
+ itemTitle: { color: 'pink' },
+ itemSubtitle: { color: 'cyan' },
+ itemContent: { color: 'magenta' },
+ itemRail: { color: 'lime' },
+ };
+
+ const { container } = render(
+ renderSteps({
+ classNames,
+ styles,
+ }),
+ );
+
+ Object.keys(classNames).forEach((key) => {
+ const className = classNames[key as SemanticName];
+ const oriClassName = classNamesTargets[key as SemanticName];
+ const style = styles[key as SemanticName];
+
+ const element = container.querySelector(`.${className}`);
+ expect(element).toBeTruthy();
+ expect(element).toHaveClass(oriClassName);
+ expect(element).toHaveStyle(style);
+ });
+ });
+});