Skip to content

Commit

Permalink
refactor(steps): refactor steps
Browse files Browse the repository at this point in the history
  • Loading branch information
epoll-j committed Sep 24, 2024
1 parent c8512cf commit 387ff12
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 103 deletions.
195 changes: 128 additions & 67 deletions src/steps/StepItem.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { FC, useContext, useMemo } from 'react';
import classnames from 'classnames';
import { Icon } from 'tdesign-icons-react';
import { CheckIcon, CloseIcon } from 'tdesign-icons-react';
import isNumber from 'lodash/isNumber';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import { TdStepItemProps } from './type';
import useConfig from '../_util/useConfig';
import { stepItemDefaultProps } from './defaultProps';
import StepsContext from './StepsContext';
import useDefaultProps from '../hooks/useDefaultProps';
import { usePrefixClass } from '../hooks/useClass';
import parseTNode from '../_util/parseTNode';

export enum StepItemStatusEnum {
DEFAULT = 'default',
Expand All @@ -29,96 +32,154 @@ export interface StepItemProps extends TdStepItemProps, NativeProps {
}

const StepItem: FC<StepItemProps> = (props) => {
const { title, content, icon, status, index, children } = props;
const { title, titleRight, extra, content, icon, status, index, children } = useDefaultProps(
props,
stepItemDefaultProps,
);

const { value, readonly, theme, layout, onChange } = useContext(StepsContext);
const {
value,
readonly,
theme,
layout,
currentStatus: contextStatus,
onChange,
sequence,
itemList,
} = useContext(StepsContext);

const stepItemClass = usePrefixClass('step-item');
const dot = useMemo(() => theme === StepThemeEnum.DOT, [theme]);

const isLastChild = useMemo(
() => index === (sequence === 'positive' ? itemList.length - 1 : 0),
[index, itemList.length, sequence],
);

const { classPrefix } = useConfig();
const currentStatus = useMemo(() => {
if (status !== StepItemStatusEnum.DEFAULT) return status;
if (index === value) return contextStatus;
if (isNumber(value) && index < value) return StepItemStatusEnum.FINISH;
return status;
}, [contextStatus, index, status, value]);

const rootClassName = useMemo(
() =>
classnames(stepItemClass, `${stepItemClass}--${layout}`, {
[`${stepItemClass}--default`]: readonly,
[`${stepItemClass}--${currentStatus}`]: currentStatus,
}),
[currentStatus, layout, readonly, stepItemClass],
);
const iconWrapperClassName = useMemo(
() => classnames(`${stepItemClass}__anchor`, `${stepItemClass}__anchor--${layout}`),
[layout, stepItemClass],
);
const dotClass = useMemo(
() => classnames(`${stepItemClass}__dot`, `${stepItemClass}__dot--${currentStatus}`),
[currentStatus, stepItemClass],
);

const name = `${classPrefix}-step`;
const iconClassName = useMemo(
() =>
classnames(``, {
[`${stepItemClass}__icon`]: icon,
[`${stepItemClass}__icon--${currentStatus}`]: icon,
[`${stepItemClass}__circle`]: !icon,
[`${stepItemClass}__circle--${currentStatus}`]: !icon,
}),
[currentStatus, icon, stepItemClass],
);

const dot = useMemo(() => theme === StepThemeEnum.DOT && layout === StepLayoutEnum.VERTICAL, [theme, layout]);
const contentClass = useMemo(
() =>
classnames(`${stepItemClass}__content`, `${stepItemClass}__content--${layout}`, {
[`${stepItemClass}__content--${layout}`]: isLastChild,
[`${stepItemClass}__content--last`]: isLastChild,
}),
[isLastChild, layout, stepItemClass],
);
const tilteClass = useMemo(
() =>
classnames(
`${stepItemClass}__title`,
`${stepItemClass}__title--${currentStatus}`,
`${stepItemClass}__title--${layout}`,
),
[currentStatus, layout, stepItemClass],
);
const descriptionClass = useMemo(
() =>
classnames(
`${stepItemClass}__description`,
`${stepItemClass}__description--${currentStatus}`,
`${stepItemClass}__description--${layout}`,
),
[currentStatus, layout, stepItemClass],
);
const extraClass = useMemo(
() =>
classnames(
`${stepItemClass}__extra`,
`${stepItemClass}__extra--${currentStatus}`,
`${stepItemClass}__extra--${layout}`,
),
[currentStatus, layout, stepItemClass],
);
const separatorClass = useMemo(
() =>
classnames(
stepItemClass,
`${stepItemClass}__line`,
`${stepItemClass}__line--${currentStatus}`,
`${stepItemClass}__line--${layout}`,
`${stepItemClass}__line--${theme}`,
`${stepItemClass}__line--${sequence}`,
),
[currentStatus, layout, sequence, stepItemClass, theme],
);

const onStepClick = (e) => {
if (readonly || dot) return;
if (readonly) return;
const currentValue = index;
onChange(currentValue, value, { e });
};

const innerClassName = useMemo(() => {
if (typeof icon === 'boolean') {
return `${name}__inner`;
const renderIconContent = () => {
if (icon) {
return parseTNode(icon);
}
return classnames(`${name}__inner`, `${name}__inner__icon`);
}, [name, icon]);

const iconContent = useMemo(() => {
if (dot) {
return '';
if (currentStatus === StepItemStatusEnum.ERROR) {
return <CloseIcon />;
}

if (status === StepItemStatusEnum.ERROR) {
return <Icon name="close" />;
if (currentStatus === StepItemStatusEnum.FINISH) {
return <CheckIcon />;
}

if (index < value && readonly) {
return <Icon name="check" />;
}

if (typeof icon === 'boolean') {
return index + 1;
}

if (typeof icon === 'string') {
return <Icon name={icon} size="24" />;
}

return icon;
}, [status, index, value, readonly, icon, dot]);

const currentStatus = useMemo(() => {
if (status !== StepItemStatusEnum.DEFAULT) {
return status;
}
if (+value === index) {
return StepItemStatusEnum.PROCESS;
}
if (+value > index) {
return StepItemStatusEnum.FINISH;
}
return '';
}, [value, index, status]);
return index + 1;
};

return withNativeProps(
props,
<div
className={classnames(`${name}`, {
[`${name}--default`]: !readonly,
[`${name}--${currentStatus}`]: currentStatus,
})}
>
<div className={innerClassName}>
<div className={`${name}-icon`}>
<div
className={classnames(`${name}-icon__number`, {
[`${name}-icon__dot`]: dot,
})}
onClick={onStepClick}
>
{iconContent}
</div>
</div>
<div className={`${name}-content`}>
<div className={`${name}-title`}>{title}</div>
<div className={`${name}-description`}>{content || children}</div>
<div className={`${name}-extra`} />
<div className={rootClassName} onClick={onStepClick}>
<div className={iconWrapperClassName}>
{dot ? <div className={dotClass}></div> : <div className={iconClassName}>{renderIconContent()}</div>}
</div>
<div className={contentClass}>
<div className={tilteClass}>
{parseTNode(title)}
{parseTNode(titleRight)}
</div>
<div className={descriptionClass}>{parseTNode(children || content)}</div>
<div className={extraClass}>{parseTNode(extra)}</div>
</div>
{!isLastChild && <div className={separatorClass}></div>}
</div>,
);
};

StepItem.displayName = 'StepItem';
StepItem.defaultProps = stepItemDefaultProps;

export default StepItem;
31 changes: 12 additions & 19 deletions src/steps/Steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import classnames from 'classnames';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import useDefault from '../_util/useDefault';
import { TdStepsProps } from './type';
import useConfig from '../_util/useConfig';
import { stepsDefaultProps } from './defaultProps';
import StepsContext from './StepsContext';
import StepItem from './StepItem';
import useDefaultProps from '../hooks/useDefaultProps';
import { usePrefixClass } from '../hooks/useClass';

export interface StepsProps extends TdStepsProps, NativeProps {}

Expand All @@ -16,17 +17,14 @@ const Steps: FC<StepsProps> = (props) => {
layout,
readonly,
theme,
separator,
sequence,
current,
defaultCurrent,
currentStatus,
onChange: onCurrentChange,
options,
} = props;

const { classPrefix } = useConfig();

const name = `${classPrefix}-steps`;

} = useDefaultProps(props, stepsDefaultProps);
const stepsClass = usePrefixClass('steps');
const [value, onChange] = useDefault(current, defaultCurrent, onCurrentChange);

const stepItemList = useMemo(() => {
Expand All @@ -42,17 +40,13 @@ const Steps: FC<StepsProps> = (props) => {

return withNativeProps(
props,
<StepsContext.Provider value={{ value, readonly, theme, layout, onChange }}>
<StepsContext.Provider
value={{ value, readonly, theme, layout, onChange, currentStatus, sequence, itemList: stepItemList }}
>
<div
className={classnames(
name,
`${name}--${layout}`,
`${name}--${theme}-anchor`,
`${name}--${separator}-separator`,
{
[`${name}--readonly`]: readonly,
},
)}
className={classnames(stepsClass, `${stepsClass}--${layout}`, `${stepsClass}--${sequence}`, {
[`${stepsClass}--readonly`]: readonly,
})}
>
{stepItemList}
</div>
Expand All @@ -61,6 +55,5 @@ const Steps: FC<StepsProps> = (props) => {
};

Steps.displayName = 'Steps';
Steps.defaultProps = stepsDefaultProps;

export default Steps;
7 changes: 7 additions & 0 deletions src/steps/StepsContext.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React, { MouseEvent } from 'react';
import { TNode } from '../common';

interface StepsContextProps {
value: string | number;
readonly: boolean;
theme: 'default' | 'dot';
layout: 'horizontal' | 'vertical';
currentStatus?: 'default' | 'process' | 'finish' | 'error';
sequence?: 'positive' | 'reverse';
itemList: TNode[];
onChange: (current: string | number, previous: string | number, context?: { e?: MouseEvent<HTMLDivElement> }) => void;
}

Expand All @@ -14,6 +18,9 @@ const StepsContext = React.createContext<StepsContextProps>({
onChange: null,
theme: 'default',
layout: 'horizontal',
sequence: 'positive',
itemList: [],
currentStatus: 'default',
});

export default StepsContext;
11 changes: 9 additions & 2 deletions src/steps/defaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
import { TdStepsProps, TdStepItemProps } from './type';

export const stepsDefaultProps: TdStepsProps = {
currentStatus: 'process',
layout: 'horizontal',
readonly: false,
separator: 'line',
sequence: 'positive',
theme: 'default',
};

export const stepItemDefaultProps: TdStepItemProps = { icon: true, status: 'default' };
export const stepItemDefaultProps: TdStepItemProps = {
content: '',
extra: '',
status: 'default',
title: '',
titleRight: '',
};
3 changes: 2 additions & 1 deletion src/steps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type { StepItemProps } from './StepItem';
export * from './type';

export const Steps = _Steps;
(Steps as any).StepItem = _StepItem;
export const StepItem = _StepItem;
// (Steps as any).StepItem = _StepItem;

export default Steps;
35 changes: 35 additions & 0 deletions src/steps/steps.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:: BASE_DOC ::

## API

### Steps Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
current | String / Number | - | \- | N
defaultCurrent | String / Number | - | uncontrolled property | N
currentStatus | String | process | options: default/process/finish/error | N
layout | String | horizontal | options: horizontal/vertical | N
options | Array | - | Typescript:`Array<TdStepItemProps>` | N
readonly | Boolean | false | \- | N
sequence | String | positive | options: positive/reverse | N
theme | String | default | options: default/dot | N
onChange | Function | | Typescript:`(current: string \| number, previous: string \| number, context?: { e?: MouseEvent }) => void`<br/> | N


### StepItem Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
children | TNode | - | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
content | TNode | '' | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
extra | TNode | '' | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
icon | TNode | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
status | String | default | options: default/process/finish/error。Typescript:`StepStatus` `type StepStatus = 'default' \| 'process' \| 'finish' \| 'error'`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/steps/type.ts) | N
subStepItems | Array | [] | `deprecated`。Typescript:`SubStepItem[]` `interface SubStepItem { status: StepStatus, title: string }`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/steps/type.ts) | N
title | TNode | '' | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
titleRight | TNode | '' | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
Loading

0 comments on commit 387ff12

Please sign in to comment.