diff --git a/components/config-provider/context.ts b/components/config-provider/context.ts index 995cb69902..cd990fd410 100644 --- a/components/config-provider/context.ts +++ b/components/config-provider/context.ts @@ -41,7 +41,7 @@ import type { TourProps } from '../tour/interface'; import type { TransferProps } from '../transfer'; import type { TreeSelectProps } from '../tree-select'; import type { RenderEmptyHandler } from './defaultRenderEmpty'; - +import type { StatisticProps } from '../statistic'; export const defaultPrefixCls = 'ant'; export const defaultIconPrefixCls = 'anticon'; @@ -201,6 +201,8 @@ export type SliderConfig = ComponentStyleConfig & Pick; +export type StatisticConfig = ComponentStyleConfig & Pick; + export type ResultConfig = ComponentStyleConfig & Pick; export type InputNumberConfig = ComponentStyleConfig & Pick; @@ -286,7 +288,7 @@ export interface ConfigConsumerProps { spin?: SpinConfig; segmented?: ComponentStyleConfig; steps?: ComponentStyleConfig; - statistic?: ComponentStyleConfig; + statistic?: StatisticConfig; image?: ImageConfig; layout?: ComponentStyleConfig; list?: ListConfig; diff --git a/components/config-provider/index.en-US.md b/components/config-provider/index.en-US.md index b4e9cd0813..5d87bc9803 100644 --- a/components/config-provider/index.en-US.md +++ b/components/config-provider/index.en-US.md @@ -154,7 +154,7 @@ const { | space | Set Space common props, ref [Space](/components/space) | { size: `small` \| `middle` \| `large` \| `number`, className?: string, style?: React.CSSProperties, classNames?: [SpaceProps\["classNames"\]](/components/space#api), styles?: [SpaceProps\["styles"\]](/components/space#api) } | - | 5.6.0 | | splitter | Set Splitter common props | { className?: string, style?: React.CSSProperties } | - | 5.21.0 | | spin | Set Spin common props | { className?: string, style?: React.CSSProperties, indicator?: React.ReactElement } | - | 5.7.0, indicator: 5.20.0 | -| statistic | Set Statistic common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | +| statistic | Set Statistic common props | { className?: string, style?: React.CSSProperties, classNames?: [StatisticProps\["classNames"\]](/components/statistic#semantic-dom), styles?: [StatisticProps\["styles"\]](/components/statistic#semantic-dom) | - | 5.7.0, `classNames` and `styles`: 6.0.0 | | steps | Set Steps common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | table | Set Table common props | { className?: string, style?: React.CSSProperties, expandable?: { expandIcon?: props => React.ReactNode } } | - | 5.7.0, expandable: 5.14.0 | | tabs | Set Tabs common props | { className?: string, style?: React.CSSProperties, indicator?: { size?: GetIndicatorSize, align?: `start` \| `center` \| `end` }, moreIcon?: ReactNode, addIcon?: ReactNode, removeIcon?: ReactNode } | - | 5.7.0, `moreIcon` and `addIcon`: 5.14.0, `removeIcon`: 5.15.0 | diff --git a/components/config-provider/index.zh-CN.md b/components/config-provider/index.zh-CN.md index 81c23d6df6..0bfec91726 100644 --- a/components/config-provider/index.zh-CN.md +++ b/components/config-provider/index.zh-CN.md @@ -156,7 +156,7 @@ const { | space | 设置 Space 的通用属性,参考 [Space](/components/space-cn) | { size: `small` \| `middle` \| `large` \| `number`, className?: string, style?: React.CSSProperties, classNames?: [SpaceProps\["classNames"\]](/components/space-cn#api), styles?: [SpaceProps\["styles"\]](/components/space-cn#api) } | - | 5.6.0 | | splitter | 设置 Splitter 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.21.0 | | spin | 设置 Spin 组件的通用属性 | { className?: string, style?: React.CSSProperties, indicator?: React.ReactElement } | - | 5.7.0, indicator: 5.20.0 | -| statistic | 设置 Statistic 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | +| statistic | 设置 Statistic 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [StatisticProps\["classNames"\]](/components/statistic-cn#semantic-dom), styles?: [StatisticProps\["styles"\]](/components/statistic-cn#semantic-dom) } | - | 5.7.0, `classNames` 和 `styles`: 6.0.0 | | steps | 设置 Steps 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | table | 设置 Table 组件的通用属性 | { className?: string, style?: React.CSSProperties, expandable?: { expandIcon?: props => React.ReactNode } } | - | 5.7.0, expandable: 5.14.0 | | tabs | 设置 Tabs 组件的通用属性 | { className?: string, style?: React.CSSProperties, indicator?: { size?: GetIndicatorSize, align?: `start` \| `center` \| `end` }, moreIcon?: ReactNode, addIcon?: ReactNode, removeIcon?: ReactNode } | - | 5.7.0, `moreIcon` and `addIcon`: 5.14.0, `removeIcon`: 5.15.0 | diff --git a/components/statistic/Statistic.tsx b/components/statistic/Statistic.tsx index 8da2e910d5..1a52c407ca 100644 --- a/components/statistic/Statistic.tsx +++ b/components/statistic/Statistic.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import pickAttrs from 'rc-util/lib/pickAttrs'; import type { HTMLAriaDataAttributes } from '../_util/aria-data-attrs'; +import { devUseWarning } from '../_util/warning'; import type { ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider'; import Skeleton from '../skeleton'; @@ -10,12 +11,16 @@ import StatisticNumber from './Number'; import useStyle from './style'; import type { FormatConfig, valueType } from './utils'; +export type SemanticName = 'root' | 'content' | 'title' | 'header' | 'prefix' | 'suffix'; interface StatisticReactProps extends FormatConfig { prefixCls?: string; className?: string; + classNames?: Partial>; + styles?: Partial>; rootClassName?: string; style?: React.CSSProperties; value?: valueType; + /** @deprecated Please use `styles={{ content: { } }}` instead */ valueStyle?: React.CSSProperties; valueRender?: (node: React.ReactNode) => React.ReactNode; title?: React.ReactNode; @@ -49,6 +54,8 @@ const Statistic: React.FC = (props) => { /* --- FormatConfig starts --- */ onMouseEnter, onMouseLeave, + styles, + classNames: statisticClassNames, ...rest } = props; @@ -59,6 +66,15 @@ const Statistic: React.FC = (props) => { const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); + // ============================= Warning ============================== + if (process.env.NODE_ENV !== 'production') { + const warning = devUseWarning('Statistic'); + + [['valueStyle', 'styles={{ content: { } }}']].forEach(([deprecatedName, newName]) => { + warning.deprecated(!(deprecatedName in props), deprecatedName, newName); + }); + } + const valueNode: React.ReactNode = ( = (props) => { /> ); - const cls = classNames( + const rootClassNames = classNames( prefixCls, { [`${prefixCls}-rtl`]: direction === 'rtl', @@ -78,26 +94,87 @@ const Statistic: React.FC = (props) => { statistic?.className, className, rootClassName, + statistic?.classNames?.root, + statisticClassNames?.root, hashId, cssVarCls, ); + const headerClassNames = classNames( + `${prefixCls}-header`, + statistic?.classNames?.header, + statisticClassNames?.header, + ); + + const titleClassNames = classNames( + `${prefixCls}-title`, + statistic?.classNames?.title, + statisticClassNames?.title, + ); + + const contentClassNames = classNames( + `${prefixCls}-content`, + statistic?.classNames?.content, + statisticClassNames?.content, + ); + + const prefixClassNames = classNames( + `${prefixCls}-content-prefix`, + statistic?.classNames?.prefix, + statisticClassNames?.prefix, + ); + + const suffixClassNames = classNames( + `${prefixCls}-content-suffix`, + statistic?.classNames?.suffix, + statisticClassNames?.suffix, + ); + const restProps = pickAttrs(rest, { aria: true, data: true }); return wrapCSSVar(
- {title &&
{title}
} + {title && ( +
+
+ {title} +
+
+ )} -
- {prefix && {prefix}} +
+ {prefix && ( + + {prefix} + + )} {valueRender ? valueRender(valueNode) : valueNode} - {suffix && {suffix}} + {suffix && ( + + {suffix} + + )}
, diff --git a/components/statistic/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/statistic/__tests__/__snapshots__/demo-extend.test.ts.snap index 4e584389ec..9c371720e1 100644 --- a/components/statistic/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/statistic/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -13,9 +13,13 @@ exports[`renders components/statistic/demo/animated.tsx extend context correctly class="ant-statistic" >
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Active +
+ Active +
- Idle +
+ Idle +
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Countdown +
+ Countdown +
- Million Seconds +
+ Million Seconds +
- Day Level +
+ Day Level +
- Countdown +
+ Countdown +
- Feedback +
+ Feedback +
- Unmerged +
+ Unmerged +
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Active +
+ Active +
- Idle +
+ Idle +
- Active Users +
+ Active Users +
- Account Balance (CNY) +
+ Account Balance (CNY) +
- Active Users +
+ Active Users +
- Countdown +
+ Countdown +
- Million Seconds +
+ Million Seconds +
- Day Level +
+ Day Level +
- Countdown +
+ Countdown +
- Feedback +
+ Feedback +
- Unmerged +
+ Unmerged +
- Account Balance (CNY) +
+ Account Balance (CNY) +
{ expect(formatTimeStr(1000 * 60 * 60 * 24, 'D [Day]')).toBe('1 Day'); }); }); + + it('should apply custom styles to Statistic', () => { + const customClassNames = { + root: 'custom-root', + header: 'custom-header', + title: 'custom-title', + content: 'custom-content', + prefix: 'custom-prefix', + suffix: 'custom-suffix', + }; + + const customStyles = { + root: { color: 'red' }, + header: { paddingBottom: '10px' }, + title: { backgroundColor: 'blue' }, + content: { backgroundColor: 'green' }, + prefix: { color: 'yellow' }, + suffix: { color: 'purple' }, + }; + + const { container } = render( + , + ); + + const rootElement = container.querySelector('.ant-statistic') as HTMLElement; + const headerElement = container.querySelector('.ant-statistic-header') as HTMLElement; + const titleElement = container.querySelector('.ant-statistic-title') as HTMLElement; + const contentElement = container.querySelector('.ant-statistic-content') as HTMLElement; + const prefixElement = container.querySelector('.ant-statistic-content-prefix') as HTMLElement; + const suffixElement = container.querySelector('.ant-statistic-content-suffix') as HTMLElement; + + // check classNames + expect(rootElement.classList).toContain('custom-root'); + expect(headerElement.classList).toContain('custom-header'); + expect(titleElement.classList).toContain('custom-title'); + expect(contentElement.classList).toContain('custom-content'); + expect(prefixElement.classList).toContain('custom-prefix'); + expect(suffixElement.classList).toContain('custom-suffix'); + + // check styles + expect(rootElement.style.color).toBe('red'); + expect(headerElement.style.paddingBottom).toBe('10px'); + expect(titleElement.style.backgroundColor).toBe('blue'); + expect(contentElement.style.backgroundColor).toBe('green'); + expect(prefixElement.style.color).toBe('yellow'); + expect(suffixElement.style.color).toBe('purple'); + }); }); diff --git a/components/statistic/demo/_semantic.tsx b/components/statistic/demo/_semantic.tsx new file mode 100644 index 0000000000..0747a66aee --- /dev/null +++ b/components/statistic/demo/_semantic.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { ArrowUpOutlined } from '@ant-design/icons'; +import { Statistic } from 'antd'; + +import SemanticPreview from '../../../.dumi/components/SemanticPreview'; +import useLocale from '../../../.dumi/hooks/useLocale'; + +const locales = { + cn: { + root: '根节点', + header: '头部元素', + title: '标题元素', + content: '内容元素', + prefix: '前缀元素', + suffix: '后缀元素', + }, + en: { + root: 'Root element', + header: 'Header element', + title: 'Title element', + content: 'Content element', + prefix: 'Prefix element', + suffix: 'Suffix element', + }, +}; + +const BlockList: React.FC = (props: any) => { + const divRef = React.useRef(null); + + return ( +
+ } + suffix="%" + {...props} + /> +
+ ); +}; + +const App: React.FC = () => { + const [locale] = useLocale(locales); + return ( + + + + ); +}; + +export default App; diff --git a/components/statistic/demo/card.tsx b/components/statistic/demo/card.tsx index 52e6b11041..58f23cb357 100644 --- a/components/statistic/demo/card.tsx +++ b/components/statistic/demo/card.tsx @@ -10,7 +10,7 @@ const App: React.FC = () => ( title="Active" value={11.28} precision={2} - valueStyle={{ color: '#3f8600' }} + styles={{ content: { color: '#3f8600' } }} prefix={} suffix="%" /> @@ -22,7 +22,7 @@ const App: React.FC = () => ( title="Idle" value={9.3} precision={2} - valueStyle={{ color: '#cf1322' }} + styles={{ content: { color: '#cf1322' } }} prefix={} suffix="%" /> diff --git a/components/statistic/index.en-US.md b/components/statistic/index.en-US.md index 15e8664c15..8cb83d9fff 100644 --- a/components/statistic/index.en-US.md +++ b/components/statistic/index.en-US.md @@ -56,6 +56,10 @@ Common props ref:[Common props](/docs/react/common-props) | onFinish | Trigger when time's up | () => void | - | | | onChange | Trigger when time's changing | (value: number) => void | - | 4.16.0 | +## Semantic DOM + + + ## Design Token diff --git a/components/statistic/index.zh-CN.md b/components/statistic/index.zh-CN.md index 523fe17344..5f7e901c73 100644 --- a/components/statistic/index.zh-CN.md +++ b/components/statistic/index.zh-CN.md @@ -57,6 +57,10 @@ demo: | onFinish | 倒计时完成时触发 | () => void | - | | | onChange | 倒计时时间变化时触发 | (value: number) => void | - | 4.16.0 | +## Semantic DOM + + + ## 主题变量(Design Token) diff --git a/components/statistic/style/index.ts b/components/statistic/style/index.ts index add666e372..2c1e1f72c2 100644 --- a/components/statistic/style/index.ts +++ b/components/statistic/style/index.ts @@ -34,10 +34,12 @@ const genStatisticStyle: GenerateStyle = (token: StatisticToken) return { [componentCls]: { ...resetComponent(token), - [`${componentCls}-title`]: { - marginBottom: marginXXS, - color: colorTextDescription, - fontSize: titleFontSize, + [`${componentCls}-header`]: { + paddingBottom: marginXXS, + [`${componentCls}-title`]: { + color: colorTextDescription, + fontSize: titleFontSize, + }, }, [`${componentCls}-skeleton`]: {