refactor: badge
parent
372ac5c729
commit
e442b0d1ec
|
@ -4,26 +4,16 @@ import classNames from '../_util/classNames';
|
||||||
import { getPropsSlot, flattenChildren } 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 { defineComponent, ExtractPropTypes, CSSProperties, computed, ref, watch } from 'vue';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
|
||||||
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';
|
||||||
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
import isNumeric from '../_util/isNumeric';
|
||||||
|
|
||||||
export const badgeProps = {
|
export const badgeProps = {
|
||||||
/** Number to show in badge */
|
/** Number to show in badge */
|
||||||
count: PropTypes.VNodeChild,
|
count: PropTypes.any,
|
||||||
showZero: PropTypes.looseBool,
|
showZero: PropTypes.looseBool,
|
||||||
/** Max count to show */
|
/** Max count to show */
|
||||||
overflowCount: PropTypes.number.def(99),
|
overflowCount: PropTypes.number.def(99),
|
||||||
|
@ -43,205 +33,189 @@ export const badgeProps = {
|
||||||
|
|
||||||
export type BadgeProps = Partial<ExtractPropTypes<typeof badgeProps>>;
|
export type BadgeProps = Partial<ExtractPropTypes<typeof badgeProps>>;
|
||||||
|
|
||||||
const Badge = defineComponent({
|
export default defineComponent({
|
||||||
name: 'ABadge',
|
name: 'ABadge',
|
||||||
Ribbon,
|
Ribbon,
|
||||||
props: badgeProps,
|
props: badgeProps,
|
||||||
setup(props, { slots }) {
|
slots: ['text', 'count'],
|
||||||
const configProvider = inject('configProvider', defaultConfigProvider);
|
setup(props, { slots, attrs }) {
|
||||||
const state = reactive({
|
const { prefixCls, direction } = useConfigInject('badge', props);
|
||||||
badgeCount: undefined,
|
|
||||||
|
// ================================ Misc ================================
|
||||||
|
const numberedDisplayCount = computed(() => {
|
||||||
|
return ((props.count as number) > (props.overflowCount as number)
|
||||||
|
? `${props.overflowCount}+`
|
||||||
|
: props.count) as string | number | null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getNumberedDispayCount = () => {
|
const hasStatus = computed(
|
||||||
const { overflowCount } = props;
|
() =>
|
||||||
const count = state.badgeCount;
|
(props.status !== null && props.status !== undefined) ||
|
||||||
const displayCount = count > overflowCount ? `${overflowCount}+` : count;
|
(props.color !== null && props.color !== undefined),
|
||||||
return displayCount;
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const getDispayCount = computed(() => {
|
const isZero = computed(
|
||||||
// dot mode don't need count
|
() => numberedDisplayCount.value === '0' || numberedDisplayCount.value === 0,
|
||||||
if (isDot.value) {
|
);
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return getNumberedDispayCount();
|
|
||||||
});
|
|
||||||
|
|
||||||
const getScrollNumberTitle = () => {
|
const showAsDot = computed(() => (props.dot && !isZero.value) || hasStatus.value);
|
||||||
const { title } = props;
|
|
||||||
const count = state.badgeCount;
|
|
||||||
if (title) {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyleWithOffset = () => {
|
const mergedCount = computed(() => (showAsDot.value ? '' : numberedDisplayCount.value));
|
||||||
const { offset, numberStyle } = props;
|
|
||||||
return offset
|
|
||||||
? {
|
|
||||||
right: `${-parseInt(offset[0] as string, 10)}px`,
|
|
||||||
marginTop: isNumeric(offset[1]) ? `${offset[1]}px` : offset[1],
|
|
||||||
...numberStyle,
|
|
||||||
}
|
|
||||||
: { ...numberStyle };
|
|
||||||
};
|
|
||||||
|
|
||||||
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 isHidden = computed(() => {
|
||||||
const { showZero } = props;
|
|
||||||
const isEmpty =
|
const isEmpty =
|
||||||
getDispayCount.value === null ||
|
mergedCount.value === null || mergedCount.value === undefined || mergedCount.value === '';
|
||||||
getDispayCount.value === undefined ||
|
return (isEmpty || (isZero.value && !props.showZero)) && !showAsDot.value;
|
||||||
getDispayCount.value === '';
|
|
||||||
return (isEmpty || (isZero.value && !showZero)) && !isDot.value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderStatusText = (prefixCls: string) => {
|
// Count should be cache in case hidden change it
|
||||||
const text = getPropsSlot(slots, props, 'text');
|
const livingCount = ref(props.count);
|
||||||
const hidden = isHidden.value;
|
|
||||||
return hidden || !text ? null : <span class={`${prefixCls}-status-text`}>{text}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBadgeClassName = (prefixCls: string, children: VNode[]) => {
|
// We need cache count since remove motion should not change count display
|
||||||
const status = hasStatus.value;
|
const displayCount = ref(mergedCount.value);
|
||||||
return classNames(prefixCls, {
|
|
||||||
[`${prefixCls}-status`]: status,
|
|
||||||
[`${prefixCls}-dot-status`]: status && props.dot && !isZero.value,
|
|
||||||
[`${prefixCls}-not-a-wrapper`]: !children.length,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDispayComponent = () => {
|
// We will cache the dot status to avoid shaking on leaved motion
|
||||||
const count = state.badgeCount;
|
const isDotRef = ref(showAsDot.value);
|
||||||
const customNode = count;
|
|
||||||
if (!customNode || typeof customNode !== 'object') {
|
watch(
|
||||||
return undefined;
|
[() => props.count, mergedCount, showAsDot],
|
||||||
|
() => {
|
||||||
|
if (!isHidden.value) {
|
||||||
|
livingCount.value = props.count;
|
||||||
|
displayCount.value = mergedCount.value;
|
||||||
|
isDotRef.value = showAsDot.value;
|
||||||
}
|
}
|
||||||
return cloneElement(
|
},
|
||||||
customNode,
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Shared styles
|
||||||
|
const statusCls = computed(() => ({
|
||||||
|
[`${prefixCls.value}-status-dot`]: hasStatus.value,
|
||||||
|
[`${prefixCls.value}-status-${props.status}`]: !!props.status,
|
||||||
|
[`${prefixCls.value}-status-${props.color}`]: isPresetColor(props.color),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const statusStyle = computed(() => {
|
||||||
|
if (props.color && !isPresetColor(props.color)) {
|
||||||
|
return { background: props.color };
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const scrollNumberCls = computed(() => ({
|
||||||
|
[`${prefixCls.value}-dot`]: isDotRef.value,
|
||||||
|
[`${prefixCls.value}-count`]: !isDotRef.value,
|
||||||
|
[`${prefixCls.value}-count-sm`]: props.size === 'small',
|
||||||
|
[`${prefixCls.value}-multiple-words`]:
|
||||||
|
!isDotRef.value && displayCount.value && displayCount.value.toString().length > 1,
|
||||||
|
[`${prefixCls.value}-status-${status}`]: !!status,
|
||||||
|
[`${prefixCls.value}-status-${props.color}`]: isPresetColor(props.color),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const { offset, title, color } = props;
|
||||||
|
const style = attrs.style as CSSProperties;
|
||||||
|
const text = getPropsSlot(slots, props, 'text');
|
||||||
|
const pre = prefixCls.value;
|
||||||
|
const count = livingCount.value;
|
||||||
|
let children = flattenChildren(slots.default?.());
|
||||||
|
children = children.length ? children : null;
|
||||||
|
|
||||||
|
const visible = !!(!isHidden.value || slots.count);
|
||||||
|
|
||||||
|
// =============================== Styles ===============================
|
||||||
|
const mergedStyle = (() => {
|
||||||
|
if (!offset) {
|
||||||
|
return { ...style };
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetStyle: CSSProperties = {
|
||||||
|
marginTop: isNumeric(offset[1]) ? `${offset[1]}px` : offset[1],
|
||||||
|
};
|
||||||
|
if (direction.value === 'rtl') {
|
||||||
|
offsetStyle.left = `${parseInt(offset[0] as string, 10)}px`;
|
||||||
|
} else {
|
||||||
|
offsetStyle.right = `${-parseInt(offset[0] as string, 10)}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...offsetStyle,
|
||||||
|
...style,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// =============================== Render ===============================
|
||||||
|
// >>> Title
|
||||||
|
const titleNode =
|
||||||
|
title ?? (typeof count === 'string' || typeof count === 'number' ? count : undefined);
|
||||||
|
|
||||||
|
// >>> Status Text
|
||||||
|
const statusTextNode =
|
||||||
|
visible || !text ? null : <span class={`${pre}-status-text`}>{text}</span>;
|
||||||
|
|
||||||
|
// >>> Display Component
|
||||||
|
const displayNode = cloneElement(
|
||||||
|
slots.count?.(),
|
||||||
{
|
{
|
||||||
style: getStyleWithOffset(),
|
style: mergedStyle,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const renderBadgeNumber = (prefixCls: string, scrollNumberPrefixCls: string) => {
|
const badgeClassName = classNames(
|
||||||
const { status, color, size } = props;
|
pre,
|
||||||
const count = state.badgeCount;
|
{
|
||||||
const displayCount = getDispayCount.value;
|
[`${pre}-status`]: hasStatus.value,
|
||||||
|
[`${pre}-not-a-wrapper`]: !children,
|
||||||
const scrollNumberCls = {
|
[`${pre}-rtl`]: direction.value === 'rtl',
|
||||||
[`${prefixCls}-dot`]: isDot.value,
|
},
|
||||||
[`${prefixCls}-count`]: !isDot.value,
|
attrs.class,
|
||||||
[`${prefixCls}-count-sm`]: size === 'small',
|
|
||||||
[`${prefixCls}-multiple-words`]:
|
|
||||||
!isDot.value && count && count.toString && count.toString().length > 1,
|
|
||||||
[`${prefixCls}-status-${status}`]: !!status,
|
|
||||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
|
||||||
};
|
|
||||||
|
|
||||||
let statusStyle = getStyleWithOffset();
|
|
||||||
if (color && !isPresetColor(color)) {
|
|
||||||
statusStyle = statusStyle || {};
|
|
||||||
statusStyle.background = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isHidden.value ? null : (
|
|
||||||
<ScrollNumber
|
|
||||||
prefixCls={scrollNumberPrefixCls}
|
|
||||||
data-show={!isHidden.value}
|
|
||||||
v-show={!isHidden.value}
|
|
||||||
class={scrollNumberCls}
|
|
||||||
count={displayCount}
|
|
||||||
displayComponent={renderDispayComponent()}
|
|
||||||
title={getScrollNumberTitle()}
|
|
||||||
style={statusStyle}
|
|
||||||
key="scrollNumber"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const {
|
|
||||||
prefixCls: customizePrefixCls,
|
|
||||||
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
|
|
||||||
status,
|
|
||||||
color,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const text = getPropsSlot(slots, props, 'text');
|
|
||||||
const getPrefixCls = configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('badge', customizePrefixCls);
|
|
||||||
const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls);
|
|
||||||
|
|
||||||
const children = flattenChildren(slots.default?.());
|
|
||||||
let count = getPropsSlot(slots, props, 'count');
|
|
||||||
if (Array.isArray(count)) {
|
|
||||||
count = count[0];
|
|
||||||
}
|
|
||||||
state.badgeCount = count;
|
|
||||||
const scrollNumber = renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
|
|
||||||
const statusText = renderStatusText(prefixCls);
|
|
||||||
const statusCls = classNames({
|
|
||||||
[`${prefixCls}-status-dot`]: hasStatus.value,
|
|
||||||
[`${prefixCls}-status-${status}`]: !!status,
|
|
||||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
|
||||||
});
|
|
||||||
const statusStyle: CSSProperties = {};
|
|
||||||
if (color && !isPresetColor(color)) {
|
|
||||||
statusStyle.background = color;
|
|
||||||
}
|
|
||||||
// <Badge status="success" />
|
// <Badge status="success" />
|
||||||
if (!children.length && hasStatus.value) {
|
if (!children && hasStatus.value) {
|
||||||
const styleWithOffset = getStyleWithOffset();
|
const statusTextColor = mergedStyle.color;
|
||||||
const statusTextColor = styleWithOffset && styleWithOffset.color;
|
|
||||||
return (
|
return (
|
||||||
<span class={getBadgeClassName(prefixCls, children)} style={styleWithOffset}>
|
<span {...attrs} class={badgeClassName} style={mergedStyle}>
|
||||||
<span class={statusCls} style={statusStyle} />
|
<span class={statusCls.value} style={statusStyle.value} />
|
||||||
<span style={{ color: statusTextColor }} class={`${prefixCls}-status-text`}>
|
<span style={{ color: statusTextColor }} class={`${pre}-status-text`}>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitionProps = getTransitionProps(children.length ? `${prefixCls}-zoom` : '');
|
const transitionProps = getTransitionProps(children ? `${pre}-zoom` : '', {
|
||||||
|
appear: false,
|
||||||
|
});
|
||||||
|
let scrollNumberStyle: CSSProperties = { ...mergedStyle, ...props.numberStyle };
|
||||||
|
if (color && !isPresetColor(color)) {
|
||||||
|
scrollNumberStyle = scrollNumberStyle || {};
|
||||||
|
scrollNumberStyle.background = color;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span class={getBadgeClassName(prefixCls, children)}>
|
<span {...attrs} class={badgeClassName}>
|
||||||
{children}
|
{children}
|
||||||
<Transition {...transitionProps}>{scrollNumber}</Transition>
|
<Transition {...transitionProps}>
|
||||||
{statusText}
|
<ScrollNumber
|
||||||
|
v-show={visible}
|
||||||
|
prefixCls={props.scrollNumberPrefixCls}
|
||||||
|
show={visible}
|
||||||
|
class={scrollNumberCls.value}
|
||||||
|
count={displayCount.value}
|
||||||
|
title={titleNode}
|
||||||
|
style={scrollNumberStyle}
|
||||||
|
key="scrollNumber"
|
||||||
|
>
|
||||||
|
{displayNode}
|
||||||
|
</ScrollNumber>
|
||||||
|
</Transition>
|
||||||
|
{statusTextNode}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Badge.install = function(app: App) {
|
|
||||||
app.component(Badge.name, Badge);
|
|
||||||
app.component(Badge.Ribbon.displayName, Badge.Ribbon);
|
|
||||||
return app;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Badge as typeof Badge &
|
|
||||||
Plugin & {
|
|
||||||
readonly Ribbon: typeof Ribbon;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,60 +1,55 @@
|
||||||
import { LiteralUnion, tuple } from '../_util/type';
|
import { LiteralUnion, tuple } from '../_util/type';
|
||||||
import { PresetColorType } from '../_util/colors';
|
import { PresetColorType } from '../_util/colors';
|
||||||
import { isPresetColor } from './utils';
|
import { isPresetColor } from './utils';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import { CSSProperties, defineComponent, PropType, ExtractPropTypes, computed } from 'vue';
|
||||||
import { HTMLAttributes, FunctionalComponent, VNodeTypes, inject, CSSProperties } from 'vue';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
|
||||||
type RibbonPlacement = 'start' | 'end';
|
const ribbonProps = {
|
||||||
|
prefix: PropTypes.string,
|
||||||
|
color: { type: String as PropType<LiteralUnion<PresetColorType, string>> },
|
||||||
|
text: PropTypes.any,
|
||||||
|
placement: PropTypes.oneOf(tuple('start', 'end')).def('end'),
|
||||||
|
};
|
||||||
|
|
||||||
export interface RibbonProps extends HTMLAttributes {
|
export type RibbonProps = Partial<ExtractPropTypes<typeof ribbonProps>>;
|
||||||
prefixCls?: string;
|
|
||||||
text?: VNodeTypes;
|
|
||||||
color?: LiteralUnion<PresetColorType, string>;
|
|
||||||
placement?: RibbonPlacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ribbon: FunctionalComponent<RibbonProps> = (props, { attrs, slots }) => {
|
export default defineComponent({
|
||||||
const { prefixCls: customizePrefixCls, color, text = slots.text?.(), placement = 'end' } = props;
|
name: 'ABadgeRibbon',
|
||||||
const { class: className, style } = attrs;
|
inheritAttrs: false,
|
||||||
const children = slots.default?.();
|
props: ribbonProps,
|
||||||
const { getPrefixCls, direction } = inject('configProvider', defaultConfigProvider);
|
slots: ['text'],
|
||||||
|
setup(props, { attrs, slots }) {
|
||||||
const prefixCls = getPrefixCls('ribbon', customizePrefixCls);
|
const { prefixCls, direction } = useConfigInject('ribbon', props);
|
||||||
const colorInPreset = isPresetColor(color);
|
const colorInPreset = computed(() => isPresetColor(props.color));
|
||||||
const ribbonCls = [
|
const ribbonCls = computed(() => [
|
||||||
prefixCls,
|
prefixCls.value,
|
||||||
`${prefixCls}-placement-${placement}`,
|
`${prefixCls.value}-placement-${props.placement}`,
|
||||||
{
|
{
|
||||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||||
[`${prefixCls}-color-${color}`]: colorInPreset,
|
[`${prefixCls.value}-color-${props.color}`]: colorInPreset.value,
|
||||||
},
|
},
|
||||||
className,
|
]);
|
||||||
];
|
return () => {
|
||||||
|
const { class: className, style, ...restAttrs } = attrs;
|
||||||
const colorStyle: CSSProperties = {};
|
const colorStyle: CSSProperties = {};
|
||||||
const cornerColorStyle: CSSProperties = {};
|
const cornerColorStyle: CSSProperties = {};
|
||||||
if (color && !colorInPreset) {
|
if (props.color && !colorInPreset.value) {
|
||||||
colorStyle.background = color;
|
colorStyle.background = props.color;
|
||||||
cornerColorStyle.color = color;
|
cornerColorStyle.color = props.color;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div class={`${prefixCls}-wrapper`}>
|
<div class={`${prefixCls.value}-wrapper`} {...restAttrs}>
|
||||||
{children}
|
{slots.default?.()}
|
||||||
<div class={ribbonCls} style={{ ...colorStyle, ...(style as CSSProperties) }}>
|
<div
|
||||||
<span class={`${prefixCls}-text`}>{text}</span>
|
class={[ribbonCls.value, className]}
|
||||||
<div class={`${prefixCls}-corner`} style={cornerColorStyle} />
|
style={{ ...colorStyle, ...(style as CSSProperties) }}
|
||||||
|
>
|
||||||
|
<span class={`${prefixCls.value}-text`}>{props.text || slots.text?.()}</span>
|
||||||
|
<div class={`${prefixCls.value}-corner`} style={cornerColorStyle} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
},
|
||||||
Ribbon.displayName = 'ABadgeRibbon';
|
});
|
||||||
Ribbon.inheritAttrs = false;
|
|
||||||
Ribbon.props = {
|
|
||||||
prefix: PropTypes.string,
|
|
||||||
color: PropTypes.string,
|
|
||||||
text: PropTypes.any,
|
|
||||||
placement: PropTypes.oneOf(tuple('start', 'end')),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Ribbon;
|
|
||||||
|
|
|
@ -1,217 +1,90 @@
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { omit } from 'lodash-es';
|
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onUpdated,
|
|
||||||
reactive,
|
|
||||||
watch,
|
|
||||||
ExtractPropTypes,
|
ExtractPropTypes,
|
||||||
CSSProperties,
|
CSSProperties,
|
||||||
DefineComponent,
|
DefineComponent,
|
||||||
|
HTMLAttributes,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
function getNumberArray(num: string | number | undefined | null) {
|
import SingleNumber from './SingleNumber';
|
||||||
return num
|
import { filterEmpty } from '../_util/props-util';
|
||||||
? num
|
|
||||||
.toString()
|
|
||||||
.split('')
|
|
||||||
.reverse()
|
|
||||||
.map(i => {
|
|
||||||
const current = Number(i);
|
|
||||||
return isNaN(current) ? i : current;
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const scrollNumberProps = {
|
export const scrollNumberProps = {
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
count: PropTypes.any,
|
count: PropTypes.any,
|
||||||
component: PropTypes.string,
|
component: PropTypes.string,
|
||||||
title: PropTypes.oneOfType([PropTypes.number, PropTypes.string, null]),
|
title: PropTypes.oneOfType([PropTypes.number, PropTypes.string, null]),
|
||||||
displayComponent: PropTypes.any,
|
show: Boolean,
|
||||||
onAnimated: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScrollNumberProps = ExtractPropTypes<typeof scrollNumberProps>;
|
export type ScrollNumberProps = Partial<ExtractPropTypes<typeof scrollNumberProps>>;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ScrollNumber',
|
name: 'ScrollNumber',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: scrollNumberProps,
|
props: scrollNumberProps,
|
||||||
emits: ['animated'],
|
setup(props, { attrs, slots }) {
|
||||||
setup(props, { emit, attrs }) {
|
const { prefixCls } = useConfigInject('scroll-number', props);
|
||||||
const configProvider = inject('configProvider', defaultConfigProvider);
|
|
||||||
const state = reactive({
|
|
||||||
animateStarted: true,
|
|
||||||
lastCount: undefined,
|
|
||||||
sCount: props.count,
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
// 同方向则在同一侧切换数字
|
|
||||||
if (currentCount > lastCount) {
|
|
||||||
if (currentDigit >= lastDigit) {
|
|
||||||
return 10 + num;
|
|
||||||
}
|
|
||||||
return 20 + num;
|
|
||||||
}
|
|
||||||
if (currentDigit <= lastDigit) {
|
|
||||||
return 10 + num;
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
const handleAnimated = () => {
|
|
||||||
emit('animated');
|
|
||||||
};
|
|
||||||
|
|
||||||
const _clearTimeout = () => {
|
|
||||||
if (state.timeout) {
|
|
||||||
clearTimeout(state.timeout);
|
|
||||||
state.timeout = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNumberList = (position: number, className: string) => {
|
|
||||||
const childrenToReturn = [];
|
|
||||||
for (let i = 0; i < 30; i++) {
|
|
||||||
childrenToReturn.push(
|
|
||||||
<p
|
|
||||||
key={i.toString()}
|
|
||||||
class={classNames(className, {
|
|
||||||
current: position === i,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{i % 10}
|
|
||||||
</p>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return childrenToReturn;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCurrentNumber = (prefixCls: string, num: number | string, i: number) => {
|
|
||||||
if (typeof num === 'number') {
|
|
||||||
const position = getPositionByNum(num, i);
|
|
||||||
const removeTransition =
|
|
||||||
state.animateStarted || getNumberArray(state.lastCount)[i] === undefined;
|
|
||||||
const style = {
|
|
||||||
transition: removeTransition ? 'none' : undefined,
|
|
||||||
msTransform: `translateY(${-position * 100}%)`,
|
|
||||||
WebkitTransform: `translateY(${-position * 100}%)`,
|
|
||||||
transform: `translateY(${-position * 100}%)`,
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<span class={`${prefixCls}-only`} style={style} key={i}>
|
|
||||||
{renderNumberList(position, `${prefixCls}-only-unit`)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span key="symbol" class={`${prefixCls}-symbol`}>
|
|
||||||
{num}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNumberElement = (prefixCls: string) => {
|
|
||||||
if (state.sCount && Number(state.sCount) % 1 === 0) {
|
|
||||||
return getNumberArray(state.sCount)
|
|
||||||
.map((num, i) => renderCurrentNumber(prefixCls, num, i))
|
|
||||||
.reverse();
|
|
||||||
}
|
|
||||||
return state.sCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
|
count,
|
||||||
title,
|
title,
|
||||||
|
show,
|
||||||
component: Tag = ('sup' as unknown) as DefineComponent,
|
component: Tag = ('sup' as unknown) as DefineComponent,
|
||||||
displayComponent,
|
class: className,
|
||||||
} = props;
|
style,
|
||||||
const getPrefixCls = configProvider.getPrefixCls;
|
...restProps
|
||||||
const prefixCls = getPrefixCls('scroll-number', customizePrefixCls);
|
} = { ...props, ...attrs } as ScrollNumberProps & HTMLAttributes & { style: CSSProperties };
|
||||||
const { class: className, style = {} } = attrs as {
|
// ============================ Render ============================
|
||||||
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 = {
|
const newProps = {
|
||||||
...restProps,
|
...restProps,
|
||||||
title,
|
style,
|
||||||
style: tempStyle,
|
'data-show': props.show,
|
||||||
class: classNames(prefixCls, className),
|
class: classNames(prefixCls.value, className),
|
||||||
|
title: title as string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only integer need motion
|
||||||
|
let numberNodes: any = count;
|
||||||
|
if (count && Number(count) % 1 === 0) {
|
||||||
|
const numberList = String(count).split('');
|
||||||
|
|
||||||
|
numberNodes = numberList.map((num, i) => (
|
||||||
|
<SingleNumber
|
||||||
|
prefixCls={prefixCls.value}
|
||||||
|
count={Number(count)}
|
||||||
|
value={num}
|
||||||
|
key={numberList.length - i}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// allow specify the border
|
// allow specify the border
|
||||||
// mock border-color by box-shadow for compatible with old usage:
|
// mock border-color by box-shadow for compatible with old usage:
|
||||||
// <Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />
|
// <Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />
|
||||||
if (style && style.borderColor) {
|
if (style && style.borderColor) {
|
||||||
newProps.style.boxShadow = `0 0 0 1px ${style.borderColor} inset`;
|
newProps.style = {
|
||||||
|
...(style as CSSProperties),
|
||||||
|
boxShadow: `0 0 0 1px ${style.borderColor} inset`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const children = filterEmpty(slots.default?.());
|
||||||
|
if (children && children.length) {
|
||||||
|
return cloneElement(
|
||||||
|
children,
|
||||||
|
{
|
||||||
|
class: classNames(`${prefixCls.value}-custom-component`),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Tag {...newProps}>{renderNumberElement(prefixCls)}</Tag>;
|
return <Tag {...newProps}>{numberNodes}</Tag>;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { computed, CSSProperties, defineComponent, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
|
import classNames from '../_util/classNames';
|
||||||
|
|
||||||
|
export interface UnitNumberProps {
|
||||||
|
prefixCls: string;
|
||||||
|
value: string | number;
|
||||||
|
offset?: number;
|
||||||
|
current?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UnitNumber({ prefixCls, value, current, offset = 0 }: UnitNumberProps) {
|
||||||
|
let style: CSSProperties | undefined;
|
||||||
|
|
||||||
|
if (offset) {
|
||||||
|
style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${offset}00%`,
|
||||||
|
left: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
style={style}
|
||||||
|
class={classNames(`${prefixCls}-only-unit`, {
|
||||||
|
current,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOffset(start: number, end: number, unit: -1 | 1) {
|
||||||
|
let index = start;
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
while ((index + 10) % 10 !== end) {
|
||||||
|
index += unit;
|
||||||
|
offset += unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SingleNumber',
|
||||||
|
props: {
|
||||||
|
prefixCls: String,
|
||||||
|
value: String,
|
||||||
|
count: Number,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const originValue = computed(() => Number(props.value));
|
||||||
|
const originCount = computed(() => Math.abs(props.count));
|
||||||
|
const state = reactive({
|
||||||
|
prevValue: originValue.value,
|
||||||
|
prevCount: originCount.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================= Events =============================
|
||||||
|
const onTransitionEnd = () => {
|
||||||
|
state.prevValue = originValue.value;
|
||||||
|
state.prevCount = originCount.value;
|
||||||
|
};
|
||||||
|
const timeout = ref();
|
||||||
|
// Fallback if transition event not support
|
||||||
|
watch(
|
||||||
|
originValue,
|
||||||
|
() => {
|
||||||
|
clearTimeout(timeout.value);
|
||||||
|
timeout.value = setTimeout(() => {
|
||||||
|
onTransitionEnd();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimeout(timeout.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
let unitNodes: any[];
|
||||||
|
let offsetStyle: CSSProperties = {};
|
||||||
|
const value = originValue.value;
|
||||||
|
if (state.prevValue === value || Number.isNaN(value) || Number.isNaN(state.prevValue)) {
|
||||||
|
// Nothing to change
|
||||||
|
unitNodes = [UnitNumber({ ...props, current: true } as UnitNumberProps)];
|
||||||
|
offsetStyle = {
|
||||||
|
transition: 'none',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
unitNodes = [];
|
||||||
|
|
||||||
|
// Fill basic number units
|
||||||
|
const end = value + 10;
|
||||||
|
const unitNumberList: number[] = [];
|
||||||
|
for (let index = value; index <= end; index += 1) {
|
||||||
|
unitNumberList.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill with number unit nodes
|
||||||
|
const prevIndex = unitNumberList.findIndex(n => n % 10 === state.prevValue);
|
||||||
|
unitNodes = unitNumberList.map((n, index) => {
|
||||||
|
const singleUnit = n % 10;
|
||||||
|
return UnitNumber({
|
||||||
|
...props,
|
||||||
|
value: singleUnit,
|
||||||
|
offset: index - prevIndex,
|
||||||
|
current: index === prevIndex,
|
||||||
|
} as UnitNumberProps);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate container offset value
|
||||||
|
const unit = state.prevCount < originCount.value ? 1 : -1;
|
||||||
|
offsetStyle = {
|
||||||
|
transform: `translateY(${-getOffset(state.prevValue, value, unit)}00%)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class={`${props.prefixCls}-only`}
|
||||||
|
style={offsetStyle}
|
||||||
|
onTransitionend={() => onTransitionEnd()}
|
||||||
|
>
|
||||||
|
{unitNodes}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,3 +1,14 @@
|
||||||
|
import { App, Plugin } from 'vue';
|
||||||
import Badge from './Badge';
|
import Badge from './Badge';
|
||||||
|
import Ribbon from './Ribbon';
|
||||||
|
|
||||||
export default Badge;
|
Badge.install = function(app: App) {
|
||||||
|
app.component(Badge.name, Badge);
|
||||||
|
app.component(Ribbon.name, Ribbon);
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge as typeof Badge &
|
||||||
|
Plugin & {
|
||||||
|
readonly Ribbon: typeof Ribbon;
|
||||||
|
};
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: unset;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
&-count {
|
&-count {
|
||||||
|
z-index: @zindex-badge;
|
||||||
min-width: @badge-height;
|
min-width: @badge-height;
|
||||||
height: @badge-height;
|
height: @badge-height;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
line-height: @badge-height;
|
line-height: @badge-height;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: @highlight-color;
|
background: @badge-color;
|
||||||
border-radius: (@badge-height / 2);
|
border-radius: (@badge-height / 2);
|
||||||
box-shadow: 0 0 0 1px @shadow-color-inverse;
|
box-shadow: 0 0 0 1px @shadow-color-inverse;
|
||||||
a,
|
a,
|
||||||
|
@ -45,7 +45,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-dot {
|
&-dot {
|
||||||
|
z-index: @zindex-badge;
|
||||||
width: @badge-dot-size;
|
width: @badge-dot-size;
|
||||||
|
min-width: @badge-dot-size;
|
||||||
height: @badge-dot-size;
|
height: @badge-dot-size;
|
||||||
background: @highlight-color;
|
background: @highlight-color;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
@ -58,9 +60,12 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: @zindex-badge;
|
|
||||||
transform: translate(50%, -50%);
|
transform: translate(50%, -50%);
|
||||||
transform-origin: 100% 0%;
|
transform-origin: 100% 0%;
|
||||||
|
|
||||||
|
&.@{iconfont-css-prefix}-spin {
|
||||||
|
animation: antBadgeLoadingCircle 1s infinite linear;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-status {
|
&-status {
|
||||||
|
@ -124,24 +129,39 @@
|
||||||
|
|
||||||
&-zoom-appear,
|
&-zoom-appear,
|
||||||
&-zoom-enter {
|
&-zoom-enter {
|
||||||
animation: antZoomBadgeIn 0.3s @ease-out-back;
|
animation: antZoomBadgeIn @animation-duration-slow @ease-out-back;
|
||||||
animation-fill-mode: both;
|
animation-fill-mode: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-zoom-leave {
|
&-zoom-leave {
|
||||||
animation: antZoomBadgeOut 0.3s @ease-in-back;
|
animation: antZoomBadgeOut @animation-duration-slow @ease-in-back;
|
||||||
animation-fill-mode: both;
|
animation-fill-mode: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-not-a-wrapper {
|
&-not-a-wrapper {
|
||||||
|
.@{badge-prefix-cls}-zoom-appear,
|
||||||
|
.@{badge-prefix-cls}-zoom-enter {
|
||||||
|
animation: antNoWrapperZoomBadgeIn @animation-duration-slow @ease-out-back;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{badge-prefix-cls}-zoom-leave {
|
||||||
|
animation: antNoWrapperZoomBadgeOut @animation-duration-slow @ease-in-back;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.@{badge-prefix-cls}-status) {
|
&:not(.@{badge-prefix-cls}-status) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.@{number-prefix-cls}-custom-component {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{number-prefix-cls}-custom-component,
|
||||||
.@{ant-prefix}-scroll-number {
|
.@{ant-prefix}-scroll-number {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: auto;
|
top: auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{badge-prefix-cls}-count {
|
.@{badge-prefix-cls}-count {
|
||||||
|
@ -161,15 +181,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safari will blink with transform when inner element has absolute style.
|
||||||
|
.safari-fix-motion() {
|
||||||
|
-webkit-transform-style: preserve-3d;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.@{number-prefix-cls} {
|
.@{number-prefix-cls} {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
&-only {
|
&-only {
|
||||||
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: @badge-height;
|
height: @badge-height;
|
||||||
transition: all 0.3s @ease-in-out;
|
transition: all @animation-duration-slow @ease-in-out;
|
||||||
|
.safari-fix-motion;
|
||||||
|
|
||||||
> p.@{number-prefix-cls}-only-unit {
|
> p.@{number-prefix-cls}-only-unit {
|
||||||
height: @badge-height;
|
height: @badge-height;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
.safari-fix-motion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,4 +228,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes antNoWrapperZoomBadgeIn {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antNoWrapperZoomBadgeOut {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antBadgeLoadingCircle {
|
||||||
|
0% {
|
||||||
|
transform-origin: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translate(50%, -50%) rotate(360deg);
|
||||||
|
transform-origin: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@import './ribbon';
|
@import './ribbon';
|
||||||
|
@import './rtl';
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
.@{badge-prefix-cls} {
|
||||||
|
&-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-count,
|
||||||
|
&-dot,
|
||||||
|
.@{number-prefix-cls}-custom-component {
|
||||||
|
.@{badge-prefix-cls}-rtl & {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
direction: ltr;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{badge-prefix-cls}-rtl& .@{number-prefix-cls}-custom-component {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
&-text {
|
||||||
|
.@{badge-prefix-cls}-rtl & {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-zoom-appear,
|
||||||
|
&-zoom-enter {
|
||||||
|
.@{badge-prefix-cls}-rtl & {
|
||||||
|
animation-name: antZoomBadgeInRtl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-zoom-leave {
|
||||||
|
.@{badge-prefix-cls}-rtl & {
|
||||||
|
animation-name: antZoomBadgeOutRtl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-not-a-wrapper {
|
||||||
|
.@{badge-prefix-cls}-count {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ribbon-prefix-cls}-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
&.@{ribbon-prefix-cls}-placement-end {
|
||||||
|
right: unset;
|
||||||
|
left: -8px;
|
||||||
|
border-bottom-right-radius: @border-radius-sm;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
.@{ribbon-prefix-cls}-corner {
|
||||||
|
right: unset;
|
||||||
|
left: 0;
|
||||||
|
border-color: currentColor currentColor transparent transparent;
|
||||||
|
&::after {
|
||||||
|
border-color: currentColor currentColor transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.@{ribbon-prefix-cls}-placement-start {
|
||||||
|
right: -8px;
|
||||||
|
left: unset;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: @border-radius-sm;
|
||||||
|
.@{ribbon-prefix-cls}-corner {
|
||||||
|
right: 0;
|
||||||
|
left: unset;
|
||||||
|
border-color: currentColor transparent transparent currentColor;
|
||||||
|
&::after {
|
||||||
|
border-color: currentColor transparent transparent currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antZoomBadgeInRtl {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) translate(-50%, -50%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antZoomBadgeOutRtl {
|
||||||
|
0% {
|
||||||
|
transform: scale(1) translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0) translate(-50%, -50%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { PresetColorTypes } from '../_util/colors';
|
import { PresetColorTypes } from '../_util/colors';
|
||||||
|
|
||||||
export function isPresetColor(color?: string): boolean {
|
export function isPresetColor(color?: string): boolean {
|
||||||
return (PresetColorTypes as string[]).indexOf(color) !== -1;
|
return (PresetColorTypes as any[]).indexOf(color) !== -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
@ -7,15 +8,10 @@
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
<meta http-equiv="Expires" content="0" />
|
<meta http-equiv="Expires" content="0" />
|
||||||
<meta
|
<meta name="description" content="An enterprise-class UI components based on Ant Design and Vue" />
|
||||||
name="description"
|
|
||||||
content="An enterprise-class UI components based on Ant Design and Vue"
|
|
||||||
/>
|
|
||||||
<title>Ant Design Vue</title>
|
<title>Ant Design Vue</title>
|
||||||
<meta
|
<meta name="keywords"
|
||||||
name="keywords"
|
content="ant design vue,ant-design-vue,ant-design-vue admin,ant design pro,vue ant design,vue ant design pro,vue ant design admin,ant design vue官网,ant design vue中文文档,ant design vue文档" />
|
||||||
content="ant design vue,ant-design-vue,ant-design-vue admin,ant design pro,vue ant design,vue ant design pro,vue ant design admin,ant design vue官网,ant design vue中文文档,ant design vue文档"
|
|
||||||
/>
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="https://qn.antdv.com/favicon.ico" />
|
<link rel="shortcut icon" type="image/x-icon" href="https://qn.antdv.com/favicon.ico" />
|
||||||
<style id="nprogress-style">
|
<style id="nprogress-style">
|
||||||
#nprogress {
|
#nprogress {
|
||||||
|
@ -25,6 +21,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app" style="padding: 50px"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue