refactor: badge

pull/6213/head
tangjinzhou 2023-01-26 11:13:29 +08:00
parent e64a19a05a
commit 1e4e3cb3b4
15 changed files with 506 additions and 549 deletions

View File

@ -1,23 +1,34 @@
import type { ElementOf } from './type';
import { tuple } from './type';
import type { PresetColorKey } from '../theme/interface';
import { PresetColors } from '../theme/interface';
export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning');
type InverseColor = `${PresetColorKey}-inverse`;
const inverseColors = PresetColors.map<InverseColor>(color => `${color}-inverse`);
export const PresetColorTypes = tuple(
'pink',
'red',
'yellow',
'orange',
'cyan',
'green',
'blue',
'purple',
'geekblue',
'magenta',
'volcano',
'gold',
'lime',
);
export const PresetStatusColorTypes = [
'success',
'processing',
'error',
'default',
'warning',
] as const;
export type PresetColorType = ElementOf<typeof PresetColorTypes>;
export type PresetStatusColorType = ElementOf<typeof PresetStatusColorTypes>;
export type PresetColorType = PresetColorKey | InverseColor;
export type PresetStatusColorType = typeof PresetStatusColorTypes[number];
/**
* determine if the color keyword belongs to the `Ant Design` {@link PresetColors}.
* @param color color to be judged
* @param includeInverse whether to include reversed colors
*/
export function isPresetColor(color?: any, includeInverse = true) {
if (includeInverse) {
return [...inverseColors, ...PresetColors].includes(color);
}
return PresetColors.includes(color);
}
export function isPresetStatusColor(color?: any): color is PresetStatusColorType {
return PresetStatusColorTypes.includes(color);
}

View File

@ -14,7 +14,7 @@ export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer
/**
* https://github.com/Microsoft/TypeScript/issues/29729
*/
export type LiteralUnion<T extends U, U> = T | (U & {});
export type LiteralUnion<T extends string> = T | (string & {});
export type Data = Record<string, unknown>;

View File

@ -7,10 +7,13 @@ import { getTransitionProps, Transition } from '../_util/transition';
import type { ExtractPropTypes, CSSProperties, PropType } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';
import Ribbon from './Ribbon';
import { isPresetColor } from './utils';
import useConfigInject from '../_util/hooks/useConfigInject';
import isNumeric from '../_util/isNumeric';
import useStyle from './style';
import type { PresetColorKey } from '../theme/interface';
import type { LiteralUnion } from '../_util/type';
import type { PresetStatusColorType } from '../_util/colors';
import { isPresetColor } from '../_util/colors';
export const badgeProps = () => ({
/** Number to show in badge */
@ -24,7 +27,7 @@ export const badgeProps = () => ({
scrollNumberPrefixCls: String,
status: { type: String as PropType<PresetStatusColorType> },
size: { type: String as PropType<'default' | 'small'>, default: 'default' },
color: String,
color: String as PropType<LiteralUnion<PresetColorKey>>,
text: PropTypes.any,
offset: Array as unknown as PropType<[number | string, number | string]>,
numberStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
@ -42,6 +45,7 @@ export default defineComponent({
slots: ['text', 'count'],
setup(props, { slots, attrs }) {
const { prefixCls, direction } = useConfigInject('badge', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
// ================================ Misc ================================
const numberedDisplayCount = computed(() => {
@ -52,15 +56,16 @@ export default defineComponent({
) as string | number | null;
});
const hasStatus = computed(
() =>
(props.status !== null && props.status !== undefined) ||
(props.color !== null && props.color !== undefined),
);
const isZero = computed(
() => numberedDisplayCount.value === '0' || numberedDisplayCount.value === 0,
);
const ignoreCount = computed(() => props.count === null || (isZero.value && !props.showZero));
const hasStatus = computed(
() =>
((props.status !== null && props.status !== undefined) ||
(props.color !== null && props.color !== undefined)) &&
ignoreCount.value,
);
const showAsDot = computed(() => props.dot && !isZero.value);
@ -92,17 +97,18 @@ export default defineComponent({
},
{ immediate: true },
);
// InternalColor
const isInternalColor = computed(() => isPresetColor(props.color, false));
// 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),
[`${prefixCls.value}-status-${props.color}`]: isInternalColor.value,
}));
const statusStyle = computed(() => {
if (props.color && !isPresetColor(props.color)) {
return { background: props.color };
if (props.color && !isInternalColor.value) {
return { background: props.color, color: props.color };
} else {
return {};
}
@ -115,7 +121,7 @@ export default defineComponent({
[`${prefixCls.value}-multiple-words`]:
!isDotRef.value && displayCount.value && displayCount.value.toString().length > 1,
[`${prefixCls.value}-status-${props.status}`]: !!props.status,
[`${prefixCls.value}-status-${props.color}`]: isPresetColor(props.color),
[`${prefixCls.value}-status-${props.color}`]: isInternalColor.value,
}));
return () => {
@ -179,18 +185,19 @@ export default defineComponent({
[`${pre}-rtl`]: direction.value === 'rtl',
},
attrs.class,
hashId.value,
);
// <Badge status="success" />
if (!children && hasStatus.value) {
const statusTextColor = mergedStyle.color;
return (
return wrapSSR(
<span {...attrs} class={badgeClassName} style={mergedStyle}>
<span class={statusCls.value} style={statusStyle.value} />
<span style={{ color: statusTextColor }} class={`${pre}-status-text`}>
{text}
</span>
</span>
</span>,
);
}
@ -198,12 +205,12 @@ export default defineComponent({
appear: false,
});
let scrollNumberStyle: CSSProperties = { ...mergedStyle, ...(props.numberStyle as object) };
if (color && !isPresetColor(color)) {
if (color && !isInternalColor.value) {
scrollNumberStyle = scrollNumberStyle || {};
scrollNumberStyle.background = color;
}
return (
return wrapSSR(
<span {...attrs} class={badgeClassName}>
{children}
<Transition {...transitionProps}>
@ -221,7 +228,7 @@ export default defineComponent({
</ScrollNumber>
</Transition>
{statusTextNode}
</span>
</span>,
);
};
},

View File

@ -1,6 +1,7 @@
import type { LiteralUnion } from '../_util/type';
import type { PresetColorType } from '../_util/colors';
import { isPresetColor } from './utils';
import useStyle from './style';
import { isPresetColor } from '../_util/colors';
import type { CSSProperties, PropType, ExtractPropTypes } from 'vue';
import { defineComponent, computed } from 'vue';
import PropTypes from '../_util/vue-types';
@ -8,7 +9,7 @@ import useConfigInject from '../_util/hooks/useConfigInject';
export const ribbonProps = () => ({
prefix: String,
color: { type: String as PropType<LiteralUnion<PresetColorType, string>> },
color: { type: String as PropType<LiteralUnion<PresetColorType>> },
text: PropTypes.any,
placement: { type: String as PropType<'start' | 'end'>, default: 'end' },
});
@ -23,7 +24,8 @@ export default defineComponent({
slots: ['text'],
setup(props, { attrs, slots }) {
const { prefixCls, direction } = useConfigInject('ribbon', props);
const colorInPreset = computed(() => isPresetColor(props.color));
const [wrapSSR, hashId] = useStyle(prefixCls);
const colorInPreset = computed(() => isPresetColor(props.color, false));
const ribbonCls = computed(() => [
prefixCls.value,
`${prefixCls.value}-placement-${props.placement}`,
@ -40,17 +42,17 @@ export default defineComponent({
colorStyle.background = props.color;
cornerColorStyle.color = props.color;
}
return (
<div class={`${prefixCls.value}-wrapper`} {...restAttrs}>
return wrapSSR(
<div class={`${prefixCls.value}-wrapper ${hashId.value}`} {...restAttrs}>
{slots.default?.()}
<div
class={[ribbonCls.value, className]}
class={[ribbonCls.value, className, hashId.value]}
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>,
);
};
},

View File

@ -1,281 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@badge-prefix-cls: ~'@{ant-prefix}-badge';
@number-prefix-cls: ~'@{ant-prefix}-scroll-number';
.@{badge-prefix-cls} {
.reset-component();
position: relative;
display: inline-block;
line-height: 1;
&-count {
z-index: @zindex-badge;
min-width: @badge-height;
height: @badge-height;
padding: 0 6px;
color: @badge-text-color;
font-weight: @badge-font-weight;
font-size: @badge-font-size;
line-height: @badge-height;
white-space: nowrap;
text-align: center;
background: @badge-color;
border-radius: (@badge-height / 2);
box-shadow: 0 0 0 1px @shadow-color-inverse;
a,
a:hover {
color: @badge-text-color;
}
}
&-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 {
padding: 0 8px;
}
&-dot {
z-index: @zindex-badge;
width: @badge-dot-size;
min-width: @badge-dot-size;
height: @badge-dot-size;
background: @highlight-color;
border-radius: 100%;
box-shadow: 0 0 0 1px @shadow-color-inverse;
}
// Tricky way to resolve https://github.com/ant-design/ant-design/issues/30088
&-dot.@{number-prefix-cls} {
transition: background 1.5s;
}
&-count,
&-dot,
.@{number-prefix-cls}-custom-component {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
transform-origin: 100% 0%;
&.@{iconfont-css-prefix}-spin {
animation: antBadgeLoadingCircle 1s infinite linear;
}
}
&-status {
line-height: inherit;
vertical-align: baseline;
&-dot {
position: relative;
top: -1px;
display: inline-block;
width: @badge-status-size;
height: @badge-status-size;
vertical-align: middle;
border-radius: 50%;
}
&-success {
background-color: @success-color;
}
&-processing {
position: relative;
background-color: @processing-color;
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid @processing-color;
border-radius: 50%;
animation: antStatusProcessing 1.2s infinite ease-in-out;
content: '';
}
}
&-default {
background-color: @normal-color;
}
&-error {
background-color: @error-color;
}
&-warning {
background-color: @warning-color;
}
// mixin to iterate over colors and create CSS class for each one
.make-color-classes(@i: length(@preset-colors)) when (@i > 0) {
.make-color-classes(@i - 1);
@color: extract(@preset-colors, @i);
@darkColor: '@{color}-6';
&-@{color} {
background: @@darkColor;
}
}
.make-color-classes();
&-text {
margin-left: 8px;
color: @text-color;
font-size: @font-size-base;
}
}
&-zoom-appear,
&-zoom-enter {
animation: antZoomBadgeIn @animation-duration-slow @ease-out-back;
animation-fill-mode: both;
}
&-zoom-leave {
animation: antZoomBadgeOut @animation-duration-slow @ease-in-back;
animation-fill-mode: both;
}
&-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) {
vertical-align: middle;
}
.@{number-prefix-cls}-custom-component,
.@{badge-prefix-cls}-count {
transform: none;
}
.@{number-prefix-cls}-custom-component,
.@{number-prefix-cls} {
position: relative;
top: auto;
display: block;
transform-origin: 50% 50%;
}
}
}
@keyframes antStatusProcessing {
0% {
transform: scale(0.8);
opacity: 0.5;
}
100% {
transform: scale(2.4);
opacity: 0;
}
}
// Safari will blink with transform when inner element has absolute style.
.safari-fix-motion() {
/* stylelint-disable property-no-vendor-prefix */
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
/* stylelint-enable property-no-vendor-prefix */
}
.@{number-prefix-cls} {
overflow: hidden;
direction: ltr;
&-only {
position: relative;
display: inline-block;
height: @badge-height;
transition: all @animation-duration-slow @ease-in-out;
.safari-fix-motion;
> p.@{number-prefix-cls}-only-unit {
height: @badge-height;
margin: 0;
.safari-fix-motion;
}
}
&-symbol {
vertical-align: top;
}
}
@keyframes antZoomBadgeIn {
0% {
transform: scale(0) translate(50%, -50%);
opacity: 0;
}
100% {
transform: scale(1) translate(50%, -50%);
}
}
@keyframes antZoomBadgeOut {
0% {
transform: scale(1) translate(50%, -50%);
}
100% {
transform: scale(0) translate(50%, -50%);
opacity: 0;
}
}
@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 './rtl';

View File

@ -0,0 +1,376 @@
import type { CSSObject } from '../../_util/cssinjs';
import { Keyframes } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genPresetColor, resetComponent } from '../../_style';
interface BadgeToken extends FullToken<'Badge'> {
badgeFontHeight: number;
badgeZIndex: number | string;
badgeHeight: number;
badgeHeightSm: number;
badgeTextColor: string;
badgeFontWeight: string;
badgeFontSize: number;
badgeColor: string;
badgeColorHover: string;
badgeDotSize: number;
badgeFontSizeSm: number;
badgeStatusSize: number;
badgeShadowSize: number;
badgeShadowColor: string;
badgeProcessingDuration: string;
badgeRibbonOffset: number;
badgeRibbonCornerTransform: string;
badgeRibbonCornerFilter: string;
}
const antStatusProcessing = new Keyframes('antStatusProcessing', {
'0%': { transform: 'scale(0.8)', opacity: 0.5 },
'100%': { transform: 'scale(2.4)', opacity: 0 },
});
const antZoomBadgeIn = new Keyframes('antZoomBadgeIn', {
'0%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 },
'100%': { transform: 'scale(1) translate(50%, -50%)' },
});
const antZoomBadgeOut = new Keyframes('antZoomBadgeOut', {
'0%': { transform: 'scale(1) translate(50%, -50%)' },
'100%': { transform: 'scale(0) translate(50%, -50%)', opacity: 0 },
});
const antNoWrapperZoomBadgeIn = new Keyframes('antNoWrapperZoomBadgeIn', {
'0%': { transform: 'scale(0)', opacity: 0 },
'100%': { transform: 'scale(1)' },
});
const antNoWrapperZoomBadgeOut = new Keyframes('antNoWrapperZoomBadgeOut', {
'0%': { transform: 'scale(1)' },
'100%': { transform: 'scale(0)', opacity: 0 },
});
const antBadgeLoadingCircle = new Keyframes('antBadgeLoadingCircle', {
'0%': { transformOrigin: '50%' },
'100%': {
transform: 'translate(50%, -50%) rotate(360deg)',
transformOrigin: '50%',
},
});
const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSObject => {
const {
componentCls,
iconCls,
antCls,
badgeFontHeight,
badgeShadowSize,
badgeHeightSm,
motionDurationSlow,
badgeStatusSize,
marginXS,
badgeRibbonOffset,
} = token;
const numberPrefixCls = `${antCls}-scroll-number`;
const ribbonPrefixCls = `${antCls}-ribbon`;
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
const statusPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`${componentCls}-status-${colorKey}`]: {
background: darkColor,
},
}));
const statusRibbonPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${ribbonPrefixCls}-color-${colorKey}`]: {
background: darkColor,
color: darkColor,
},
}));
return {
[componentCls]: {
...resetComponent(token),
position: 'relative',
display: 'inline-block',
width: 'fit-content',
lineHeight: 1,
[`${componentCls}-count`]: {
zIndex: token.badgeZIndex,
minWidth: token.badgeHeight,
height: token.badgeHeight,
color: token.badgeTextColor,
fontWeight: token.badgeFontWeight,
fontSize: token.badgeFontSize,
lineHeight: `${token.badgeHeight}px`,
whiteSpace: 'nowrap',
textAlign: 'center',
background: token.badgeColor,
borderRadius: token.badgeHeight / 2,
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`,
transition: `background ${token.motionDurationMid}`,
a: {
color: token.badgeTextColor,
},
'a:hover': {
color: token.badgeTextColor,
},
'a:hover &': {
background: token.badgeColorHover,
},
},
[`${componentCls}-count-sm`]: {
minWidth: badgeHeightSm,
height: badgeHeightSm,
fontSize: token.badgeFontSizeSm,
lineHeight: `${badgeHeightSm}px`,
borderRadius: badgeHeightSm / 2,
},
[`${componentCls}-multiple-words`]: {
padding: `0 ${token.paddingXS}px`,
},
[`${componentCls}-dot`]: {
zIndex: token.badgeZIndex,
width: token.badgeDotSize,
minWidth: token.badgeDotSize,
height: token.badgeDotSize,
background: token.badgeColor,
borderRadius: '100%',
boxShadow: `0 0 0 ${badgeShadowSize}px ${token.badgeShadowColor}`,
},
[`${componentCls}-dot${numberPrefixCls}`]: {
transition: `background ${motionDurationSlow}`,
},
[`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
transform: 'translate(50%, -50%)',
transformOrigin: '100% 0%',
[`${iconCls}-spin`]: {
animationName: antBadgeLoadingCircle,
animationDuration: token.motionDurationMid,
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
},
},
[`&${componentCls}-status`]: {
lineHeight: 'inherit',
verticalAlign: 'baseline',
[`${componentCls}-status-dot`]: {
position: 'relative',
top: -1, // Magic number, but seems better experience
display: 'inline-block',
width: badgeStatusSize,
height: badgeStatusSize,
verticalAlign: 'middle',
borderRadius: '50%',
},
[`${componentCls}-status-success`]: {
backgroundColor: token.colorSuccess,
},
[`${componentCls}-status-processing`]: {
position: 'relative',
color: token.colorPrimary,
backgroundColor: token.colorPrimary,
'&::after': {
position: 'absolute',
top: 0,
insetInlineStart: 0,
width: '100%',
height: '100%',
borderWidth: badgeShadowSize,
borderStyle: 'solid',
borderColor: 'inherit',
borderRadius: '50%',
animationName: antStatusProcessing,
animationDuration: token.badgeProcessingDuration,
animationIterationCount: 'infinite',
animationTimingFunction: 'ease-in-out',
content: '""',
},
},
[`${componentCls}-status-default`]: {
backgroundColor: token.colorTextPlaceholder,
},
[`${componentCls}-status-error`]: {
backgroundColor: token.colorError,
},
[`${componentCls}-status-warning`]: {
backgroundColor: token.colorWarning,
},
...statusPreset,
[`${componentCls}-status-text`]: {
marginInlineStart: marginXS,
color: token.colorText,
fontSize: token.fontSize,
},
},
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
animationName: antZoomBadgeIn,
animationDuration: token.motionDurationSlow,
animationTimingFunction: token.motionEaseOutBack,
animationFillMode: 'both',
},
[`${componentCls}-zoom-leave`]: {
animationName: antZoomBadgeOut,
animationDuration: token.motionDurationSlow,
animationTimingFunction: token.motionEaseOutBack,
animationFillMode: 'both',
},
[`&${componentCls}-not-a-wrapper`]: {
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
animationName: antNoWrapperZoomBadgeIn,
animationDuration: token.motionDurationSlow,
animationTimingFunction: token.motionEaseOutBack,
},
[`${componentCls}-zoom-leave`]: {
animationName: antNoWrapperZoomBadgeOut,
animationDuration: token.motionDurationSlow,
animationTimingFunction: token.motionEaseOutBack,
},
[`&:not(${componentCls}-status)`]: {
verticalAlign: 'middle',
},
[`${numberPrefixCls}-custom-component, ${componentCls}-count`]: {
transform: 'none',
},
[`${numberPrefixCls}-custom-component, ${numberPrefixCls}`]: {
position: 'relative',
top: 'auto',
display: 'block',
transformOrigin: '50% 50%',
},
},
[`${numberPrefixCls}`]: {
overflow: 'hidden',
[`${numberPrefixCls}-only`]: {
position: 'relative',
display: 'inline-block',
height: token.badgeHeight,
transition: `all ${token.motionDurationSlow} ${token.motionEaseOutBack}`,
WebkitTransformStyle: 'preserve-3d',
WebkitBackfaceVisibility: 'hidden',
[`> p${numberPrefixCls}-only-unit`]: {
height: token.badgeHeight,
margin: 0,
WebkitTransformStyle: 'preserve-3d',
WebkitBackfaceVisibility: 'hidden',
},
},
[`${numberPrefixCls}-symbol`]: { verticalAlign: 'top' },
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
[`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: {
transform: 'translate(-50%, -50%)',
},
},
},
[`${ribbonWrapperPrefixCls}`]: { position: 'relative' },
[`${ribbonPrefixCls}`]: {
...resetComponent(token),
position: 'absolute',
top: marginXS,
height: badgeFontHeight,
padding: `0 ${token.paddingXS}px`,
color: token.colorPrimary,
lineHeight: `${badgeFontHeight}px`,
whiteSpace: 'nowrap',
backgroundColor: token.colorPrimary,
borderRadius: token.borderRadiusSM,
[`${ribbonPrefixCls}-text`]: { color: token.colorTextLightSolid },
[`${ribbonPrefixCls}-corner`]: {
position: 'absolute',
top: '100%',
width: badgeRibbonOffset,
height: badgeRibbonOffset,
color: 'currentcolor',
border: `${badgeRibbonOffset / 2}px solid`,
transform: token.badgeRibbonCornerTransform,
transformOrigin: 'top',
filter: token.badgeRibbonCornerFilter,
},
...statusRibbonPreset,
[`&${ribbonPrefixCls}-placement-end`]: {
insetInlineEnd: -badgeRibbonOffset,
borderEndEndRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineEnd: 0,
borderInlineEndColor: 'transparent',
borderBlockEndColor: 'transparent',
},
},
[`&${ribbonPrefixCls}-placement-start`]: {
insetInlineStart: -badgeRibbonOffset,
borderEndStartRadius: 0,
[`${ribbonPrefixCls}-corner`]: {
insetInlineStart: 0,
borderBlockEndColor: 'transparent',
borderInlineStartColor: 'transparent',
},
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Badge', token => {
const { fontSize, lineHeight, fontSizeSM, lineWidth, marginXS, colorBorderBg } = token;
const badgeFontHeight = Math.round(fontSize * lineHeight);
const badgeShadowSize = lineWidth;
const badgeZIndex = 'auto';
const badgeHeight = badgeFontHeight - 2 * badgeShadowSize;
const badgeTextColor = token.colorBgContainer;
const badgeFontWeight = 'normal';
const badgeFontSize = fontSizeSM;
const badgeColor = token.colorError;
const badgeColorHover = token.colorErrorHover;
const badgeHeightSm = fontSize;
const badgeDotSize = fontSizeSM / 2;
const badgeFontSizeSm = fontSizeSM;
const badgeStatusSize = fontSizeSM / 2;
const badgeToken = mergeToken<BadgeToken>(token, {
badgeFontHeight,
badgeShadowSize,
badgeZIndex,
badgeHeight,
badgeTextColor,
badgeFontWeight,
badgeFontSize,
badgeColor,
badgeColorHover,
badgeShadowColor: colorBorderBg,
badgeHeightSm,
badgeDotSize,
badgeFontSizeSm,
badgeStatusSize,
badgeProcessingDuration: '1.2s',
badgeRibbonOffset: marginXS,
// Follow token just by Design. Not related with token
badgeRibbonCornerTransform: 'scaleY(0.75)',
badgeRibbonCornerFilter: `brightness(75%)`,
});
return [genSharedBadgeStyle(badgeToken)];
});

View File

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

View File

@ -1,81 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@ribbon-prefix-cls: ~'@{ant-prefix}-ribbon';
@ribbon-wrapper-prefix-cls: ~'@{ant-prefix}-ribbon-wrapper';
.@{ribbon-wrapper-prefix-cls} {
position: relative;
}
.@{ribbon-prefix-cls} {
.reset-component();
position: absolute;
top: 8px;
height: 22px;
padding: 0 8px;
color: @badge-text-color;
line-height: 22px;
white-space: nowrap;
background-color: @primary-color;
border-radius: @border-radius-sm;
&-text {
color: @white;
}
&-corner {
position: absolute;
top: 100%;
width: 8px;
height: 8px;
color: currentcolor;
border: 4px solid;
transform: scaleY(0.75);
transform-origin: top;
// If not support IE 11, use filter: brightness(75%) instead
&::after {
position: absolute;
top: -4px;
left: -4px;
width: inherit;
height: inherit;
color: rgba(0, 0, 0, 0.25);
border: inherit;
content: '';
}
}
// colors
// mixin to iterate over colors and create CSS class for each one
.make-color-classes(@i: length(@preset-colors)) when (@i > 0) {
.make-color-classes(@i - 1);
@color: extract(@preset-colors, @i);
@darkColor: '@{color}-6';
&-color-@{color} {
color: @@darkColor;
background: @@darkColor;
}
}
.make-color-classes();
// placement
&.@{ribbon-prefix-cls}-placement-end {
right: -8px;
border-bottom-right-radius: 0;
.@{ribbon-prefix-cls}-corner {
right: 0;
border-color: currentcolor transparent transparent currentcolor;
}
}
&.@{ribbon-prefix-cls}-placement-start {
left: -8px;
border-bottom-left-radius: 0;
.@{ribbon-prefix-cls}-corner {
left: 0;
border-color: currentcolor currentcolor transparent transparent;
}
}
}

View File

@ -1,100 +0,0 @@
.@{badge-prefix-cls} {
&-rtl {
direction: rtl;
}
&:not(&-not-a-wrapper) &-count,
&:not(&-not-a-wrapper) &-dot,
&:not(&-not-a-wrapper) .@{number-prefix-cls}-custom-component {
.@{badge-prefix-cls}-rtl& {
right: auto;
left: 0;
direction: ltr;
transform: translate(-50%, -50%);
transform-origin: 0% 0%;
}
}
&-rtl&:not(&-not-a-wrapper) .@{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;
}
}
}
&:not(&-not-a-wrapper).@{badge-prefix-cls}-rtl {
.@{badge-prefix-cls}-zoom-appear,
.@{badge-prefix-cls}-zoom-enter {
animation-name: antZoomBadgeInRtl;
}
.@{badge-prefix-cls}-zoom-leave {
animation-name: antZoomBadgeOutRtl;
}
}
}
.@{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;
}
}

View File

@ -1,5 +0,0 @@
import { PresetColorTypes } from '../_util/colors';
export function isPresetColor(color?: string): boolean {
return (PresetColorTypes as any[]).indexOf(color) !== -1;
}

View File

@ -7,7 +7,7 @@ import './tag/style';
import './rate/style';
import './pagination/style';
// import './avatar/style';
import './badge/style';
// import './badge/style';
import './tabs/style';
import './input/style';
import './tooltip/style';

View File

@ -5,18 +5,15 @@ import PropTypes from '../_util/vue-types';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import Wave from '../_util/wave';
import type { PresetColorType, PresetStatusColorType } from '../_util/colors';
import { PresetColorTypes, PresetStatusColorTypes } from '../_util/colors';
import { isPresetColor, isPresetStatusColor } from '../_util/colors';
import type { LiteralUnion } from '../_util/type';
import CheckableTag from './CheckableTag';
import useConfigInject from '../_util/hooks/useConfigInject';
const PresetColorRegex = new RegExp(`^(${PresetColorTypes.join('|')})(-inverse)?$`);
const PresetStatusColorRegex = new RegExp(`^(${PresetStatusColorTypes.join('|')})$`);
export const tagProps = () => ({
prefixCls: String,
color: {
type: String as PropType<LiteralUnion<PresetColorType | PresetStatusColorType, string>>,
type: String as PropType<LiteralUnion<PresetColorType | PresetStatusColorType>>,
},
closable: { type: Boolean, default: false },
closeIcon: PropTypes.any,
@ -60,18 +57,22 @@ const Tag = defineComponent({
}
};
const isPresetColor = computed(() => {
const { color } = props;
if (!color) {
return false;
}
return PresetColorRegex.test(color) || PresetStatusColorRegex.test(color);
});
// const isPresetColor = computed(() => {
// const { color } = props;
// if (!color) {
// return false;
// }
// return PresetColorRegex.test(color) || PresetStatusColorRegex.test(color);
// });
const isInternalColor = computed(
() => isPresetColor(props.color) || isPresetStatusColor(props.color),
);
const tagClassName = computed(() =>
classNames(prefixCls.value, {
[`${prefixCls.value}-${props.color}`]: isPresetColor.value,
[`${prefixCls.value}-has-color`]: props.color && !isPresetColor.value,
[`${prefixCls.value}-${props.color}`]: isInternalColor.value,
[`${prefixCls.value}-has-color`]: props.color && !isInternalColor.value,
[`${prefixCls.value}-hidden`]: !visible.value,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}),
@ -99,7 +100,7 @@ const Tag = defineComponent({
};
const tagStyle = {
backgroundColor: color && !isPresetColor.value ? color : undefined,
backgroundColor: color && !isInternalColor.value ? color : undefined,
};
const iconNode = icon || null;

View File

@ -1,9 +1,8 @@
import type { ExtractPropTypes, CSSProperties } from 'vue';
import type { ExtractPropTypes } from 'vue';
import { computed, watch, defineComponent, onMounted, ref } from 'vue';
import VcTooltip from '../vc-tooltip';
import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
import { PresetColorTypes } from '../_util/colors';
import warning from '../_util/warning';
import { getStyle, filterEmpty, isValidElement, initDefaultProps } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
@ -13,6 +12,7 @@ import useConfigInject from '../_util/hooks/useConfigInject';
import getPlacements from './placements';
import firstNotUndefined from '../_util/firstNotUndefined';
import raf from '../_util/raf';
import { parseColor } from './util';
export type { AdjustOverflow, PlacementsConfig } from './placements';
// https://github.com/react-component/tooltip
@ -39,8 +39,6 @@ const splitObject = (obj: any, keys: string[]) => {
return { picked, omitted };
};
const PresetColorRegex = new RegExp(`^(${PresetColorTypes.join('|')})(-inverse)?$`);
export const tooltipProps = () => ({
...abstractTooltipProps(),
title: PropTypes.any,
@ -76,7 +74,7 @@ export default defineComponent({
slots: ['title'],
// emits: ['update:visible', 'visibleChange'],
setup(props, { slots, emit, attrs, expose }) {
const { prefixCls, getPopupContainer } = useConfigInject('tooltip', props);
const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props);
const visible = ref(firstNotUndefined([props.visible, props.defaultVisible]));
@ -216,9 +214,9 @@ export default defineComponent({
}
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
};
const colorInfo = computed(() => parseColor(prefixCls.value, props.color));
return () => {
const { openClassName, color, overlayClassName } = props;
const { openClassName, overlayClassName } = props;
let children = filterEmpty(slots.default?.()) ?? null;
children = children.length === 1 ? children[0] : children;
@ -237,15 +235,19 @@ export default defineComponent({
[openClassName || `${prefixCls.value}-open`]: true,
[child.props && child.props.class]: child.props && child.props.class,
});
const customOverlayClassName = classNames(overlayClassName, {
[`${prefixCls.value}-${color}`]: color && PresetColorRegex.test(color),
});
let formattedOverlayInnerStyle: CSSProperties;
let arrowContentStyle: CSSProperties;
if (color && !PresetColorRegex.test(color)) {
formattedOverlayInnerStyle = { backgroundColor: color };
arrowContentStyle = { '--antd-arrow-background-color': color };
}
const customOverlayClassName = classNames(
overlayClassName,
{
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
colorInfo.value.className,
);
const formattedOverlayInnerStyle = {
...props.overlayInnerStyle,
...colorInfo.value.overlayStyle,
};
const arrowContentStyle = colorInfo.value.arrowStyle;
const vcTooltipProps = {
...attrs,
...(props as TooltipProps),

View File

@ -2,7 +2,8 @@ import type { CSSProperties, PropType } from 'vue';
import type { AlignType, BuildInPlacements } from '../vc-trigger/interface';
import type { AdjustOverflow } from './placements';
export type TriggerType = 'hover' | 'focus' | 'click' | 'contextmenu';
import type { PresetColorType } from '../_util/colors';
import type { LiteralUnion } from '../_util/type';
export type TooltipPlacement =
| 'top'
| 'left'
@ -22,9 +23,13 @@ export default () => ({
visible: { type: Boolean, default: undefined },
defaultVisible: { type: Boolean, default: undefined },
placement: String as PropType<TooltipPlacement>,
color: String,
color: String as PropType<LiteralUnion<PresetColorType>>,
transitionName: String,
overlayStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
overlayInnerStyle: {
type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties,
},
overlayClassName: String,
openClassName: String,
prefixCls: String,

View File

@ -0,0 +1,22 @@
import type { CSSProperties } from 'vue';
import classNames from '../_util/classNames';
import { isPresetColor } from '../_util/colors';
export function parseColor(prefixCls: string, color?: string) {
const isInternalColor = isPresetColor(color);
const className = classNames({
[`${prefixCls}-${color}`]: color && isInternalColor,
});
const overlayStyle: CSSProperties = {};
const arrowStyle: CSSProperties = {};
if (color && !isInternalColor) {
overlayStyle.background = color;
// @ts-ignore
arrowStyle['--antd-arrow-background-color'] = color;
}
return { className, overlayStyle, arrowStyle };
}