mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
feat(badge): Support better customization with semantic classNames/styles as function (#54977)
* feat(badge): Support better customization with semantic classNames/styles as function * update demo and snap * update docs * feat(badge): Support better customization with semantic classNames/styles as function * update demo and snap * update docs * chore: update demo and snap * chore: update demo * chore: update demo --------- Co-authored-by: thinkasany <480968828@qq.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import classNames from 'classnames';
|
||||
import type { PresetColorType } from '../_util/colors';
|
||||
import { isPresetColor } from '../_util/colors';
|
||||
import type { LiteralUnion } from '../_util/type';
|
||||
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
|
||||
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks/useMergeSemantic';
|
||||
import useStyle from './style/ribbon';
|
||||
import { useComponentConfig } from '../config-provider/context';
|
||||
|
||||
@@ -11,6 +13,9 @@ type RibbonPlacement = 'start' | 'end';
|
||||
|
||||
type SemanticName = 'root' | 'content' | 'indicator';
|
||||
|
||||
export type RibbonClassNamesType = SemanticClassNamesType<RibbonProps, SemanticName>;
|
||||
export type RibbonStylesType = SemanticStylesType<RibbonProps, SemanticName>;
|
||||
|
||||
export interface RibbonProps {
|
||||
className?: string;
|
||||
prefixCls?: string;
|
||||
@@ -20,8 +25,8 @@ export interface RibbonProps {
|
||||
children?: React.ReactNode;
|
||||
placement?: RibbonPlacement;
|
||||
rootClassName?: string;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
classNames?: RibbonClassNamesType;
|
||||
styles?: RibbonStylesType;
|
||||
}
|
||||
|
||||
const Ribbon: React.FC<RibbonProps> = (props) => {
|
||||
@@ -50,6 +55,20 @@ const Ribbon: React.FC<RibbonProps> = (props) => {
|
||||
const wrapperCls = `${prefixCls}-wrapper`;
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls, wrapperCls);
|
||||
|
||||
// =========== Merged Props for Semantic ===========
|
||||
const mergedProps: RibbonProps = {
|
||||
...props,
|
||||
placement,
|
||||
};
|
||||
|
||||
const [mergedClassNames, mergedStyles] = useMergeSemantic<
|
||||
RibbonClassNamesType,
|
||||
RibbonStylesType,
|
||||
RibbonProps
|
||||
>([contextClassNames, ribbonClassNames], [contextStyles, styles], undefined, {
|
||||
props: mergedProps,
|
||||
});
|
||||
|
||||
const colorInPreset = isPresetColor(color, false);
|
||||
const ribbonCls = classNames(
|
||||
prefixCls,
|
||||
@@ -60,8 +79,7 @@ const Ribbon: React.FC<RibbonProps> = (props) => {
|
||||
},
|
||||
className,
|
||||
contextClassName,
|
||||
contextClassNames.indicator,
|
||||
ribbonClassNames?.indicator,
|
||||
mergedClassNames.indicator,
|
||||
);
|
||||
|
||||
const colorStyle: React.CSSProperties = {};
|
||||
@@ -72,34 +90,22 @@ const Ribbon: React.FC<RibbonProps> = (props) => {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
wrapperCls,
|
||||
rootClassName,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
ribbonClassNames?.root,
|
||||
contextClassNames.root,
|
||||
)}
|
||||
style={{ ...contextStyles.root, ...styles?.root }}
|
||||
className={classNames(wrapperCls, rootClassName, hashId, cssVarCls, mergedClassNames.root)}
|
||||
style={mergedStyles.root}
|
||||
>
|
||||
{children}
|
||||
<div
|
||||
className={classNames(ribbonCls, hashId)}
|
||||
style={{
|
||||
...colorStyle,
|
||||
...contextStyles.indicator,
|
||||
...mergedStyles.indicator,
|
||||
...contextStyle,
|
||||
...styles?.indicator,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
`${prefixCls}-content`,
|
||||
ribbonClassNames?.content,
|
||||
contextClassNames.content,
|
||||
)}
|
||||
style={{ ...contextStyles.content, ...styles?.content }}
|
||||
className={classNames(`${prefixCls}-content`, mergedClassNames.content)}
|
||||
style={mergedStyles.content}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
|
||||
@@ -3060,6 +3060,173 @@ Array [
|
||||
|
||||
exports[`renders components/badge/demo/status.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/badge/demo/style-class.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-large ant-space-gap-col-large css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-gap-middle"
|
||||
>
|
||||
<span
|
||||
class="ant-badge css-var-test-id"
|
||||
style="background-color: rgb(240, 240, 240);"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-lg ant-avatar-square css-var-test-id ant-avatar-css-var"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="-webkit-transform: scale(1); transform: scale(1);"
|
||||
/>
|
||||
</span>
|
||||
<sup
|
||||
class="ant-scroll-number acss-9c7r58 ant-badge-count ant-badge-count-sm"
|
||||
data-show="true"
|
||||
title="5"
|
||||
>
|
||||
<bdi>
|
||||
<span
|
||||
class="ant-scroll-number-only"
|
||||
style="transition: none;"
|
||||
>
|
||||
<span
|
||||
class="ant-scroll-number-only-unit current"
|
||||
>
|
||||
5
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</sup>
|
||||
</span>
|
||||
<span
|
||||
class="ant-badge css-var-test-id"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-lg ant-avatar-square css-var-test-id ant-avatar-css-var"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="-webkit-transform: scale(1); transform: scale(1);"
|
||||
/>
|
||||
</span>
|
||||
<sup
|
||||
class="ant-scroll-number acss-9c7r58 ant-badge-count"
|
||||
data-show="true"
|
||||
style="font-size: 14px; background-color: rgb(105, 111, 199);"
|
||||
title="5"
|
||||
>
|
||||
<bdi>
|
||||
<span
|
||||
class="ant-scroll-number-only"
|
||||
style="transition: none;"
|
||||
>
|
||||
<span
|
||||
class="ant-scroll-number-only-unit current"
|
||||
>
|
||||
5
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</sup>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-ribbon-wrapper css-var-test-id acss-19a7de9"
|
||||
>
|
||||
<div
|
||||
class="ant-card ant-card-bordered ant-card-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-title"
|
||||
>
|
||||
Card with custom ribbon
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-card-body"
|
||||
>
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon ant-ribbon-placement-end"
|
||||
style="box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
|
||||
>
|
||||
<span
|
||||
class="ant-ribbon-content"
|
||||
>
|
||||
Custom Ribbon
|
||||
</span>
|
||||
<div
|
||||
class="ant-ribbon-corner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon-wrapper css-var-test-id acss-19a7de9"
|
||||
>
|
||||
<div
|
||||
class="ant-card ant-card-bordered ant-card-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-title"
|
||||
>
|
||||
Card with custom ribbon
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-card-body"
|
||||
>
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon ant-ribbon-placement-end"
|
||||
style="background: rgb(105, 111, 199); box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
|
||||
>
|
||||
<span
|
||||
class="ant-ribbon-content"
|
||||
style="font-weight: bold;"
|
||||
>
|
||||
Custom Ribbon
|
||||
</span>
|
||||
<div
|
||||
class="ant-ribbon-corner"
|
||||
style="color: rgb(105, 111, 199);"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/badge/demo/style-class.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/badge/demo/title.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-large ant-space-gap-col-large css-var-test-id"
|
||||
|
||||
@@ -3030,6 +3030,171 @@ Array [
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/badge/demo/style-class.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-large ant-space-gap-col-large css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-gap-middle"
|
||||
>
|
||||
<span
|
||||
class="ant-badge css-var-test-id"
|
||||
style="background-color:#f0f0f0"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-lg ant-avatar-square css-var-test-id ant-avatar-css-var"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="opacity:0"
|
||||
/>
|
||||
</span>
|
||||
<sup
|
||||
class="ant-scroll-number acss-9c7r58 ant-badge-count ant-badge-count-sm"
|
||||
data-show="true"
|
||||
title="5"
|
||||
>
|
||||
<bdi>
|
||||
<span
|
||||
class="ant-scroll-number-only"
|
||||
style="transition:none"
|
||||
>
|
||||
<span
|
||||
class="ant-scroll-number-only-unit current"
|
||||
>
|
||||
5
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</sup>
|
||||
</span>
|
||||
<span
|
||||
class="ant-badge css-var-test-id"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-lg ant-avatar-square css-var-test-id ant-avatar-css-var"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="opacity:0"
|
||||
/>
|
||||
</span>
|
||||
<sup
|
||||
class="ant-scroll-number acss-9c7r58 ant-badge-count"
|
||||
data-show="true"
|
||||
style="font-size:14px;background-color:#696FC7"
|
||||
title="5"
|
||||
>
|
||||
<bdi>
|
||||
<span
|
||||
class="ant-scroll-number-only"
|
||||
style="transition:none"
|
||||
>
|
||||
<span
|
||||
class="ant-scroll-number-only-unit current"
|
||||
>
|
||||
5
|
||||
</span>
|
||||
</span>
|
||||
</bdi>
|
||||
</sup>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-ribbon-wrapper css-var-test-id acss-19a7de9"
|
||||
>
|
||||
<div
|
||||
class="ant-card ant-card-bordered ant-card-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-title"
|
||||
>
|
||||
Card with custom ribbon
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-card-body"
|
||||
>
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon ant-ribbon-placement-end"
|
||||
style="box-shadow:0 2px 4px rgba(0,0,0,0.1)"
|
||||
>
|
||||
<span
|
||||
class="ant-ribbon-content"
|
||||
>
|
||||
Custom Ribbon
|
||||
</span>
|
||||
<div
|
||||
class="ant-ribbon-corner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon-wrapper css-var-test-id acss-19a7de9"
|
||||
>
|
||||
<div
|
||||
class="ant-card ant-card-bordered ant-card-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-wrapper"
|
||||
>
|
||||
<div
|
||||
class="ant-card-head-title"
|
||||
>
|
||||
Card with custom ribbon
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-card-body"
|
||||
>
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-ribbon ant-ribbon-placement-end"
|
||||
style="background:#696FC7;box-shadow:0 2px 4px rgba(0,0,0,0.1)"
|
||||
>
|
||||
<span
|
||||
class="ant-ribbon-content"
|
||||
style="font-weight:bold"
|
||||
>
|
||||
Custom Ribbon
|
||||
</span>
|
||||
<div
|
||||
class="ant-ribbon-corner"
|
||||
style="color:#696FC7"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/badge/demo/title.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-large ant-space-gap-col-large css-var-test-id"
|
||||
|
||||
@@ -272,4 +272,33 @@ describe('Badge', () => {
|
||||
backgroundColor: 'rgb(0, 0, 255)',
|
||||
});
|
||||
});
|
||||
|
||||
it('should support function-based semantic classNames and styles', () => {
|
||||
const { container } = render(
|
||||
<Badge
|
||||
count={5}
|
||||
size="small"
|
||||
classNames={({ props }) => ({
|
||||
root: `badge-${props.size}`,
|
||||
indicator: 'indicator-small',
|
||||
})}
|
||||
styles={({ props }) => ({
|
||||
root: { padding: props.size === 'small' ? '2px' : '4px' },
|
||||
indicator: { fontSize: '10px' },
|
||||
})}
|
||||
>
|
||||
test
|
||||
</Badge>,
|
||||
);
|
||||
|
||||
const element = container.querySelector<HTMLSpanElement>('.ant-badge');
|
||||
|
||||
// function-based classNames
|
||||
expect(element).toHaveClass('badge-small');
|
||||
expect(element?.querySelector<HTMLElement>('sup')).toHaveClass('indicator-small');
|
||||
|
||||
// function-based styles
|
||||
expect(element).toHaveStyle({ padding: '2px' });
|
||||
expect(element?.querySelector<HTMLElement>('sup')).toHaveStyle({ fontSize: '10px' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,4 +113,40 @@ describe('Ribbon', () => {
|
||||
expect(indicatorElement.style.color).toBe('green');
|
||||
expect(contentElement.style.color).toBe('yellow');
|
||||
});
|
||||
|
||||
it('should support function-based classNames and styles', () => {
|
||||
const { container } = render(
|
||||
<Badge.Ribbon
|
||||
text="Test"
|
||||
color="blue"
|
||||
placement="start"
|
||||
classNames={({ props }) => ({
|
||||
root: `ribbon-${props.placement}`,
|
||||
indicator: 'ribbon-indicator',
|
||||
content: 'ribbon-content',
|
||||
})}
|
||||
styles={({ props }) => ({
|
||||
root: { border: props.placement === 'start' ? '1px solid red' : '1px solid blue' },
|
||||
indicator: { opacity: '0.8' },
|
||||
content: { fontWeight: 'bold' },
|
||||
})}
|
||||
>
|
||||
<div>Test content</div>
|
||||
</Badge.Ribbon>,
|
||||
);
|
||||
|
||||
const rootElement = container.querySelector('.ant-ribbon-wrapper') as HTMLElement;
|
||||
const indicatorElement = container.querySelector('.ant-ribbon') as HTMLElement;
|
||||
const contentElement = container.querySelector('.ant-ribbon-content') as HTMLElement;
|
||||
|
||||
// check function-based classNames
|
||||
expect(rootElement.classList).toContain('ribbon-start');
|
||||
expect(indicatorElement.classList).toContain('ribbon-indicator');
|
||||
expect(contentElement.classList).toContain('ribbon-content');
|
||||
|
||||
// check function-based styles
|
||||
expect(rootElement.style.border).toBe('1px solid red');
|
||||
expect(indicatorElement.style.opacity).toBe('0.8');
|
||||
expect(contentElement.style.fontWeight).toBe('bold');
|
||||
});
|
||||
});
|
||||
|
||||
7
components/badge/demo/style-class.md
Normal file
7
components/badge/demo/style-class.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
通过 `classNames` 和 `styles` 传入对象/函数可以自定义 Badge 的[语义化结构](#semantic-dom)样式。
|
||||
|
||||
## en-US
|
||||
|
||||
You can customize the [semantic dom](#semantic-dom) style of Badge by passing objects/functions through `classNames` and `styles`.
|
||||
94
components/badge/demo/style-class.tsx
Normal file
94
components/badge/demo/style-class.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { Avatar, Badge, Card, Flex, Space } from 'antd';
|
||||
import type { BadgeProps } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import type { RibbonProps } from 'antd/es/badge/Ribbon';
|
||||
|
||||
const useStylesBadge = createStyles(() => ({
|
||||
indicator: {
|
||||
fontSize: 10,
|
||||
},
|
||||
}));
|
||||
|
||||
const useStylesRibbon = createStyles(() => ({
|
||||
root: {
|
||||
width: 400,
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: 10,
|
||||
},
|
||||
}));
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { styles: badgeClassNames } = useStylesBadge();
|
||||
const { styles: ribbonClassNames } = useStylesRibbon();
|
||||
const badgeStyles: BadgeProps['styles'] = {
|
||||
root: {
|
||||
backgroundColor: '#f0f0f0',
|
||||
},
|
||||
};
|
||||
|
||||
const badgeStylesFn: BadgeProps['styles'] = (info) => {
|
||||
if (info.props.size === 'default') {
|
||||
return {
|
||||
indicator: {
|
||||
fontSize: 14,
|
||||
backgroundColor: '#696FC7',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const ribbonStyles: RibbonProps['styles'] = {
|
||||
indicator: {
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
},
|
||||
};
|
||||
|
||||
const ribbonStylesFn: RibbonProps['styles'] = (info) => {
|
||||
if (info.props.color === '#696FC7') {
|
||||
return {
|
||||
content: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
indicator: {
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
return (
|
||||
<Space size="large" vertical>
|
||||
<Flex gap="middle">
|
||||
<Badge size="small" count={5} classNames={badgeClassNames} styles={badgeStyles}>
|
||||
<Avatar shape="square" size="large" />
|
||||
</Badge>
|
||||
<Badge count={5} classNames={badgeClassNames} styles={badgeStylesFn}>
|
||||
<Avatar shape="square" size="large" />
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Flex vertical gap="middle">
|
||||
<Badge.Ribbon text="Custom Ribbon" classNames={ribbonClassNames} styles={ribbonStyles}>
|
||||
<Card title="Card with custom ribbon" size="small">
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</Card>
|
||||
</Badge.Ribbon>
|
||||
|
||||
<Badge.Ribbon
|
||||
text="Custom Ribbon"
|
||||
color="#696FC7"
|
||||
classNames={ribbonClassNames}
|
||||
styles={ribbonStylesFn}
|
||||
>
|
||||
<Card title="Card with custom ribbon" size="small">
|
||||
This card has a customized ribbon with semantic classNames and styles.
|
||||
</Card>
|
||||
</Badge.Ribbon>
|
||||
</Flex>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -27,6 +27,7 @@ Badge normally appears in proximity to notifications or user avatars with eye-ca
|
||||
<code src="./demo/status.tsx">Status</code>
|
||||
<code src="./demo/colorful.tsx">Colorful Badge</code>
|
||||
<code src="./demo/ribbon.tsx">Ribbon</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">Custom semantic dom styling</code>
|
||||
<code src="./demo/ribbon-debug.tsx" debug>Ribbon Debug</code>
|
||||
<code src="./demo/mix.tsx" debug>Mixed usage</code>
|
||||
<code src="./demo/title.tsx" debug>Title</code>
|
||||
@@ -43,14 +44,14 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| --- | --- | --- | --- | --- |
|
||||
| color | Customize Badge dot color | string | - | |
|
||||
| count | Number to show in badge | ReactNode | - | |
|
||||
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.7.0 |
|
||||
| classNames | Customize class for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
|
||||
| dot | Whether to display a red dot instead of `count` | boolean | false | |
|
||||
| offset | Set offset of the badge dot | \[number, number] | - | |
|
||||
| overflowCount | Max count to show | number | 99 | |
|
||||
| showZero | Whether to show badge when `count` is zero | boolean | false | |
|
||||
| size | If `count` is set, `size` sets the size of badge | `default` \| `small` | - | - |
|
||||
| status | Set Badge as a status dot | `success` \| `processing` \| `default` \| `error` \| `warning` | - | |
|
||||
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.7.0 |
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| text | If `status` is set, `text` sets the display text of the status `dot` | ReactNode | - | |
|
||||
| title | Text to show when hovering over the badge | string | - | |
|
||||
|
||||
@@ -58,8 +59,10 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| classNames | Customize class for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
|
||||
| color | Customize Ribbon color | string | - | |
|
||||
| placement | The placement of the Ribbon, `start` and `end` follow text direction (RTL or LTR) | `start` \| `end` | `end` | |
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| text | Content inside the Ribbon | ReactNode | - | |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
@@ -8,6 +8,8 @@ import { isPresetColor } from '../_util/colors';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import type { LiteralUnion } from '../_util/type';
|
||||
import type { PresetColorKey } from '../theme/internal';
|
||||
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
|
||||
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks/useMergeSemantic';
|
||||
import Ribbon from './Ribbon';
|
||||
import ScrollNumber from './ScrollNumber';
|
||||
import useStyle from './style';
|
||||
@@ -16,6 +18,10 @@ import { useComponentConfig } from '../config-provider/context';
|
||||
export type { ScrollNumberProps } from './ScrollNumber';
|
||||
|
||||
type SemanticName = 'root' | 'indicator';
|
||||
|
||||
export type BadgeClassNamesType = SemanticClassNamesType<BadgeProps, SemanticName>;
|
||||
export type BadgeStylesType = SemanticStylesType<BadgeProps, SemanticName>;
|
||||
|
||||
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
/** Number to show in badge */
|
||||
count?: React.ReactNode;
|
||||
@@ -36,8 +42,8 @@ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
offset?: [number | string, number | string];
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
classNames?: BadgeClassNamesType;
|
||||
styles?: BadgeStylesType;
|
||||
}
|
||||
|
||||
const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
|
||||
@@ -74,6 +80,21 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
// =========== Merged Props for Semantic ===========
|
||||
const mergedProps: BadgeProps = {
|
||||
...props,
|
||||
overflowCount,
|
||||
size,
|
||||
dot,
|
||||
showZero,
|
||||
};
|
||||
|
||||
const [mergedClassNames, mergedStyles] = useMergeSemantic<
|
||||
BadgeClassNamesType,
|
||||
BadgeStylesType,
|
||||
BadgeProps
|
||||
>([contextClassNames, classNames], [contextStyles, styles], undefined, { props: mergedProps });
|
||||
|
||||
// ================================ Misc ================================
|
||||
const numberedDisplayCount = (
|
||||
(count as number) > (overflowCount as number) ? `${overflowCount}+` : count
|
||||
@@ -162,7 +183,7 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
const isInternalColor = isPresetColor(color, false);
|
||||
|
||||
// Shared styles
|
||||
const statusCls = classnames(classNames?.indicator, contextClassNames.indicator, {
|
||||
const statusCls = classnames(mergedClassNames.indicator, {
|
||||
[`${prefixCls}-status-dot`]: hasStatus,
|
||||
[`${prefixCls}-status-${status}`]: !!status,
|
||||
[`${prefixCls}-color-${color}`]: isInternalColor,
|
||||
@@ -184,8 +205,7 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
className,
|
||||
rootClassName,
|
||||
contextClassName,
|
||||
contextClassNames.root,
|
||||
classNames?.root,
|
||||
mergedClassNames.root,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
);
|
||||
@@ -197,12 +217,9 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
<span
|
||||
{...restProps}
|
||||
className={badgeClassName}
|
||||
style={{ ...styles?.root, ...contextStyles.root, ...mergedStyle }}
|
||||
style={{ ...mergedStyles.root, ...mergedStyle }}
|
||||
>
|
||||
<span
|
||||
className={statusCls}
|
||||
style={{ ...styles?.indicator, ...contextStyles.indicator, ...statusStyle }}
|
||||
/>
|
||||
<span className={statusCls} style={{ ...mergedStyles.indicator, ...statusStyle }} />
|
||||
{showStatusTextNode && (
|
||||
<span style={{ color: statusTextColor }} className={`${prefixCls}-status-text`}>
|
||||
{text}
|
||||
@@ -213,12 +230,7 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
{...restProps}
|
||||
className={badgeClassName}
|
||||
style={{ ...contextStyles.root, ...styles?.root }}
|
||||
>
|
||||
<span ref={ref} {...restProps} className={badgeClassName} style={mergedStyles.root}>
|
||||
{children}
|
||||
<CSSMotion
|
||||
visible={!isHidden}
|
||||
@@ -234,7 +246,7 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
|
||||
const isDot = isDotRef.current;
|
||||
|
||||
const scrollNumberCls = classnames(classNames?.indicator, contextClassNames.indicator, {
|
||||
const scrollNumberCls = classnames(mergedClassNames.indicator, {
|
||||
[`${prefixCls}-dot`]: isDot,
|
||||
[`${prefixCls}-count`]: !isDot,
|
||||
[`${prefixCls}-count-sm`]: size === 'small',
|
||||
@@ -245,8 +257,7 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
});
|
||||
|
||||
let scrollNumberStyle: React.CSSProperties = {
|
||||
...styles?.indicator,
|
||||
...contextStyles.indicator,
|
||||
...mergedStyles.indicator,
|
||||
...mergedStyle,
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ group: 数据展示
|
||||
<code src="./demo/status.tsx">状态点</code>
|
||||
<code src="./demo/colorful.tsx">多彩徽标</code>
|
||||
<code src="./demo/ribbon.tsx">缎带</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">自定义各种语义结构的样式和类</code>
|
||||
<code src="./demo/ribbon-debug.tsx" debug>Ribbon Debug</code>
|
||||
<code src="./demo/mix.tsx" debug>各种混用的情况</code>
|
||||
<code src="./demo/title.tsx" debug>自定义标题</code>
|
||||
@@ -44,14 +45,14 @@ group: 数据展示
|
||||
| --- | --- | --- | --- | --- |
|
||||
| color | 自定义小圆点的颜色 | string | - | |
|
||||
| count | 展示的数字,大于 overflowCount 时显示为 `${overflowCount}+`,为 0 时隐藏 | ReactNode | - | |
|
||||
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.7.0 |
|
||||
| classNames | 用于自定义组件内部各语义化结构的 class,支持对象或函数 | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
|
||||
| dot | 不展示数字,只有一个小红点 | boolean | false | |
|
||||
| offset | 设置状态点的位置偏移 | \[number, number] | - | |
|
||||
| overflowCount | 展示封顶的数字值 | number | 99 | |
|
||||
| showZero | 当数值为 0 时,是否展示 Badge | boolean | false | |
|
||||
| size | 在设置了 `count` 的前提下有效,设置小圆点的大小 | `default` \| `small` | - | - |
|
||||
| status | 设置 Badge 为状态点 | `success` \| `processing` \| `default` \| `error` \| `warning` | - | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.7.0 |
|
||||
| styles | 用于自定义组件内部各语义化结构的行内 style,支持对象或函数 | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| text | 在设置了 `status` 的前提下有效,设置状态点的文本 | ReactNode | - | |
|
||||
| title | 设置鼠标放在状态点上时显示的文字 | string | - | |
|
||||
|
||||
@@ -59,8 +60,10 @@ group: 数据展示
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| classNames | 用于自定义组件内部各语义化结构的 class,支持对象或函数 | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
|
||||
| color | 自定义缎带的颜色 | string | - | |
|
||||
| placement | 缎带的位置,`start` 和 `end` 随文字方向(RTL 或 LTR)变动 | `start` \| `end` | `end` | |
|
||||
| styles | 用于自定义组件内部各语义化结构的行内 style,支持对象或函数 | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| text | 缎带中填入的内容 | ReactNode | - | |
|
||||
|
||||
## Semantic DOM
|
||||
|
||||
Reference in New Issue
Block a user