refactor: steps

pull/4606/head
tangjinzhou 2021-08-30 15:39:57 +08:00
parent 899757a054
commit 740ba04c02
21 changed files with 808 additions and 501 deletions

View File

@ -3,3 +3,5 @@ timeline: 新增 label
tree、tree-slelct: 新增虚拟滚动、title 逻辑变动
date 相关组件: dayjs UI 变动
steps: add responsive、percent

View File

@ -1,19 +1,25 @@
import type { App, ExtractPropTypes, Plugin } from 'vue';
import { defineComponent, inject } from 'vue';
import { computed } from 'vue';
import { defineComponent } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CheckOutlined from '@ant-design/icons-vue/CheckOutlined';
import PropTypes, { withUndefined } from '../_util/vue-types';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import VcSteps from '../vc-steps';
import { defaultConfigProvider } from '../config-provider';
import { tuple } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
import useBreakpoint from '../_util/hooks/useBreakpoint';
import classNames from '../_util/classNames';
import Progress from '../progress';
import omit from '../_util/omit';
const stepsProps = {
export const stepsProps = () => ({
prefixCls: PropTypes.string,
iconPrefix: PropTypes.string,
current: PropTypes.number,
initial: PropTypes.number,
percent: PropTypes.number,
responsive: PropTypes.looseBool,
labelPlacement: PropTypes.oneOf(tuple('horizontal', 'vertical')).def('horizontal'),
status: PropTypes.oneOf(tuple('wait', 'process', 'finish', 'error')),
size: PropTypes.oneOf(tuple('default', 'small')),
@ -22,52 +28,98 @@ const stepsProps = {
type: PropTypes.oneOf(tuple('default', 'navigation')),
onChange: PropTypes.func,
'onUpdate:current': PropTypes.func,
};
});
export type StepsProps = Partial<ExtractPropTypes<typeof stepsProps>>;
export const stepProps = () => ({
description: PropTypes.any,
icon: PropTypes.any,
status: PropTypes.oneOf(tuple('wait', 'process', 'finish', 'error')),
disabled: PropTypes.looseBool,
title: PropTypes.any,
subTitle: PropTypes.any,
onClick: PropTypes.func,
});
export type StepsProps = Partial<ExtractPropTypes<ReturnType<typeof stepsProps>>>;
export type StepProps = Partial<ExtractPropTypes<ReturnType<typeof stepProps>>>;
const Steps = defineComponent({
name: 'ASteps',
inheritAttrs: false,
props: initDefaultProps(stepsProps, {
props: initDefaultProps(stepsProps(), {
current: 0,
responsive: true,
}),
slots: ['progressDot'],
emits: ['update:current', 'change'],
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
setup(props, { attrs, slots, emit }) {
const { prefixCls, direction: rtlDirection, configProvider } = useConfigInject('steps', props);
const screens = useBreakpoint();
const direction = computed(() =>
props.responsive && screens.value.xs ? 'vertical' : props.direction,
);
const iconPrefix = computed(() => configProvider.getPrefixCls('', props.iconPrefix));
const handleChange = (current: number) => {
emit('update:current', current);
emit('change', current);
};
const stepIconRender = ({
node,
status,
}: {
node: any;
index: number;
status: string;
title: any;
description: any;
}) => {
if (status === 'process' && props.percent !== undefined) {
// currently it's hard-coded, since we can't easily read the actually width of icon
const progressWidth = props.size === 'small' ? 32 : 40;
const iconWithProgress = (
<div class={`${prefixCls}-progress-icon`}>
<Progress
type="circle"
percent={props.percent}
width={progressWidth}
strokeWidth={4}
format={() => null}
/>
{node}
</div>
);
return iconWithProgress;
}
return node;
};
return () => {
const stepsClassName = classNames(
{
[`${prefixCls.value}-rtl`]: rtlDirection.value === 'rtl',
[`${prefixCls.value}-with-progress`]: props.percent !== undefined,
},
attrs.class,
);
const icons = {
finish: <CheckOutlined class={`${prefixCls}-finish-icon`} />,
error: <CloseOutlined class={`${prefixCls}-error-icon`} />,
};
return (
<VcSteps
icons={icons}
{...omit(props, ['percent', 'responsive'])}
direction={direction.value}
prefixCls={prefixCls.value}
iconPrefix={iconPrefix.value}
class={stepsClassName}
onChange={handleChange}
v-slots={{ ...slots, stepIcon: stepIconRender }}
/>
);
};
},
Step: { ...VcSteps.Step, name: 'AStep' },
methods: {
handleChange(current: number) {
this.$emit('update:current', current);
this.$emit('change', current);
},
},
render() {
const props: StepsProps = { ...getOptionProps(this), ...this.$attrs };
const { prefixCls: customizePrefixCls, iconPrefix: customizeIconPrefixCls } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('steps', customizePrefixCls);
const iconPrefix = getPrefixCls('', customizeIconPrefixCls);
const progressDot = getComponent(this, 'progressDot', this, false);
const icons = {
finish: <CheckOutlined class={`${prefixCls}-finish-icon`} />,
error: <CloseOutlined class={`${prefixCls}-error-icon`} />,
};
const stepsProps = {
icons,
iconPrefix,
prefixCls,
progressDot,
...props,
canClick: !!(this.onChange || this['onUpdate:current']),
onChange: this.handleChange,
};
return <VcSteps {...stepsProps}>{getSlot(this)}</VcSteps>;
},
});
/* istanbul ignore next */

View File

@ -1,50 +0,0 @@
.@{steps-prefix-cls}-flex-not-supported {
&.@{steps-prefix-cls}-horizontal.@{steps-prefix-cls}-label-horizontal {
.@{steps-prefix-cls}-item {
margin-left: -16px;
padding-left: 16px;
background: @steps-background;
}
&.@{steps-prefix-cls}-small .@{steps-prefix-cls}-item {
margin-left: -12px;
padding-left: 12px;
}
}
&.@{steps-prefix-cls}-dot {
.@{steps-prefix-cls}-item {
&:last-child {
overflow: hidden;
.@{steps-prefix-cls}-icon-dot::after {
right: -200px;
width: 200px;
}
}
.@{steps-prefix-cls}-icon-dot::before,
.@{steps-prefix-cls}-icon-dot::after {
position: absolute;
top: 0;
left: -10px;
width: 10px;
height: 8px;
background: @steps-background;
content: '';
}
.@{steps-prefix-cls}-icon-dot::after {
right: -10px;
left: auto;
}
}
.@{steps-prefix-cls}-item-wait
.@{steps-prefix-cls}-item-icon
> .@{steps-prefix-cls}-icon
.@{steps-prefix-cls}-icon-dot {
background: #ccc;
}
}
}

View File

@ -1,15 +1,15 @@
.@{steps-prefix-cls}-item-custom {
.@{steps-prefix-cls}-item-icon {
> .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-icon {
height: auto;
background: none;
border: 0;
> .@{steps-prefix-cls}-icon {
top: 0;
top: @steps-icon-custom-top;
left: 0.5px;
width: @steps-icon-size;
height: @steps-icon-size;
font-size: 24px;
line-height: @steps-icon-size;
width: @steps-icon-custom-size;
height: @steps-icon-custom-size;
font-size: @steps-icon-custom-font-size;
line-height: @steps-icon-custom-size;
}
}
&.@{steps-prefix-cls}-item-process {
@ -25,6 +25,7 @@
.@{steps-prefix-cls}-item-custom {
.@{steps-prefix-cls}-item-icon {
width: auto;
background: none;
}
}
}

View File

@ -5,7 +5,6 @@
@process-icon-color: @primary-color;
@process-title-color: @heading-color;
@process-description-color: @text-color;
@process-tail-color: @border-color-split;
@process-icon-text-color: @text-color-inverse;
@wait-icon-color: @disabled-color;
@wait-title-color: @text-color-secondary;
@ -21,19 +20,13 @@
@error-tail-color: @wait-tail-color;
@steps-nav-active-color: @primary-color;
@steps-icon-size: 32px;
@steps-small-icon-size: 24px;
@steps-dot-size: 8px;
@steps-current-dot-size: 10px;
@steps-desciption-max-width: 140px;
@steps-nav-content-max-width: auto;
.@{steps-prefix-cls} {
.reset-component();
display: flex;
width: 100%;
font-size: 0;
text-align: initial;
}
.@{steps-prefix-cls}-item {
@ -65,8 +58,8 @@
&-icon {
width: @steps-icon-size;
height: @steps-icon-size;
margin-right: 8px;
font-size: @font-size-lg;
margin: @steps-icon-margin;
font-size: @steps-icon-font-size;
font-family: @font-family;
line-height: @steps-icon-size;
text-align: center;
@ -74,9 +67,9 @@
border-radius: @steps-icon-size;
transition: background-color 0.3s, border-color 0.3s;
> .@{steps-prefix-cls}-icon {
.@{steps-prefix-cls}-icon {
position: relative;
top: -1px;
top: @steps-icon-top;
color: @primary-color;
line-height: 1;
}
@ -87,6 +80,7 @@
left: 0;
width: 100%;
padding: 0 10px;
&::after {
display: inline-block;
width: 100%;
@ -103,10 +97,11 @@
padding-right: 16px;
color: @text-color;
font-size: @font-size-lg;
line-height: @steps-icon-size;
line-height: @steps-title-line-height;
&::after {
position: absolute;
top: (@steps-icon-size / 2);
top: (@steps-title-line-height / 2);
left: 100%;
display: block;
width: 9999px;
@ -128,13 +123,13 @@
}
.step-item-status(wait);
.step-item-status(process);
&-process &-icon {
&-process > &-container > &-icon {
background: @process-icon-color;
> .@{steps-prefix-cls}-icon {
.@{steps-prefix-cls}-icon {
color: @process-icon-text-color;
}
}
&-process &-title {
&-process > &-container > &-title {
font-weight: 500;
}
.step-item-status(finish);
@ -143,6 +138,10 @@
&.@{steps-prefix-cls}-next-error .@{steps-prefix-cls}-item-title::after {
background: @error-icon-color;
}
&-disabled {
cursor: not-allowed;
}
}
// ===================== Clickable =====================
@ -153,6 +152,7 @@
.@{steps-prefix-cls}-item {
&-title,
&-subtitle,
&-description,
&-icon .@{steps-prefix-cls}-icon {
transition: color 0.3s;
@ -188,10 +188,11 @@
.@{steps-prefix-cls}-horizontal:not(.@{steps-prefix-cls}-label-vertical) {
.@{steps-prefix-cls}-item {
margin-right: 16px;
padding-left: 16px;
white-space: nowrap;
&:last-child {
margin-right: 0;
&:first-child {
padding-left: 0;
}
&:last-child .@{steps-prefix-cls}-item-title {
padding-right: 0;
@ -200,7 +201,7 @@
display: none;
}
&-description {
max-width: @steps-desciption-max-width;
max-width: @steps-description-max-width;
white-space: normal;
}
}
@ -235,10 +236,11 @@
}
}
@import 'custom-icon';
@import 'small';
@import 'vertical';
@import 'label-placement';
@import 'progress-dot';
@import 'nav';
@import 'compatibility';
@import './custom-icon';
@import './small';
@import './vertical';
@import './label-placement';
@import './progress-dot';
@import './nav';
@import './rtl';
@import './progress.less';

View File

@ -1,2 +0,0 @@
import '../../style/index.less';
import './index.less';

View File

@ -0,0 +1,6 @@
import '../../style/index.less';
import './index.less';
// style dependencies
// deps-lint-skip: grid
import '../../progress/style';

View File

@ -17,6 +17,7 @@
}
&-title {
padding-right: 0;
padding-left: 0;
&::after {
display: none;
}

View File

@ -76,7 +76,7 @@
left: 50%;
display: inline-block;
width: 0;
height: 3px;
height: 2px;
background-color: @steps-nav-active-color;
transition: width 0.3s, left 0.3s;
transition-timing-function: ease-out;
@ -90,35 +90,33 @@
}
}
@media (max-width: @screen-xs) {
.@{steps-prefix-cls}-navigation {
> .@{steps-prefix-cls}-item {
margin-right: 0 !important;
&::before {
display: none;
}
&.@{steps-prefix-cls}-item-active::before {
top: 0;
right: 0;
left: unset;
display: block;
width: 3px;
height: calc(100% - 24px);
}
&::after {
position: relative;
top: -2px;
left: 50%;
display: block;
width: 8px;
height: 8px;
margin-bottom: 8px;
text-align: center;
transform: rotate(135deg);
}
> .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
visibility: hidden;
}
.@{steps-prefix-cls}-navigation.@{steps-prefix-cls}-vertical {
> .@{steps-prefix-cls}-item {
margin-right: 0 !important;
&::before {
display: none;
}
&.@{steps-prefix-cls}-item-active::before {
top: 0;
right: 0;
left: unset;
display: block;
width: 3px;
height: calc(100% - 24px);
}
&::after {
position: relative;
top: -2px;
left: 50%;
display: block;
width: 8px;
height: 8px;
margin-bottom: 8px;
text-align: center;
transform: rotate(135deg);
}
> .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
visibility: hidden;
}
}
}

View File

@ -5,10 +5,11 @@
line-height: @line-height-base;
}
&-tail {
top: 2px;
top: @steps-dot-top;
width: 100%;
margin: 0 0 0 (@steps-desciption-max-width / 2);
margin: 0 0 0 (@steps-description-max-width / 2);
padding: 0;
&::after {
width: ~'calc(100% - 20px)';
height: 3px;
@ -26,6 +27,7 @@
line-height: @steps-dot-size;
background: transparent;
border: 0;
.@{steps-prefix-cls}-icon-dot {
position: relative;
float: left;
@ -46,14 +48,19 @@
}
}
&-content {
width: @steps-desciption-max-width;
width: @steps-description-max-width;
}
&-process .@{steps-prefix-cls}-item-icon {
position: relative;
top: -1px;
width: @steps-current-dot-size;
height: @steps-current-dot-size;
line-height: @steps-current-dot-size;
.@{steps-prefix-cls}-icon-dot {
top: -1px;
background: none;
}
&-process .@{steps-prefix-cls}-icon {
&:first-child .@{steps-prefix-cls}-icon-dot {
left: 0;
}
}
}
@ -63,6 +70,7 @@
.@{steps-prefix-cls}-item-icon {
margin-top: 8px;
margin-left: 0;
background: none;
}
// https://github.com/ant-design/ant-design/issues/18354
.@{steps-prefix-cls}-item > .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
@ -74,7 +82,13 @@
.@{steps-prefix-cls}-item:first-child .@{steps-prefix-cls}-icon-dot {
left: 0;
}
.@{steps-prefix-cls}-item-process .@{steps-prefix-cls}-icon-dot {
.@{steps-prefix-cls}-item-content {
width: inherit;
}
.@{steps-prefix-cls}-item-process
.@{steps-prefix-cls}-item-container
.@{steps-prefix-cls}-item-icon
.@{steps-prefix-cls}-icon-dot {
left: -2px;
}
}

View File

@ -0,0 +1,28 @@
@progress-prefix-cls: ~'@{ant-prefix}-progress';
.@{steps-prefix-cls}-with-progress {
.@{steps-prefix-cls}-item {
padding-top: 4px;
.@{steps-prefix-cls}-item-tail {
top: 4px !important;
}
}
&.@{steps-prefix-cls}-horizontal .@{steps-prefix-cls}-item:first-child {
padding-bottom: 4px;
padding-left: 4px;
}
.@{steps-prefix-cls}-item-icon {
position: relative;
.@{progress-prefix-cls} {
position: absolute;
top: -5px;
right: -5px;
bottom: -5px;
left: -5px;
}
}
}

View File

@ -0,0 +1,251 @@
.@{steps-prefix-cls} {
&-rtl {
direction: rtl;
}
}
.@{steps-prefix-cls}-item {
&-icon {
.@{steps-prefix-cls}.@{steps-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
}
&-tail {
.@{steps-prefix-cls}-rtl & {
right: 0;
left: auto;
}
}
&-title {
.@{steps-prefix-cls}-rtl & {
padding-right: 0;
padding-left: 16px;
}
&::after {
.@{steps-prefix-cls}-rtl & {
right: 100%;
left: auto;
}
}
}
}
.@{steps-prefix-cls}-horizontal:not(.@{steps-prefix-cls}-label-vertical) {
.@{steps-prefix-cls}-item {
.@{steps-prefix-cls}-rtl& {
padding-right: 16px;
padding-left: 0;
}
&:first-child {
.@{steps-prefix-cls}-rtl& {
padding-right: 0;
}
}
&:last-child .@{steps-prefix-cls}-item-title {
.@{steps-prefix-cls}-rtl& {
padding-left: 0;
}
}
}
}
// custom-icon
.@{steps-prefix-cls}-item-custom {
.@{steps-prefix-cls}-item-icon {
> .@{steps-prefix-cls}-icon {
.@{steps-prefix-cls}-rtl & {
right: 0.5px;
left: auto;
}
}
}
}
// nav
.@{steps-prefix-cls}-navigation {
&.@{steps-prefix-cls}-small {
.@{steps-prefix-cls}-item {
&-container {
.@{steps-prefix-cls}-rtl& {
margin-right: -12px;
margin-left: 0;
}
}
}
}
.@{steps-prefix-cls}-item {
&-container {
.@{steps-prefix-cls}-rtl& {
margin-right: -16px;
margin-left: 0;
text-align: right;
}
.@{steps-prefix-cls}-item-title {
.@{steps-prefix-cls}-rtl& {
padding-left: 0;
}
}
}
&::after {
.@{steps-prefix-cls}-rtl& {
right: 100%;
left: auto;
margin-right: -2px;
margin-left: 0;
transform: rotate(225deg);
}
}
}
}
// small
.@{steps-prefix-cls}-small {
&.@{steps-prefix-cls}-horizontal:not(.@{steps-prefix-cls}-label-vertical)
.@{steps-prefix-cls}-item {
.@{steps-prefix-cls}-rtl& {
padding-right: 12px;
padding-left: 0;
}
&:first-child {
.@{steps-prefix-cls}-rtl& {
padding-right: 0;
}
}
}
.@{steps-prefix-cls}-item-title {
.@{steps-prefix-cls}-rtl& {
padding-right: 0;
padding-left: 12px;
}
}
}
// vertical
.@{steps-prefix-cls}-vertical {
> .@{steps-prefix-cls}-item {
.@{steps-prefix-cls}-item-icon {
.@{steps-prefix-cls}-rtl& {
float: right;
margin-right: 0;
margin-left: @steps-vertical-icon-width;
}
}
}
> .@{steps-prefix-cls}-item
> .@{steps-prefix-cls}-item-container
> .@{steps-prefix-cls}-item-tail {
.@{steps-prefix-cls}-rtl& {
right: @steps-vertical-tail-width;
left: auto;
}
}
&.@{steps-prefix-cls}-small .@{steps-prefix-cls}-item-container {
.@{steps-prefix-cls}-item-tail {
.@{steps-prefix-cls}-rtl& {
right: @steps-vertical-tail-width-sm;
left: auto;
}
}
}
}
// label
.@{steps-prefix-cls}-label-vertical {
.@{steps-prefix-cls}-item {
&-title {
.@{steps-prefix-cls}-rtl& {
padding-left: 0;
}
}
}
}
// progress-dot
.@{steps-prefix-cls}-dot,
.@{steps-prefix-cls}-dot.@{steps-prefix-cls}-small {
.@{steps-prefix-cls}-item {
&-tail {
.@{steps-prefix-cls}-rtl& {
margin: 0 (@steps-description-max-width / 2) 0 0;
}
&::after {
.@{steps-prefix-cls}-rtl& {
margin-right: 12px;
margin-left: 0;
}
}
}
&:first-child .@{steps-prefix-cls}-icon-dot {
.@{steps-prefix-cls}-rtl& {
right: 2px;
left: auto;
}
}
&-icon {
.@{steps-prefix-cls}-rtl& {
margin-right: 67px;
margin-left: 0;
}
.@{steps-prefix-cls}-icon-dot {
.@{steps-prefix-cls}-rtl& {
float: right;
}
/* expand hover area */
&::after {
.@{steps-prefix-cls}-rtl& {
right: -26px;
left: auto;
}
}
}
}
}
}
.@{steps-prefix-cls}-vertical.@{steps-prefix-cls}-dot {
.@{steps-prefix-cls}-item-icon {
.@{steps-prefix-cls}-rtl& {
margin-right: 0;
margin-left: 16px;
}
}
// https://github.com/ant-design/ant-design/issues/18354
.@{steps-prefix-cls}-item > .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
.@{steps-prefix-cls}-rtl& {
right: -9px;
left: auto;
}
}
.@{steps-prefix-cls}-item:first-child .@{steps-prefix-cls}-icon-dot {
.@{steps-prefix-cls}-rtl& {
right: 0;
left: auto;
}
}
.@{steps-prefix-cls}-item-process .@{steps-prefix-cls}-icon-dot {
.@{steps-prefix-cls}-rtl& {
right: -2px;
left: auto;
}
}
}
// RTL Steps with progress
.@{steps-prefix-cls}-rtl.@{steps-prefix-cls}-with-progress.@{steps-prefix-cls}-horizontal.@{steps-prefix-cls}-label-horizontal {
.@{steps-prefix-cls}-item:first-child.@{steps-prefix-cls}-item-active {
padding-right: 4px;
}
}

View File

@ -1,14 +1,16 @@
.@{steps-prefix-cls}-small {
&.@{steps-prefix-cls}-horizontal:not(.@{steps-prefix-cls}-label-vertical)
.@{steps-prefix-cls}-item {
margin-right: 12px;
&:last-child {
margin-right: 0;
padding-left: 12px;
&:first-child {
padding-left: 0;
}
}
.@{steps-prefix-cls}-item-icon {
width: @steps-small-icon-size;
height: @steps-small-icon-size;
margin: @steps-small-icon-margin;
font-size: @font-size-sm;
line-height: @steps-small-icon-size;
text-align: center;

View File

@ -1,21 +1,29 @@
.steps-vertical() {
display: block;
.@{steps-prefix-cls}-item {
.@{steps-prefix-cls}-vertical {
display: flex;
flex-direction: column;
> .@{steps-prefix-cls}-item {
display: block;
flex: 1 0 auto;
padding-left: 0;
overflow: visible;
&-icon {
.@{steps-prefix-cls}-item-icon {
float: left;
margin-right: 16px;
margin-right: @steps-vertical-icon-width;
}
&-content {
.@{steps-prefix-cls}-item-content {
display: block;
min-height: 48px;
overflow: hidden;
}
&-title {
.@{steps-prefix-cls}-item-title {
line-height: @steps-icon-size;
}
&-description {
.@{steps-prefix-cls}-item-description {
padding-bottom: 12px;
}
}
@ -25,10 +33,11 @@
> .@{steps-prefix-cls}-item-tail {
position: absolute;
top: 0;
left: 16px;
left: @steps-vertical-tail-width;
width: 1px;
height: 100%;
padding: @steps-icon-size + 6px 0 6px;
&::after {
width: 1px;
height: 100%;
@ -54,7 +63,7 @@
.@{steps-prefix-cls}-item-tail {
position: absolute;
top: 0;
left: 12px;
left: @steps-vertical-tail-width-sm;
padding: @steps-small-icon-size + 6px 0 6px;
}
.@{steps-prefix-cls}-item-title {
@ -62,13 +71,3 @@
}
}
}
.@{steps-prefix-cls}-vertical {
.steps-vertical();
}
@media (max-width: @screen-xs) {
.@{steps-prefix-cls}-horizontal.@{steps-prefix-cls}-label-horizontal {
.steps-vertical();
}
}

View File

@ -887,8 +887,28 @@
@image-preview-operation-disabled-color: fade(@image-preview-operation-color, 45%);
// Steps
// ---
@process-tail-color: @border-color-split;
@steps-nav-arrow-color: fade(@black, 25%);
@steps-background: @component-background;
@steps-icon-size: 32px;
@steps-icon-custom-size: @steps-icon-size;
@steps-icon-custom-top: 0px;
@steps-icon-custom-font-size: 24px;
@steps-icon-top: -0.5px;
@steps-icon-font-size: @font-size-lg;
@steps-icon-margin: 0 8px 0 0;
@steps-title-line-height: @height-base;
@steps-small-icon-size: 24px;
@steps-small-icon-margin: 0 8px 0 0;
@steps-dot-size: 8px;
@steps-dot-top: 2px;
@steps-current-dot-size: 10px;
@steps-description-max-width: 140px;
@steps-nav-content-max-width: auto;
@steps-vertical-icon-width: 16px;
@steps-vertical-tail-width: 16px;
@steps-vertical-tail-width-sm: 12px;
// Notification
// ---

View File

@ -1,146 +0,0 @@
import PropTypes, { withUndefined } from '../_util/vue-types';
import { getOptionProps, getComponent } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import { defineComponent } from 'vue';
function isString(str) {
return typeof str === 'string';
}
function noop() {}
export default defineComponent({
name: 'Step',
mixins: [BaseMixin],
props: {
prefixCls: PropTypes.string,
wrapperStyle: PropTypes.object,
itemWidth: PropTypes.string,
active: PropTypes.looseBool,
disabled: PropTypes.looseBool,
status: PropTypes.string,
iconPrefix: PropTypes.string,
icon: PropTypes.any,
adjustMarginRight: PropTypes.string,
stepNumber: PropTypes.string,
stepIndex: PropTypes.number,
description: PropTypes.any,
title: PropTypes.any,
subTitle: PropTypes.any,
progressDot: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func])),
tailContent: PropTypes.any,
icons: PropTypes.shape({
finish: PropTypes.any,
error: PropTypes.any,
}).loose,
onClick: PropTypes.func,
onStepClick: PropTypes.func,
},
methods: {
onItemClick(...args) {
this.__emit('click', ...args);
this.__emit('stepClick', this.stepIndex);
},
renderIconNode() {
const { prefixCls, stepNumber, status, iconPrefix, icons, progressDot } =
getOptionProps(this);
const icon = getComponent(this, 'icon');
const title = getComponent(this, 'title');
const description = getComponent(this, 'description');
let iconNode;
const iconClassName = {
[`${prefixCls}-icon`]: true,
[`${iconPrefix}icon`]: true,
[`${iconPrefix}icon-${icon}`]: icon && isString(icon),
[`${iconPrefix}icon-check`]: !icon && status === 'finish' && icons && !icons.finish,
[`${iconPrefix}icon-close`]: !icon && status === 'error' && icons && !icons.error,
};
const iconDot = <span class={`${prefixCls}-icon-dot`} />;
// `progressDot` enjoy the highest priority
if (progressDot) {
if (typeof progressDot === 'function') {
iconNode = (
<span class={`${prefixCls}-icon`}>
{progressDot({ index: stepNumber - 1, status, title, description, prefixCls })}
</span>
);
} else {
iconNode = <span class={`${prefixCls}-icon`}>{iconDot}</span>;
}
} else if (icon && !isString(icon)) {
iconNode = <span class={`${prefixCls}-icon`}>{icon}</span>;
} else if (icons && icons.finish && status === 'finish') {
iconNode = <span class={`${prefixCls}-icon`}>{icons.finish}</span>;
} else if (icons && icons.error && status === 'error') {
iconNode = <span class={`${prefixCls}-icon`}>{icons.error}</span>;
} else if (icon || status === 'finish' || status === 'error') {
iconNode = <span class={iconClassName} />;
} else {
iconNode = <span class={`${prefixCls}-icon`}>{stepNumber}</span>;
}
return iconNode;
},
},
render() {
const {
prefixCls,
itemWidth,
active,
status = 'wait',
tailContent,
adjustMarginRight,
disabled,
onClick,
onStepClick,
} = getOptionProps(this);
const title = getComponent(this, 'title');
const subTitle = getComponent(this, 'subTitle');
const description = getComponent(this, 'description');
const classString = {
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-${status}`]: true,
[`${prefixCls}-item-custom`]: getComponent(this, 'icon'),
[`${prefixCls}-item-active`]: active,
[`${prefixCls}-item-disabled`]: disabled === true,
};
const stepProps = {
class: classString,
};
const stepItemStyle = {};
if (itemWidth) {
stepItemStyle.width = itemWidth;
}
if (adjustMarginRight) {
stepItemStyle.marginRight = adjustMarginRight;
}
const accessibilityProps = {
onClick: onClick || noop,
};
if (onStepClick && !disabled) {
accessibilityProps.role = 'button';
accessibilityProps.tabindex = 0;
accessibilityProps.onClick = this.onItemClick;
}
return (
<div {...stepProps} style={stepItemStyle}>
<div {...accessibilityProps} class={`${prefixCls}-item-container`}>
<div class={`${prefixCls}-item-tail`}>{tailContent}</div>
<div class={`${prefixCls}-item-icon`}>{this.renderIconNode()}</div>
<div class={`${prefixCls}-item-content`}>
<div class={`${prefixCls}-item-title`}>
{title}
{subTitle && (
<div title={subTitle} class={`${prefixCls}-item-subtitle`}>
{subTitle}
</div>
)}
</div>
{description && <div class={`${prefixCls}-item-description`}>{description}</div>}
</div>
</div>
</div>
);
},
});

View File

@ -0,0 +1,177 @@
import PropTypes, { withUndefined } from '../_util/vue-types';
import type { CSSProperties } from 'vue';
import { defineComponent } from 'vue';
import type { EventHandler } from '../_util/EventInterface';
function isString(str: any): str is string {
return typeof str === 'string';
}
function noop() {}
export default defineComponent({
name: 'Step',
props: {
prefixCls: PropTypes.string,
wrapperStyle: PropTypes.style,
itemWidth: PropTypes.string,
active: PropTypes.looseBool,
disabled: PropTypes.looseBool,
status: PropTypes.string,
iconPrefix: PropTypes.string,
icon: PropTypes.any,
adjustMarginRight: PropTypes.string,
stepNumber: PropTypes.number,
stepIndex: PropTypes.number,
description: PropTypes.any,
title: PropTypes.any,
subTitle: PropTypes.any,
progressDot: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func])),
tailContent: PropTypes.any,
icons: PropTypes.shape({
finish: PropTypes.any,
error: PropTypes.any,
}).loose,
onClick: PropTypes.func,
onStepClick: PropTypes.func,
stepIcon: PropTypes.func,
},
slots: ['title', 'subTitle', 'description', 'tailContent', 'stepIcon', 'progressDot'],
emits: ['click', 'stepClick'],
setup(props, { slots, emit }) {
const onItemClick: EventHandler = e => {
emit('click', e);
emit('stepClick', props.stepIndex);
};
const renderIconNode = ({ icon, title, description }) => {
const {
prefixCls,
stepNumber,
status,
iconPrefix,
icons,
progressDot = slots.progressDot,
stepIcon = slots.stepIcon,
} = props;
let iconNode: any;
const iconClassName = {
[`${prefixCls}-icon`]: true,
[`${iconPrefix}icon`]: true,
[`${iconPrefix}icon-${icon}`]: icon && isString(icon),
[`${iconPrefix}icon-check`]: !icon && status === 'finish' && icons && !icons.finish,
[`${iconPrefix}icon-close`]: !icon && status === 'error' && icons && !icons.error,
};
const iconDot = <span class={`${prefixCls}-icon-dot`} />;
// `progressDot` enjoy the highest priority
if (progressDot) {
if (typeof progressDot === 'function') {
iconNode = (
<span class={`${prefixCls}-icon`}>
{progressDot({
iconDot,
index: stepNumber - 1,
status,
title,
description,
prefixCls,
})}
</span>
);
} else {
iconNode = <span class={`${prefixCls}-icon`}>{iconDot}</span>;
}
} else if (icon && !isString(icon)) {
iconNode = <span class={`${prefixCls}-icon`}>{icon}</span>;
} else if (icons && icons.finish && status === 'finish') {
iconNode = <span class={`${prefixCls}-icon`}>{icons.finish}</span>;
} else if (icons && icons.error && status === 'error') {
iconNode = <span class={`${prefixCls}-icon`}>{icons.error}</span>;
} else if (icon || status === 'finish' || status === 'error') {
iconNode = <span class={iconClassName} />;
} else {
iconNode = <span class={`${prefixCls}-icon`}>{stepNumber}</span>;
}
if (stepIcon) {
iconNode = stepIcon({
index: stepNumber - 1,
status,
title,
description,
node: iconNode,
});
}
return iconNode;
};
return () => {
const {
prefixCls,
itemWidth,
active,
status = 'wait',
tailContent,
adjustMarginRight,
disabled,
title = slots.title?.(),
description = slots.description?.(),
subTitle = slots.subTitle?.(),
icon = slots.icon?.(),
onClick,
onStepClick,
} = props;
const classString = {
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-${status}`]: true,
[`${prefixCls}-item-custom`]: icon,
[`${prefixCls}-item-active`]: active,
[`${prefixCls}-item-disabled`]: disabled === true,
};
const stepProps = {
class: classString,
};
const stepItemStyle: CSSProperties = {};
if (itemWidth) {
stepItemStyle.width = itemWidth;
}
if (adjustMarginRight) {
stepItemStyle.marginRight = adjustMarginRight;
}
const accessibilityProps: {
role?: string;
tabindex?: number;
onClick?: EventHandler;
} = {
onClick: onClick || noop,
};
if (onStepClick && !disabled) {
accessibilityProps.role = 'button';
accessibilityProps.tabindex = 0;
accessibilityProps.onClick = onItemClick;
}
return (
<div {...stepProps} style={stepItemStyle}>
<div {...accessibilityProps} class={`${prefixCls}-item-container`}>
<div class={`${prefixCls}-item-tail`}>{tailContent}</div>
<div class={`${prefixCls}-item-icon`}>
{renderIconNode({ icon, title, description })}
</div>
<div class={`${prefixCls}-item-content`}>
<div class={`${prefixCls}-item-title`}>
{title}
{subTitle && (
<div title={subTitle} class={`${prefixCls}-item-subtitle`}>
{subTitle}
</div>
)}
</div>
{description && <div class={`${prefixCls}-item-description`}>{description}</div>}
</div>
</div>
</div>
);
};
},
});

View File

@ -1,170 +0,0 @@
import PropTypes, { withUndefined } from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import debounce from 'lodash-es/debounce';
import isFlexSupported from '../_util/isFlexSupported';
import { getSlot, getPropsData } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Steps',
mixins: [BaseMixin],
props: {
type: PropTypes.string.def('default'),
prefixCls: PropTypes.string.def('rc-steps'),
iconPrefix: PropTypes.string.def('rc'),
direction: PropTypes.string.def('horizontal'),
labelPlacement: PropTypes.string.def('horizontal'),
status: PropTypes.string.def('process'),
size: PropTypes.string.def(''),
progressDot: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func])),
initial: PropTypes.number.def(0),
current: PropTypes.number.def(0),
icons: PropTypes.shape({
finish: PropTypes.any,
error: PropTypes.any,
}).loose,
canClick: PropTypes.looseBool,
},
data() {
this.calcStepOffsetWidth = debounce(this.calcStepOffsetWidth, 150);
return {
flexSupported: true,
lastStepOffsetWidth: 0,
};
},
mounted() {
this.$nextTick(() => {
this.calcStepOffsetWidth();
if (!isFlexSupported()) {
this.setState({
flexSupported: false,
});
}
});
},
updated() {
this.$nextTick(() => {
this.calcStepOffsetWidth();
});
},
beforeUnmount() {
if (this.calcTimeout) {
clearTimeout(this.calcTimeout);
}
if (this.calcStepOffsetWidth && this.calcStepOffsetWidth.cancel) {
this.calcStepOffsetWidth.cancel();
}
},
methods: {
onStepClick(next) {
const { current } = this.$props;
if (current !== next) {
this.__emit('change', next);
}
},
calcStepOffsetWidth() {
if (isFlexSupported()) {
return;
}
const { lastStepOffsetWidth } = this.$data;
// Just for IE9
const domNode = this.$refs.vcStepsRef;
if (domNode.children.length > 0) {
if (this.calcTimeout) {
clearTimeout(this.calcTimeout);
}
this.calcTimeout = setTimeout(() => {
// +1 for fit edge bug of digit width, like 35.4px
const offsetWidth = (domNode.lastChild.offsetWidth || 0) + 1;
// Reduce shake bug
if (
lastStepOffsetWidth === offsetWidth ||
Math.abs(lastStepOffsetWidth - offsetWidth) <= 3
) {
return;
}
this.setState({ lastStepOffsetWidth: offsetWidth });
});
}
},
},
render() {
const {
prefixCls,
direction,
type,
labelPlacement,
iconPrefix,
status,
size,
current,
progressDot,
initial,
icons,
canClick,
} = this;
const isNav = type === 'navigation';
const { lastStepOffsetWidth, flexSupported } = this;
const filteredChildren = getSlot(this);
const lastIndex = filteredChildren.length - 1;
const adjustedlabelPlacement = progressDot ? 'vertical' : labelPlacement;
const classString = {
[prefixCls]: true,
[`${prefixCls}-${direction}`]: true,
[`${prefixCls}-${size}`]: size,
[`${prefixCls}-label-${adjustedlabelPlacement}`]: direction === 'horizontal',
[`${prefixCls}-dot`]: !!progressDot,
[`${prefixCls}-navigation`]: isNav,
[`${prefixCls}-flex-not-supported`]: !flexSupported,
};
const stepsProps = {
class: classString,
ref: 'vcStepsRef',
};
return (
<div {...stepsProps}>
{filteredChildren.map((child, index) => {
const childProps = getPropsData(child);
const stepNumber = initial + index;
const stepProps = {
stepNumber: `${stepNumber + 1}`,
stepIndex: stepNumber,
prefixCls,
iconPrefix,
progressDot,
icons,
...childProps,
};
if (canClick) {
stepProps.onStepClick = this.onStepClick;
}
if (!flexSupported && direction !== 'vertical') {
if (isNav) {
stepProps.itemWidth = `${100 / (lastIndex + 1)}%`;
stepProps.adjustMarginRight = 0;
} else if (index !== lastIndex) {
stepProps.itemWidth = `${100 / lastIndex}%`;
stepProps.adjustMarginRight = `${-Math.round(lastStepOffsetWidth / lastIndex + 1)}px`;
}
}
// fix tail color
if (status === 'error' && index === current - 1) {
stepProps.class = `${prefixCls}-next-error`;
}
if (!childProps.status) {
if (stepNumber === current) {
stepProps.status = status;
} else if (stepNumber < current) {
stepProps.status = 'finish';
} else {
stepProps.status = 'wait';
}
}
stepProps.active = stepNumber === current;
return cloneElement(child, stepProps);
})}
</div>
);
},
});

View File

@ -0,0 +1,122 @@
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import { filterEmpty } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import { defineComponent } from 'vue';
import classNames from '../_util/classNames';
export type Status = 'error' | 'process' | 'finish' | 'wait';
export type StepIconRender = (info: {
index: number;
status: Status;
title: any;
description: any;
node: any;
}) => any;
export type ProgressDotRender = (info: {
iconDot: any;
index: number;
status: Status;
title: any;
description: any;
}) => any;
export default defineComponent({
name: 'Steps',
mixins: [BaseMixin],
props: {
type: PropTypes.string.def('default'),
prefixCls: PropTypes.string.def('vc-steps'),
iconPrefix: PropTypes.string.def('vc'),
direction: PropTypes.string.def('horizontal'),
labelPlacement: PropTypes.string.def('horizontal'),
status: PropTypes.string.def('process'),
size: PropTypes.string.def(''),
progressDot: PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func]).def(false),
initial: PropTypes.number.def(0),
current: PropTypes.number.def(0),
icons: PropTypes.shape({
finish: PropTypes.any,
error: PropTypes.any,
}).loose,
stepIcon: PropTypes.func,
},
slots: ['stepIcon', 'progressDot'],
emits: ['change'],
setup(props, { slots, emit }) {
const onStepClick = next => {
const { current } = props;
if (current !== next) {
emit('change', next);
}
};
return () => {
const {
prefixCls,
direction,
type,
labelPlacement,
iconPrefix,
status,
size,
current,
progressDot = slots.progressDot,
initial,
icons,
stepIcon = slots.stepIcon,
} = props;
const isNav = type === 'navigation';
const adjustedLabelPlacement = progressDot ? 'vertical' : labelPlacement;
const classString = classNames(prefixCls, `${prefixCls}-${direction}`, {
[`${prefixCls}-${size}`]: size,
[`${prefixCls}-label-${adjustedLabelPlacement}`]: direction === 'horizontal',
[`${prefixCls}-dot`]: !!progressDot,
[`${prefixCls}-navigation`]: isNav,
});
const children = filterEmpty(slots.default?.());
return (
<div class={classString}>
{children.map((child, index) => {
// description: PropTypes.any,
// icon: PropTypes.any,
// status: PropTypes.oneOf(tuple('wait', 'process', 'finish', 'error')),
// disabled: PropTypes.looseBool,
// title: PropTypes.any,
// subTitle: PropTypes.any,
const { prefixCls: pre = prefixCls, ...restProps } = child.props || {};
const stepNumber = initial + index;
const stepProps = {
...restProps,
stepNumber: stepNumber + 1,
stepIndex: stepNumber,
key: stepNumber,
prefixCls: pre,
iconPrefix,
progressDot,
icons,
stepIcon,
onStepClick,
};
// fix tail color
if (status === 'error' && index === current - 1) {
stepProps.class = `${prefixCls}-next-error`;
}
if (!restProps.status) {
if (stepNumber === current) {
stepProps.status = status;
} else if (stepNumber < current) {
stepProps.status = 'finish';
} else {
stepProps.status = 'wait';
}
}
stepProps.active = stepNumber === current;
return cloneElement(child, stepProps);
})}
</div>
);
};
},
});

View File

@ -124,7 +124,7 @@ module.exports = {
'ant-design-vue': path.join(__dirname, './components'),
vue$: 'vue/dist/vue.runtime.esm-bundler.js',
},
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.md'],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.md'],
},
devServer: {
historyApiFallback: {