refactor(v3/badge): use composition api (#4076)
parent
69b9f80a01
commit
372ac5c729
|
@ -1,27 +1,39 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import ScrollNumber from './ScrollNumber';
|
import ScrollNumber from './ScrollNumber';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { initDefaultProps, getComponent, getSlot } from '../_util/props-util';
|
import { getPropsSlot, flattenChildren } from '../_util/props-util';
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import { getTransitionProps, Transition } from '../_util/transition';
|
import { getTransitionProps, Transition } from '../_util/transition';
|
||||||
import isNumeric from '../_util/isNumeric';
|
import isNumeric from '../_util/isNumeric';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
import { inject, defineComponent, CSSProperties, VNode, App, Plugin } from 'vue';
|
import {
|
||||||
|
inject,
|
||||||
|
defineComponent,
|
||||||
|
ExtractPropTypes,
|
||||||
|
CSSProperties,
|
||||||
|
VNode,
|
||||||
|
App,
|
||||||
|
Plugin,
|
||||||
|
reactive,
|
||||||
|
computed,
|
||||||
|
} from 'vue';
|
||||||
import { tuple } from '../_util/type';
|
import { tuple } from '../_util/type';
|
||||||
import Ribbon from './Ribbon';
|
import Ribbon from './Ribbon';
|
||||||
import { isPresetColor } from './utils';
|
import { isPresetColor } from './utils';
|
||||||
|
|
||||||
const BadgeProps = {
|
export const badgeProps = {
|
||||||
/** Number to show in badge */
|
/** Number to show in badge */
|
||||||
count: PropTypes.VNodeChild,
|
count: PropTypes.VNodeChild,
|
||||||
showZero: PropTypes.looseBool,
|
showZero: PropTypes.looseBool,
|
||||||
/** Max count to show */
|
/** Max count to show */
|
||||||
overflowCount: PropTypes.number,
|
overflowCount: PropTypes.number.def(99),
|
||||||
/** whether to show red dot without number */
|
/** whether to show red dot without number */
|
||||||
dot: PropTypes.looseBool,
|
dot: PropTypes.looseBool,
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
scrollNumberPrefixCls: PropTypes.string,
|
scrollNumberPrefixCls: PropTypes.string,
|
||||||
status: PropTypes.oneOf(tuple('success', 'processing', 'default', 'error', 'warning')),
|
status: PropTypes.oneOf(tuple('success', 'processing', 'default', 'error', 'warning')),
|
||||||
|
// sync antd@4.6.0
|
||||||
|
size: PropTypes.oneOf(tuple('default', 'small')).def('default'),
|
||||||
color: PropTypes.string,
|
color: PropTypes.string,
|
||||||
text: PropTypes.VNodeChild,
|
text: PropTypes.VNodeChild,
|
||||||
offset: PropTypes.arrayOf(PropTypes.oneOfType([String, Number])),
|
offset: PropTypes.arrayOf(PropTypes.oneOfType([String, Number])),
|
||||||
|
@ -29,48 +41,44 @@ const BadgeProps = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BadgeProps = Partial<ExtractPropTypes<typeof badgeProps>>;
|
||||||
|
|
||||||
const Badge = defineComponent({
|
const Badge = defineComponent({
|
||||||
name: 'ABadge',
|
name: 'ABadge',
|
||||||
Ribbon,
|
Ribbon,
|
||||||
props: initDefaultProps(BadgeProps, {
|
props: badgeProps,
|
||||||
showZero: false,
|
setup(props, { slots }) {
|
||||||
dot: false,
|
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||||
overflowCount: 99,
|
const state = reactive({
|
||||||
}) as typeof BadgeProps,
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
badgeCount: undefined,
|
badgeCount: undefined,
|
||||||
};
|
});
|
||||||
},
|
|
||||||
methods: {
|
const getNumberedDispayCount = () => {
|
||||||
getNumberedDispayCount() {
|
const { overflowCount } = props;
|
||||||
const { overflowCount } = this.$props;
|
const count = state.badgeCount;
|
||||||
const count = this.badgeCount;
|
|
||||||
const displayCount = count > overflowCount ? `${overflowCount}+` : count;
|
const displayCount = count > overflowCount ? `${overflowCount}+` : count;
|
||||||
return displayCount;
|
return displayCount;
|
||||||
},
|
};
|
||||||
|
|
||||||
getDispayCount() {
|
const getDispayCount = computed(() => {
|
||||||
const isDot = this.isDot();
|
|
||||||
// dot mode don't need count
|
// dot mode don't need count
|
||||||
if (isDot) {
|
if (isDot.value) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return this.getNumberedDispayCount();
|
return getNumberedDispayCount();
|
||||||
},
|
});
|
||||||
|
|
||||||
getScrollNumberTitle() {
|
const getScrollNumberTitle = () => {
|
||||||
const { title } = this.$props;
|
const { title } = props;
|
||||||
const count = this.badgeCount;
|
const count = state.badgeCount;
|
||||||
if (title) {
|
if (title) {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
|
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
|
||||||
},
|
};
|
||||||
|
|
||||||
getStyleWithOffset() {
|
const getStyleWithOffset = () => {
|
||||||
const { offset, numberStyle } = this.$props;
|
const { offset, numberStyle } = props;
|
||||||
return offset
|
return offset
|
||||||
? {
|
? {
|
||||||
right: `${-parseInt(offset[0] as string, 10)}px`,
|
right: `${-parseInt(offset[0] as string, 10)}px`,
|
||||||
|
@ -78,47 +86,49 @@ const Badge = defineComponent({
|
||||||
...numberStyle,
|
...numberStyle,
|
||||||
}
|
}
|
||||||
: { ...numberStyle };
|
: { ...numberStyle };
|
||||||
},
|
};
|
||||||
getBadgeClassName(prefixCls: string, children: VNode[]) {
|
|
||||||
const hasStatus = this.hasStatus();
|
const hasStatus = computed(() => {
|
||||||
|
const { status, color } = props;
|
||||||
|
return !!status || !!color;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isZero = computed(() => {
|
||||||
|
const numberedDispayCount = getNumberedDispayCount();
|
||||||
|
return numberedDispayCount === '0' || numberedDispayCount === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDot = computed(() => {
|
||||||
|
const { dot } = props;
|
||||||
|
return (dot && !isZero.value) || hasStatus.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isHidden = computed(() => {
|
||||||
|
const { showZero } = props;
|
||||||
|
const isEmpty =
|
||||||
|
getDispayCount.value === null ||
|
||||||
|
getDispayCount.value === undefined ||
|
||||||
|
getDispayCount.value === '';
|
||||||
|
return (isEmpty || (isZero.value && !showZero)) && !isDot.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderStatusText = (prefixCls: string) => {
|
||||||
|
const text = getPropsSlot(slots, props, 'text');
|
||||||
|
const hidden = isHidden.value;
|
||||||
|
return hidden || !text ? null : <span class={`${prefixCls}-status-text`}>{text}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBadgeClassName = (prefixCls: string, children: VNode[]) => {
|
||||||
|
const status = hasStatus.value;
|
||||||
return classNames(prefixCls, {
|
return classNames(prefixCls, {
|
||||||
[`${prefixCls}-status`]: hasStatus,
|
[`${prefixCls}-status`]: status,
|
||||||
[`${prefixCls}-dot-status`]: hasStatus && this.dot && !this.isZero(),
|
[`${prefixCls}-dot-status`]: status && props.dot && !isZero.value,
|
||||||
[`${prefixCls}-not-a-wrapper`]: !children.length,
|
[`${prefixCls}-not-a-wrapper`]: !children.length,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
hasStatus() {
|
|
||||||
const { status, color } = this.$props;
|
|
||||||
return !!status || !!color;
|
|
||||||
},
|
|
||||||
isZero() {
|
|
||||||
const numberedDispayCount = this.getNumberedDispayCount();
|
|
||||||
return numberedDispayCount === '0' || numberedDispayCount === 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
isDot() {
|
const renderDispayComponent = () => {
|
||||||
const { dot } = this.$props;
|
const count = state.badgeCount;
|
||||||
const isZero = this.isZero();
|
|
||||||
return (dot && !isZero) || this.hasStatus();
|
|
||||||
},
|
|
||||||
|
|
||||||
isHidden() {
|
|
||||||
const { showZero } = this.$props;
|
|
||||||
const displayCount = this.getDispayCount();
|
|
||||||
const isZero = this.isZero();
|
|
||||||
const isDot = this.isDot();
|
|
||||||
const isEmpty = displayCount === null || displayCount === undefined || displayCount === '';
|
|
||||||
return (isEmpty || (isZero && !showZero)) && !isDot;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderStatusText(prefixCls: string) {
|
|
||||||
const text = getComponent(this, 'text');
|
|
||||||
const hidden = this.isHidden();
|
|
||||||
return hidden || !text ? null : <span class={`${prefixCls}-status-text`}>{text}</span>;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderDispayComponent() {
|
|
||||||
const count = this.badgeCount;
|
|
||||||
const customNode = count;
|
const customNode = count;
|
||||||
if (!customNode || typeof customNode !== 'object') {
|
if (!customNode || typeof customNode !== 'object') {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -126,103 +136,102 @@ const Badge = defineComponent({
|
||||||
return cloneElement(
|
return cloneElement(
|
||||||
customNode,
|
customNode,
|
||||||
{
|
{
|
||||||
style: this.getStyleWithOffset(),
|
style: getStyleWithOffset(),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
|
|
||||||
renderBadgeNumber(prefixCls: string, scrollNumberPrefixCls: string) {
|
const renderBadgeNumber = (prefixCls: string, scrollNumberPrefixCls: string) => {
|
||||||
const { status, color } = this.$props;
|
const { status, color, size } = props;
|
||||||
const count = this.badgeCount;
|
const count = state.badgeCount;
|
||||||
const displayCount = this.getDispayCount();
|
const displayCount = getDispayCount.value;
|
||||||
const isDot = this.isDot();
|
|
||||||
const hidden = this.isHidden();
|
|
||||||
|
|
||||||
const scrollNumberCls = {
|
const scrollNumberCls = {
|
||||||
[`${prefixCls}-dot`]: isDot,
|
[`${prefixCls}-dot`]: isDot.value,
|
||||||
[`${prefixCls}-count`]: !isDot,
|
[`${prefixCls}-count`]: !isDot.value,
|
||||||
|
[`${prefixCls}-count-sm`]: size === 'small',
|
||||||
[`${prefixCls}-multiple-words`]:
|
[`${prefixCls}-multiple-words`]:
|
||||||
!isDot && count && count.toString && count.toString().length > 1,
|
!isDot.value && count && count.toString && count.toString().length > 1,
|
||||||
[`${prefixCls}-status-${status}`]: !!status,
|
[`${prefixCls}-status-${status}`]: !!status,
|
||||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
||||||
};
|
};
|
||||||
|
|
||||||
let statusStyle = this.getStyleWithOffset();
|
let statusStyle = getStyleWithOffset();
|
||||||
if (color && !isPresetColor(color)) {
|
if (color && !isPresetColor(color)) {
|
||||||
statusStyle = statusStyle || {};
|
statusStyle = statusStyle || {};
|
||||||
statusStyle.background = color;
|
statusStyle.background = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hidden ? null : (
|
return isHidden.value ? null : (
|
||||||
<ScrollNumber
|
<ScrollNumber
|
||||||
prefixCls={scrollNumberPrefixCls}
|
prefixCls={scrollNumberPrefixCls}
|
||||||
data-show={!hidden}
|
data-show={!isHidden.value}
|
||||||
v-show={!hidden}
|
v-show={!isHidden.value}
|
||||||
class={scrollNumberCls}
|
class={scrollNumberCls}
|
||||||
count={displayCount}
|
count={displayCount}
|
||||||
displayComponent={this.renderDispayComponent()}
|
displayComponent={renderDispayComponent()}
|
||||||
title={this.getScrollNumberTitle()}
|
title={getScrollNumberTitle()}
|
||||||
style={statusStyle}
|
style={statusStyle}
|
||||||
key="scrollNumber"
|
key="scrollNumber"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
|
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
|
||||||
status,
|
status,
|
||||||
color,
|
color,
|
||||||
} = this;
|
} = props;
|
||||||
|
|
||||||
const text = getComponent(this, 'text');
|
const text = getPropsSlot(slots, props, 'text');
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
const getPrefixCls = configProvider.getPrefixCls;
|
||||||
const prefixCls = getPrefixCls('badge', customizePrefixCls);
|
const prefixCls = getPrefixCls('badge', customizePrefixCls);
|
||||||
const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls);
|
const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls);
|
||||||
|
|
||||||
const children = getSlot(this);
|
const children = flattenChildren(slots.default?.());
|
||||||
let count = getComponent(this, 'count');
|
let count = getPropsSlot(slots, props, 'count');
|
||||||
if (Array.isArray(count)) {
|
if (Array.isArray(count)) {
|
||||||
count = count[0];
|
count = count[0];
|
||||||
}
|
}
|
||||||
this.badgeCount = count;
|
state.badgeCount = count;
|
||||||
const scrollNumber = this.renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
|
const scrollNumber = renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
|
||||||
const statusText = this.renderStatusText(prefixCls);
|
const statusText = renderStatusText(prefixCls);
|
||||||
const statusCls = classNames({
|
const statusCls = classNames({
|
||||||
[`${prefixCls}-status-dot`]: this.hasStatus(),
|
[`${prefixCls}-status-dot`]: hasStatus.value,
|
||||||
[`${prefixCls}-status-${status}`]: !!status,
|
[`${prefixCls}-status-${status}`]: !!status,
|
||||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
||||||
});
|
});
|
||||||
const statusStyle: CSSProperties = {};
|
const statusStyle: CSSProperties = {};
|
||||||
if (color && !isPresetColor(color)) {
|
if (color && !isPresetColor(color)) {
|
||||||
statusStyle.background = color;
|
statusStyle.background = color;
|
||||||
}
|
}
|
||||||
// <Badge status="success" />
|
// <Badge status="success" />
|
||||||
if (!children.length && this.hasStatus()) {
|
if (!children.length && hasStatus.value) {
|
||||||
const styleWithOffset = this.getStyleWithOffset();
|
const styleWithOffset = getStyleWithOffset();
|
||||||
const statusTextColor = styleWithOffset && styleWithOffset.color;
|
const statusTextColor = styleWithOffset && styleWithOffset.color;
|
||||||
return (
|
return (
|
||||||
<span class={this.getBadgeClassName(prefixCls, children)} style={styleWithOffset}>
|
<span class={getBadgeClassName(prefixCls, children)} style={styleWithOffset}>
|
||||||
<span class={statusCls} style={statusStyle} />
|
<span class={statusCls} style={statusStyle} />
|
||||||
<span style={{ color: statusTextColor }} class={`${prefixCls}-status-text`}>
|
<span style={{ color: statusTextColor }} class={`${prefixCls}-status-text`}>
|
||||||
{text}
|
{text}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionProps = getTransitionProps(children.length ? `${prefixCls}-zoom` : '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span class={getBadgeClassName(prefixCls, children)}>
|
||||||
|
{children}
|
||||||
|
<Transition {...transitionProps}>{scrollNumber}</Transition>
|
||||||
|
{statusText}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const transitionProps = getTransitionProps(children.length ? `${prefixCls}-zoom` : '');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span class={this.getBadgeClassName(prefixCls, children)}>
|
|
||||||
{children}
|
|
||||||
<Transition {...transitionProps}>{scrollNumber}</Transition>
|
|
||||||
{statusText}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
import { omit } from 'lodash-es';
|
||||||
import omit from 'omit.js';
|
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
import { CSSProperties, defineComponent, inject } from 'vue';
|
import {
|
||||||
|
defineComponent,
|
||||||
|
inject,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onUpdated,
|
||||||
|
reactive,
|
||||||
|
watch,
|
||||||
|
ExtractPropTypes,
|
||||||
|
CSSProperties,
|
||||||
|
DefineComponent,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
function getNumberArray(num: string | number | undefined | null) {
|
function getNumberArray(num: string | number | undefined | null) {
|
||||||
return num
|
return num
|
||||||
|
@ -19,7 +29,7 @@ function getNumberArray(num: string | number | undefined | null) {
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScrollNumberProps = {
|
export const scrollNumberProps = {
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
count: PropTypes.any,
|
count: PropTypes.any,
|
||||||
component: PropTypes.string,
|
component: PropTypes.string,
|
||||||
|
@ -28,68 +38,30 @@ const ScrollNumberProps = {
|
||||||
onAnimated: PropTypes.func,
|
onAnimated: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ScrollNumberProps = ExtractPropTypes<typeof scrollNumberProps>;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ScrollNumber',
|
name: 'ScrollNumber',
|
||||||
mixins: [BaseMixin],
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: ScrollNumberProps,
|
props: scrollNumberProps,
|
||||||
emits: ['animated'],
|
emits: ['animated'],
|
||||||
setup() {
|
setup(props, { emit, attrs }) {
|
||||||
return {
|
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
const state = reactive({
|
||||||
lastCount: undefined,
|
|
||||||
timeout: undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
animateStarted: true,
|
animateStarted: true,
|
||||||
sCount: this.count,
|
lastCount: undefined,
|
||||||
};
|
sCount: props.count,
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
count() {
|
|
||||||
this.lastCount = this.sCount;
|
|
||||||
this.setState({
|
|
||||||
animateStarted: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
const { animateStarted, count } = this;
|
|
||||||
if (animateStarted) {
|
|
||||||
this.clearTimeout();
|
|
||||||
// Let browser has time to reset the scroller before actually
|
|
||||||
// performing the transition.
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
animateStarted: false,
|
|
||||||
sCount: count,
|
|
||||||
},
|
|
||||||
this.handleAnimated,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.clearTimeout();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clearTimeout() {
|
|
||||||
if (this.timeout) {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
this.timeout = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getPositionByNum(num: number, i: number) {
|
|
||||||
const { sCount } = this;
|
|
||||||
const currentCount = Math.abs(Number(sCount));
|
|
||||||
const lastCount = Math.abs(Number(this.lastCount));
|
|
||||||
const currentDigit = Math.abs(getNumberArray(sCount)[i] as number);
|
|
||||||
const lastDigit = Math.abs(getNumberArray(this.lastCount)[i] as number);
|
|
||||||
|
|
||||||
if (this.animateStarted) {
|
timeout: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPositionByNum = (num: number, i: number) => {
|
||||||
|
const currentCount = Math.abs(Number(state.sCount));
|
||||||
|
const lastCount = Math.abs(Number(state.lastCount));
|
||||||
|
const currentDigit = Math.abs(getNumberArray(state.sCount)[i] as number);
|
||||||
|
const lastDigit = Math.abs(getNumberArray(state.lastCount)[i] as number);
|
||||||
|
|
||||||
|
if (state.animateStarted) {
|
||||||
return 10 + num;
|
return 10 + num;
|
||||||
}
|
}
|
||||||
// 同方向则在同一侧切换数字
|
// 同方向则在同一侧切换数字
|
||||||
|
@ -103,12 +75,19 @@ export default defineComponent({
|
||||||
return 10 + num;
|
return 10 + num;
|
||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
},
|
};
|
||||||
handleAnimated() {
|
const handleAnimated = () => {
|
||||||
this.$emit('animated');
|
emit('animated');
|
||||||
},
|
};
|
||||||
|
|
||||||
renderNumberList(position: number, className: string) {
|
const _clearTimeout = () => {
|
||||||
|
if (state.timeout) {
|
||||||
|
clearTimeout(state.timeout);
|
||||||
|
state.timeout = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNumberList = (position: number, className: string) => {
|
||||||
const childrenToReturn = [];
|
const childrenToReturn = [];
|
||||||
for (let i = 0; i < 30; i++) {
|
for (let i = 0; i < 30; i++) {
|
||||||
childrenToReturn.push(
|
childrenToReturn.push(
|
||||||
|
@ -122,14 +101,14 @@ export default defineComponent({
|
||||||
</p>,
|
</p>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return childrenToReturn;
|
return childrenToReturn;
|
||||||
},
|
};
|
||||||
renderCurrentNumber(prefixCls: string, num: number | string, i: number) {
|
|
||||||
|
const renderCurrentNumber = (prefixCls: string, num: number | string, i: number) => {
|
||||||
if (typeof num === 'number') {
|
if (typeof num === 'number') {
|
||||||
const position = this.getPositionByNum(num, i);
|
const position = getPositionByNum(num, i);
|
||||||
const removeTransition =
|
const removeTransition =
|
||||||
this.animateStarted || getNumberArray(this.lastCount)[i] === undefined;
|
state.animateStarted || getNumberArray(state.lastCount)[i] === undefined;
|
||||||
const style = {
|
const style = {
|
||||||
transition: removeTransition ? 'none' : undefined,
|
transition: removeTransition ? 'none' : undefined,
|
||||||
msTransform: `translateY(${-position * 100}%)`,
|
msTransform: `translateY(${-position * 100}%)`,
|
||||||
|
@ -138,7 +117,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<span class={`${prefixCls}-only`} style={style} key={i}>
|
<span class={`${prefixCls}-only`} style={style} key={i}>
|
||||||
{this.renderNumberList(position, `${prefixCls}-only-unit`)}
|
{renderNumberList(position, `${prefixCls}-only-unit`)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -147,57 +126,92 @@ export default defineComponent({
|
||||||
{num}
|
{num}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
|
|
||||||
renderNumberElement(prefixCls: string) {
|
const renderNumberElement = (prefixCls: string) => {
|
||||||
const { sCount } = this;
|
if (state.sCount && Number(state.sCount) % 1 === 0) {
|
||||||
if (sCount && Number(sCount) % 1 === 0) {
|
return getNumberArray(state.sCount)
|
||||||
return getNumberArray(sCount)
|
.map((num, i) => renderCurrentNumber(prefixCls, num, i))
|
||||||
.map((num, i) => this.renderCurrentNumber(prefixCls, num, i))
|
|
||||||
.reverse();
|
.reverse();
|
||||||
}
|
}
|
||||||
return sCount;
|
return state.sCount;
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { prefixCls: customizePrefixCls, title, component: Tag = 'sup', displayComponent } = this;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('scroll-number', customizePrefixCls);
|
|
||||||
const { class: className, style = {} } = this.$attrs as {
|
|
||||||
class?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
};
|
};
|
||||||
if (displayComponent) {
|
|
||||||
return cloneElement(displayComponent, {
|
|
||||||
class: classNames(
|
|
||||||
`${prefixCls}-custom-component`,
|
|
||||||
displayComponent.props && displayComponent.props.class,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// fix https://fb.me/react-unknown-prop
|
|
||||||
const restProps = omit({ ...this.$props, ...this.$attrs }, [
|
|
||||||
'count',
|
|
||||||
'onAnimated',
|
|
||||||
'component',
|
|
||||||
'prefixCls',
|
|
||||||
'displayComponent',
|
|
||||||
]);
|
|
||||||
const tempStyle = { ...style };
|
|
||||||
const newProps = {
|
|
||||||
...restProps,
|
|
||||||
title,
|
|
||||||
style: tempStyle,
|
|
||||||
class: classNames(prefixCls, className),
|
|
||||||
};
|
|
||||||
// allow specify the border
|
|
||||||
// mock border-color by box-shadow for compatible with old usage:
|
|
||||||
// <Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />
|
|
||||||
if (style && style.borderColor) {
|
|
||||||
newProps.style.boxShadow = `0 0 0 1px ${style.borderColor} inset`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Tag {...newProps}>{this.renderNumberElement(prefixCls)}</Tag>;
|
watch(
|
||||||
|
() => props.count,
|
||||||
|
() => {
|
||||||
|
state.lastCount = state.sCount;
|
||||||
|
state.animateStarted = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
if (state.animateStarted) {
|
||||||
|
_clearTimeout();
|
||||||
|
// Let browser has time to reset the scroller before actually
|
||||||
|
// performing the transition.
|
||||||
|
state.timeout = setTimeout(() => {
|
||||||
|
state.animateStarted = false;
|
||||||
|
state.sCount = props.count;
|
||||||
|
nextTick(() => {
|
||||||
|
handleAnimated();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
_clearTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
|
// configProvider: inject('configProvider', defaultConfigProvider),
|
||||||
|
// lastCount: undefined,
|
||||||
|
// timeout: undefined,
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
title,
|
||||||
|
component: Tag = ('sup' as unknown) as DefineComponent,
|
||||||
|
displayComponent,
|
||||||
|
} = props;
|
||||||
|
const getPrefixCls = configProvider.getPrefixCls;
|
||||||
|
const prefixCls = getPrefixCls('scroll-number', customizePrefixCls);
|
||||||
|
const { class: className, style = {} } = attrs as {
|
||||||
|
class?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
};
|
||||||
|
if (displayComponent) {
|
||||||
|
return cloneElement(displayComponent, {
|
||||||
|
class: classNames(
|
||||||
|
`${prefixCls}-custom-component`,
|
||||||
|
displayComponent.props && displayComponent.props.class,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// fix https://fb.me/react-unknown-prop
|
||||||
|
const restProps = omit({ ...props, ...attrs }, [
|
||||||
|
'count',
|
||||||
|
'onAnimated',
|
||||||
|
'component',
|
||||||
|
'prefixCls',
|
||||||
|
'displayComponent',
|
||||||
|
]);
|
||||||
|
const tempStyle = { ...style };
|
||||||
|
const newProps = {
|
||||||
|
...restProps,
|
||||||
|
title,
|
||||||
|
style: tempStyle,
|
||||||
|
class: classNames(prefixCls, className),
|
||||||
|
};
|
||||||
|
// allow specify the border
|
||||||
|
// mock border-color by box-shadow for compatible with old usage:
|
||||||
|
// <Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />
|
||||||
|
if (style && style.borderColor) {
|
||||||
|
newProps.style.boxShadow = `0 0 0 1px ${style.borderColor} inset`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Tag {...newProps}>{renderNumberElement(prefixCls)}</Tag>;
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-count-sm {
|
||||||
|
min-width: @badge-height-sm;
|
||||||
|
height: @badge-height-sm;
|
||||||
|
padding: 0;
|
||||||
|
font-size: @badge-font-size-sm;
|
||||||
|
line-height: @badge-height-sm;
|
||||||
|
border-radius: (@badge-height-sm / 2);
|
||||||
|
}
|
||||||
|
|
||||||
&-multiple-words {
|
&-multiple-words {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -581,11 +581,14 @@
|
||||||
// Badge
|
// Badge
|
||||||
// ---
|
// ---
|
||||||
@badge-height: 20px;
|
@badge-height: 20px;
|
||||||
|
@badge-height-sm: 14px;
|
||||||
@badge-dot-size: 6px;
|
@badge-dot-size: 6px;
|
||||||
@badge-font-size: @font-size-sm;
|
@badge-font-size: @font-size-sm;
|
||||||
|
@badge-font-size-sm: @font-size-sm;
|
||||||
@badge-font-weight: normal;
|
@badge-font-weight: normal;
|
||||||
@badge-status-size: 6px;
|
@badge-status-size: 6px;
|
||||||
@badge-text-color: @component-background;
|
@badge-text-color: @component-background;
|
||||||
|
@badge-color: @highlight-color;
|
||||||
|
|
||||||
// Rate
|
// Rate
|
||||||
// ---
|
// ---
|
||||||
|
|
Loading…
Reference in New Issue