Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/steps 样式功能对齐vue #530

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export default {
{
title: 'Steps 步骤条',
name: 'steps',
component: () => import('tdesign-mobile-react/steps/_example/index.jsx'),
component: () => import('tdesign-mobile-react/steps/_example/index.tsx'),
},
{
title: 'TabBar 标签栏',
Expand Down
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;
54 changes: 0 additions & 54 deletions src/steps/_example/click.jsx

This file was deleted.

Loading
Loading