import PropTypes from '../_util/vue-types'; import ScrollNumber from './ScrollNumber'; import { PresetColorTypes } from '../_util/colors'; import classNames from '../_util/classNames'; import { initDefaultProps, getComponent, getSlot } from '../_util/props-util'; import { cloneElement } from '../_util/vnode'; import { getTransitionProps, Transition } from '../_util/transition'; import isNumeric from '../_util/isNumeric'; import { defaultConfigProvider } from '../config-provider'; import { inject, defineComponent, CSSProperties, VNode } from 'vue'; import { tuple } from '../_util/type'; const BadgeProps = { /** Number to show in badge */ count: PropTypes.VNodeChild, showZero: PropTypes.looseBool, /** Max count to show */ overflowCount: PropTypes.number, /** whether to show red dot without number */ dot: PropTypes.looseBool, prefixCls: PropTypes.string, scrollNumberPrefixCls: PropTypes.string, status: PropTypes.oneOf(tuple('success', 'processing', 'default', 'error', 'warning')), color: PropTypes.string, text: PropTypes.VNodeChild, offset: PropTypes.arrayOf(PropTypes.oneOfType([String, Number])), numberStyle: PropTypes.style, title: PropTypes.string, }; function isPresetColor(color?: string): boolean { return (PresetColorTypes as string[]).indexOf(color) !== -1; } export default defineComponent({ name: 'ABadge', props: initDefaultProps(BadgeProps, { showZero: false, dot: false, overflowCount: 99, }) as typeof BadgeProps, setup() { return { configProvider: inject('configProvider', defaultConfigProvider), badgeCount: undefined, }; }, methods: { getNumberedDispayCount() { const { overflowCount } = this.$props; const count = this.badgeCount; const displayCount = count > overflowCount ? `${overflowCount}+` : count; return displayCount; }, getDispayCount() { const isDot = this.isDot(); // dot mode don't need count if (isDot) { return ''; } return this.getNumberedDispayCount(); }, getScrollNumberTitle() { const { title } = this.$props; const count = this.badgeCount; if (title) { return title; } return typeof count === 'string' || typeof count === 'number' ? count : undefined; }, getStyleWithOffset() { const { offset, numberStyle } = this.$props; return offset ? { right: `${-parseInt(offset[0] as string, 10)}px`, marginTop: isNumeric(offset[1]) ? `${offset[1]}px` : offset[1], ...numberStyle, } : { ...numberStyle }; }, getBadgeClassName(prefixCls: string, children: VNode[]) { const hasStatus = this.hasStatus(); return classNames(prefixCls, { [`${prefixCls}-status`]: hasStatus, [`${prefixCls}-dot-status`]: hasStatus && this.dot && !this.isZero(), [`${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 { dot } = this.$props; 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 : {text}; }, renderDispayComponent() { const count = this.badgeCount; const customNode = count; if (!customNode || typeof customNode !== 'object') { return undefined; } return cloneElement( customNode, { style: this.getStyleWithOffset(), }, false, ); }, renderBadgeNumber(prefixCls: string, scrollNumberPrefixCls: string) { const { status, color } = this.$props; const count = this.badgeCount; const displayCount = this.getDispayCount(); const isDot = this.isDot(); const hidden = this.isHidden(); const scrollNumberCls = { [`${prefixCls}-dot`]: isDot, [`${prefixCls}-count`]: !isDot, [`${prefixCls}-multiple-words`]: !isDot && count && count.toString && count.toString().length > 1, [`${prefixCls}-status-${status}`]: !!status, [`${prefixCls}-status-${color}`]: isPresetColor(color), }; let statusStyle = this.getStyleWithOffset(); if (color && !isPresetColor(color)) { statusStyle = statusStyle || {}; statusStyle.background = color; } return hidden ? null : ( ); }, }, render() { const { prefixCls: customizePrefixCls, scrollNumberPrefixCls: customizeScrollNumberPrefixCls, status, color, } = this; const text = getComponent(this, 'text'); const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('badge', customizePrefixCls); const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls); const children = getSlot(this); let count = getComponent(this, 'count'); if (Array.isArray(count)) { count = count[0]; } this.badgeCount = count; const scrollNumber = this.renderBadgeNumber(prefixCls, scrollNumberPrefixCls); const statusText = this.renderStatusText(prefixCls); const statusCls = classNames({ [`${prefixCls}-status-dot`]: this.hasStatus(), [`${prefixCls}-status-${status}`]: !!status, [`${prefixCls}-status-${color}`]: isPresetColor(color), }); const statusStyle: CSSProperties = {}; if (color && !isPresetColor(color)) { statusStyle.background = color; } // if (!children.length && this.hasStatus()) { const styleWithOffset = this.getStyleWithOffset(); const statusTextColor = styleWithOffset && styleWithOffset.color; return ( {text} ); } const transitionProps = getTransitionProps(children.length ? `${prefixCls}-zoom` : ''); return ( {children} {scrollNumber} {statusText} ); }, });