Badge.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { withDirectives as _withDirectives, vShow as _vShow, createVNode as _createVNode } from "vue";
  4. import PropTypes from '../_util/vue-types';
  5. import ScrollNumber from './ScrollNumber';
  6. import classNames from '../_util/classNames';
  7. import { getPropsSlot, flattenChildren } from '../_util/props-util';
  8. import { cloneElement } from '../_util/vnode';
  9. import { getTransitionProps, Transition } from '../_util/transition';
  10. import { defineComponent, computed, ref, watch } from 'vue';
  11. import Ribbon from './Ribbon';
  12. import useConfigInject from '../config-provider/hooks/useConfigInject';
  13. import isNumeric from '../_util/isNumeric';
  14. import useStyle from './style';
  15. import { isPresetColor } from '../_util/colors';
  16. export const badgeProps = () => ({
  17. /** Number to show in badge */
  18. count: PropTypes.any.def(null),
  19. showZero: {
  20. type: Boolean,
  21. default: undefined
  22. },
  23. /** Max count to show */
  24. overflowCount: {
  25. type: Number,
  26. default: 99
  27. },
  28. /** whether to show red dot without number */
  29. dot: {
  30. type: Boolean,
  31. default: undefined
  32. },
  33. prefixCls: String,
  34. scrollNumberPrefixCls: String,
  35. status: {
  36. type: String
  37. },
  38. size: {
  39. type: String,
  40. default: 'default'
  41. },
  42. color: String,
  43. text: PropTypes.any,
  44. offset: Array,
  45. numberStyle: {
  46. type: Object,
  47. default: undefined
  48. },
  49. title: String
  50. });
  51. export default defineComponent({
  52. compatConfig: {
  53. MODE: 3
  54. },
  55. name: 'ABadge',
  56. Ribbon,
  57. inheritAttrs: false,
  58. props: badgeProps(),
  59. slots: Object,
  60. setup(props, _ref) {
  61. let {
  62. slots,
  63. attrs
  64. } = _ref;
  65. const {
  66. prefixCls,
  67. direction
  68. } = useConfigInject('badge', props);
  69. const [wrapSSR, hashId] = useStyle(prefixCls);
  70. // ================================ Misc ================================
  71. const numberedDisplayCount = computed(() => {
  72. return props.count > props.overflowCount ? `${props.overflowCount}+` : props.count;
  73. });
  74. const isZero = computed(() => numberedDisplayCount.value === '0' || numberedDisplayCount.value === 0);
  75. const ignoreCount = computed(() => props.count === null || isZero.value && !props.showZero);
  76. const hasStatus = computed(() => (props.status !== null && props.status !== undefined || props.color !== null && props.color !== undefined) && ignoreCount.value);
  77. const showAsDot = computed(() => props.dot && !isZero.value);
  78. const mergedCount = computed(() => showAsDot.value ? '' : numberedDisplayCount.value);
  79. const isHidden = computed(() => {
  80. const isEmpty = mergedCount.value === null || mergedCount.value === undefined || mergedCount.value === '';
  81. return (isEmpty || isZero.value && !props.showZero) && !showAsDot.value;
  82. });
  83. // Count should be cache in case hidden change it
  84. const livingCount = ref(props.count);
  85. // We need cache count since remove motion should not change count display
  86. const displayCount = ref(mergedCount.value);
  87. // We will cache the dot status to avoid shaking on leaved motion
  88. const isDotRef = ref(showAsDot.value);
  89. watch([() => props.count, mergedCount, showAsDot], () => {
  90. if (!isHidden.value) {
  91. livingCount.value = props.count;
  92. displayCount.value = mergedCount.value;
  93. isDotRef.value = showAsDot.value;
  94. }
  95. }, {
  96. immediate: true
  97. });
  98. // InternalColor
  99. const isInternalColor = computed(() => isPresetColor(props.color, false));
  100. // Shared styles
  101. const statusCls = computed(() => ({
  102. [`${prefixCls.value}-status-dot`]: hasStatus.value,
  103. [`${prefixCls.value}-status-${props.status}`]: !!props.status,
  104. [`${prefixCls.value}-color-${props.color}`]: isInternalColor.value
  105. }));
  106. const statusStyle = computed(() => {
  107. if (props.color && !isInternalColor.value) {
  108. return {
  109. background: props.color,
  110. color: props.color
  111. };
  112. } else {
  113. return {};
  114. }
  115. });
  116. const scrollNumberCls = computed(() => ({
  117. [`${prefixCls.value}-dot`]: isDotRef.value,
  118. [`${prefixCls.value}-count`]: !isDotRef.value,
  119. [`${prefixCls.value}-count-sm`]: props.size === 'small',
  120. [`${prefixCls.value}-multiple-words`]: !isDotRef.value && displayCount.value && displayCount.value.toString().length > 1,
  121. [`${prefixCls.value}-status-${props.status}`]: !!props.status,
  122. [`${prefixCls.value}-color-${props.color}`]: isInternalColor.value
  123. }));
  124. return () => {
  125. var _a, _b;
  126. const {
  127. offset,
  128. title,
  129. color
  130. } = props;
  131. const style = attrs.style;
  132. const text = getPropsSlot(slots, props, 'text');
  133. const pre = prefixCls.value;
  134. const count = livingCount.value;
  135. let children = flattenChildren((_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots));
  136. children = children.length ? children : null;
  137. const visible = !!(!isHidden.value || slots.count);
  138. // =============================== Styles ===============================
  139. const mergedStyle = (() => {
  140. if (!offset) {
  141. return _extends({}, style);
  142. }
  143. const offsetStyle = {
  144. marginTop: isNumeric(offset[1]) ? `${offset[1]}px` : offset[1]
  145. };
  146. if (direction.value === 'rtl') {
  147. offsetStyle.left = `${parseInt(offset[0], 10)}px`;
  148. } else {
  149. offsetStyle.right = `${-parseInt(offset[0], 10)}px`;
  150. }
  151. return _extends(_extends({}, offsetStyle), style);
  152. })();
  153. // =============================== Render ===============================
  154. // >>> Title
  155. const titleNode = title !== null && title !== void 0 ? title : typeof count === 'string' || typeof count === 'number' ? count : undefined;
  156. // >>> Status Text
  157. const statusTextNode = visible || !text ? null : _createVNode("span", {
  158. "class": `${pre}-status-text`
  159. }, [text]);
  160. // >>> Display Component
  161. const displayNode = typeof count === 'object' || count === undefined && slots.count ? cloneElement(count !== null && count !== void 0 ? count : (_b = slots.count) === null || _b === void 0 ? void 0 : _b.call(slots), {
  162. style: mergedStyle
  163. }, false) : null;
  164. const badgeClassName = classNames(pre, {
  165. [`${pre}-status`]: hasStatus.value,
  166. [`${pre}-not-a-wrapper`]: !children,
  167. [`${pre}-rtl`]: direction.value === 'rtl'
  168. }, attrs.class, hashId.value);
  169. // <Badge status="success" />
  170. if (!children && hasStatus.value) {
  171. const statusTextColor = mergedStyle.color;
  172. return wrapSSR(_createVNode("span", _objectSpread(_objectSpread({}, attrs), {}, {
  173. "class": badgeClassName,
  174. "style": mergedStyle
  175. }), [_createVNode("span", {
  176. "class": statusCls.value,
  177. "style": statusStyle.value
  178. }, null), _createVNode("span", {
  179. "style": {
  180. color: statusTextColor
  181. },
  182. "class": `${pre}-status-text`
  183. }, [text])]));
  184. }
  185. const transitionProps = getTransitionProps(children ? `${pre}-zoom` : '', {
  186. appear: false
  187. });
  188. let scrollNumberStyle = _extends(_extends({}, mergedStyle), props.numberStyle);
  189. if (color && !isInternalColor.value) {
  190. scrollNumberStyle = scrollNumberStyle || {};
  191. scrollNumberStyle.background = color;
  192. }
  193. return wrapSSR(_createVNode("span", _objectSpread(_objectSpread({}, attrs), {}, {
  194. "class": badgeClassName
  195. }), [children, _createVNode(Transition, transitionProps, {
  196. default: () => [_withDirectives(_createVNode(ScrollNumber, {
  197. "prefixCls": props.scrollNumberPrefixCls,
  198. "show": visible,
  199. "class": scrollNumberCls.value,
  200. "count": displayCount.value,
  201. "title": titleNode,
  202. "style": scrollNumberStyle,
  203. "key": "scrollNumber"
  204. }, {
  205. default: () => [displayNode]
  206. }), [[_vShow, visible]])]
  207. }), statusTextNode]));
  208. };
  209. }
  210. });