feat: ConfigProvider support qrcode (#52172)

* feat: ConfigProvider support ClassNames and styles for qrcode

* update mask to cover

* fix test

* update token from QRCodeMaskBackgroundColor to QRCodeCoverBackgroundColor

* rm

* fix typo
This commit is contained in:
thinkasany
2025-04-10 15:08:32 +08:00
committed by GitHub
parent 0a50c71d59
commit f3cebe3dd2
14 changed files with 143 additions and 33 deletions

View File

@@ -37,6 +37,7 @@ import type { ArgsProps as NotificationProps } from '../notification';
import type { PaginationProps } from '../pagination';
import type { PopconfirmProps } from '../popconfirm';
import type { PopoverProps } from '../popover';
import type { QRCodeProps } from '../qr-code';
import type { RadioProps } from '../radio';
import type { ResultProps } from '../result';
import type { SegmentedProps } from '../segmented';
@@ -271,6 +272,8 @@ export type PopconfirmConfig = Pick<
'className' | 'style' | 'styles' | 'classNames' | 'arrow'
>;
export type QRcodeConfig = ComponentStyleConfig & Pick<QRCodeProps, 'classNames' | 'styles'>;
export type SliderConfig = ComponentStyleConfig & Pick<SliderProps, 'styles' | 'classNames'>;
export type SkeletonConfig = ComponentStyleConfig & Pick<SkeletonProps, 'styles' | 'classNames'>;
@@ -406,6 +409,7 @@ export interface ConfigComponentProps {
dropdown?: ComponentStyleConfig;
flex?: FlexConfig;
wave?: WaveConfig;
qrcode?: QRcodeConfig;
watermark?: ComponentStyleConfig;
}

View File

@@ -169,7 +169,8 @@ const {
| tooltip | Set Tooltip common props | { className?: string, style?: React.CSSProperties, classNames?:[Tooltip\["classNames"\]](/components/tooltip#semantic-dom), styles?: [Tooltip\["styles"\]](/components/tooltip#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| popover | Set Popover common props | { className?: string, style?: React.CSSProperties, classNames?:[Popover\["classNames"\]](/components/popover#semantic-dom), styles?: [Popover\["styles"\]](/components/popover#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| popconfirm | Set Popconfirm common props | { className?: string, style?: React.CSSProperties, classNames?:[Popconfirm\["classNames"\]](/components/popconfirm#semantic-dom), styles?: [Popconfirm\["styles"\]](/components/popconfirm#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| transfer | Set Transfer common props | { className?: string, style?: React.CSSProperties, classNames?: [TransferConfig\["classNames"\]](/components/transfer#semantic-dom), styles?: [TransferConfig\["styles"\]](/components/transfer#semantic-dom), selectionsIcon?: React.ReactNode } | - | |
| qrcode | Set QRCode common props | { className?: string, style?: React.CSSProperties, classNames?:[QRCode\["classNames"\]](/components/qr-code#semantic-dom), styles?: [QRCode\["styles"\]](/components/qr-code#semantic-dom) } | - | |
| transfer | Set Transfer common props | { className?: string, style?: React.CSSProperties, classNames?: [TransferConfig\["classNames"\]](/components/transfer#semantic-dom), styles?: [TransferConfig\["styles"\]](/components/transfer#semantic-dom), selectionsIcon?: React.ReactNode } | - | |
| tree | Set Tree common props | { className?: string, style?: React.CSSProperties, classNames?: [TreeConfig\["classNames"\]](/components/tree#semantic-dom), styles?: [TreeConfig\["styles"\]](/components/tree#semantic-dom) } | - | 5.7.0, `classNames` and `styles`: 6.0.0 |
| treeSelect | Set TreeSelect common props | { className?: string, style?: React.CSSProperties, classNames?: [TreeSelectConfig\["classNames"\]](/components/tree-select#semantic-dom), styles?: [TreeSelectConfig\["styles"\]](/components/tree-select#semantic-dom) } | - | |
| typography | Set Typography common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |

View File

@@ -66,6 +66,7 @@ import type {
TreeSelectConfig,
Variant,
WaveConfig,
QRcodeConfig,
} from './context';
import {
ConfigConsumer,
@@ -252,6 +253,7 @@ export interface ConfigProviderProps {
popover?: PopoverConfig;
popconfirm?: PopconfirmConfig;
watermark?: ComponentStyleConfig;
qrcode?: QRcodeConfig;
}
interface ProviderChildrenProps extends ConfigProviderProps {
@@ -400,6 +402,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
tooltip,
popover,
popconfirm,
qrcode,
floatButtonGroup,
variant,
inputNumber,
@@ -508,6 +511,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
tooltip,
popover,
popconfirm,
qrcode,
floatButtonGroup,
variant,
inputNumber,

View File

@@ -171,7 +171,8 @@ const {
| tooltip | 设置 Tooltip 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Tooltip\["classNames"\]](/components/tooltip-cn#semantic-dom), styles?: [Tooltip\["styles"\]](/components/tooltip-cn#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| popover | 设置 Popover 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Popover\["classNames"\]](/components/popover-cn#semantic-dom), styles?: [Popover\["styles"\]](/components/popover-cn#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| popconfirm | 设置 Popconfirm 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[Popconfirm\["classNames"\]](/components/popconfirm-cn#semantic-dom), styles?: [Popconfirm\["styles"\]](/components/popconfirm-cn#semantic-dom), arrow: boolean \| { pointAtCenter: boolean } } | - | 5.23.0, `arrow`: 6.0.0 |
| transfer | 设置 Transfer 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [TransferConfig\["classNames"\]](/components/transfer-cn#semantic-dom), styles?: [TransferConfig\["styles"\]](/components/transfer-cn#semantic-dom), selectionsIcon?: React.ReactNode } | - | |
| qrcode | 设置 QRCode 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?:[QRCode\["classNames"\]](/components/qr-code-cn#semantic-dom), styles?: [QRCode\["styles"\]](/components/qr-code-cn#semantic-dom) } | - | |
| transfer | 设置 Transfer 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [TransferConfig\["classNames"\]](/components/transfer-cn#semantic-dom), styles?: [TransferConfig\["styles"\]](/components/transfer-cn#semantic-dom), selectionsIcon?: React.ReactNode } | - | |
| tree | 设置 Tree 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [TreeConfig\["classNames"\]](/components/tree-cn#semantic-dom), styles?: [TreeConfig\["styles"\]](/components/tree-cn#semantic-dom) } | - | 5.7.0, `classNames``styles`: 6.0.0 |
| treeSelect | 设置 TreeSelect 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [TreeSelectConfig\["classNames"\]](/components/tree-select-cn#semantic-dom), styles?: [TreeSelectConfig\["styles"\]](/components/tree-select-cn#semantic-dom) } | - | |
| typography | 设置 Typography 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |

View File

@@ -221,7 +221,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx extend context c
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
@@ -276,7 +276,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx extend context c
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div>
<span
@@ -343,7 +343,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx extend context c
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div>
<span
@@ -595,7 +595,7 @@ exports[`renders components/qr-code/demo/status.tsx extend context correctly 1`]
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
aria-busy="true"
@@ -635,7 +635,7 @@ exports[`renders components/qr-code/demo/status.tsx extend context correctly 1`]
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<p
class="ant-qrcode-expired"
@@ -685,7 +685,7 @@ exports[`renders components/qr-code/demo/status.tsx extend context correctly 1`]
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<p
class="ant-qrcode-scanned"

View File

@@ -178,7 +178,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx correctly 1`] =
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
@@ -233,7 +233,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx correctly 1`] =
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div>
<span
@@ -301,7 +301,7 @@ exports[`renders components/qr-code/demo/customStatusRender.tsx correctly 1`] =
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div>
<span
@@ -546,7 +546,7 @@ exports[`renders components/qr-code/demo/status.tsx correctly 1`] = `
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
aria-busy="true"
@@ -586,7 +586,7 @@ exports[`renders components/qr-code/demo/status.tsx correctly 1`] = `
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<p
class="ant-qrcode-expired"
@@ -636,7 +636,7 @@ exports[`renders components/qr-code/demo/status.tsx correctly 1`] = `
style="background-color:transparent;width:160px;height:160px"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<p
class="ant-qrcode-scanned"

View File

@@ -7,7 +7,7 @@ exports[`QRCode test custom status render 1`] = `
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
class="custom-expired"
@@ -34,7 +34,7 @@ exports[`QRCode test custom status render 1`] = `
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
class="custom-loading"
@@ -53,7 +53,7 @@ exports[`QRCode test custom status render 1`] = `
style="background-color: transparent; width: 160px; height: 160px;"
>
<div
class="ant-qrcode-mask"
class="ant-qrcode-cover"
>
<div
class="custom-scanned"

View File

@@ -189,4 +189,31 @@ describe('QRCode test', () => {
);
expect(svgContainer.querySelector('svg')).toHaveAttribute('aria-label', ariaLabel);
});
it('should apply custom styles to QRCode', () => {
const customClassNames = {
root: 'custom-root',
cover: 'custom-cover',
};
const customStyles = {
root: { color: 'red' },
cover: { color: 'blue' },
};
const { container } = render(
<QRCode classNames={customClassNames} styles={customStyles} value="antd" status="loading" />,
);
const QRCodeElement = container.querySelector('.ant-qrcode') as HTMLElement;
const QRCodeCoverElement = container.querySelector('.ant-qrcode-cover') as HTMLElement;
// check classNames
expect(QRCodeElement.classList).toContain('custom-root');
expect(QRCodeCoverElement.classList).toContain('custom-cover');
// check styles
expect(QRCodeElement.style.color).toBe('red');
expect(QRCodeCoverElement.style.color).toBe('blue');
});
});

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { QRCode } from 'antd';
import SemanticPreview from '../../../.dumi/components/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根元素',
cover: '遮罩层元素',
},
en: {
root: 'Root element',
cover: 'Cover element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
const value = 'https://ant.design';
return (
<SemanticPreview
componentName="QRCode"
semantics={[
{ name: 'root', desc: locale.root },
{ name: 'cover', desc: locale.cover },
]}
>
<QRCode value={value} status="loading" />
</SemanticPreview>
);
};
export default App;

View File

@@ -61,6 +61,10 @@ type StatusRenderInfo = {
};
```
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## Design Token
<ComponentTokenTable component="QRCode"></ComponentTokenTable>

View File

@@ -1,18 +1,20 @@
import React, { useContext } from 'react';
import React from 'react';
import { QRCodeCanvas, QRCodeSVG } from '@rc-component/qrcode';
import omit from '@rc-component/util/lib/omit';
import pickAttrs from '@rc-component/util/lib/pickAttrs';
import classNames from 'classnames';
import cls from 'classnames';
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import { useLocale } from '../locale';
import { useToken } from '../theme/internal';
import type { QRCodeProps, QRProps } from './interface';
import QRcodeStatus from './QrcodeStatus';
import useStyle from './style/index';
export type { QRCodeProps, QRProps };
const QRCode: React.FC<QRCodeProps> = (props) => {
const [, token] = useToken();
const {
@@ -32,9 +34,24 @@ const QRCode: React.FC<QRCodeProps> = (props) => {
prefixCls: customizePrefixCls,
bgColor = 'transparent',
statusRender,
classNames: qrcodeClassNames,
styles,
...rest
} = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const {
getPrefixCls,
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles,
} = useComponentConfig('qrcode');
const [mergedClassNames, mergedStyles] = useMergeSemantic(
[contextClassNames, qrcodeClassNames],
[contextStyles, styles],
);
const prefixCls = getPrefixCls('qrcode', customizePrefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls);
@@ -85,21 +102,32 @@ const QRCode: React.FC<QRCodeProps> = (props) => {
return null;
}
const mergedCls = classNames(prefixCls, className, rootClassName, hashId, cssVarCls, {
[`${prefixCls}-borderless`]: !bordered,
});
const rootClassNames = cls(
prefixCls,
className,
rootClassName,
hashId,
cssVarCls,
contextClassName,
mergedClassNames.root,
{
[`${prefixCls}-borderless`]: !bordered,
},
);
const mergedStyle: React.CSSProperties = {
const rootStyle: React.CSSProperties = {
backgroundColor: bgColor,
...mergedStyles.root,
...contextStyle,
...style,
width: style?.width ?? size,
height: style?.height ?? size,
};
return (
<div {...restProps} className={mergedCls} style={mergedStyle}>
<div {...restProps} className={rootClassNames} style={rootStyle}>
{status !== 'active' && (
<div className={`${prefixCls}-mask`}>
<div className={cls(`${prefixCls}-cover`, mergedClassNames.cover)} style={mergedStyles.cover}>
<QRcodeStatus
prefixCls={prefixCls}
locale={locale}

View File

@@ -62,6 +62,10 @@ type StatusRenderInfo = {
};
```
## Semantic DOM
<code src="./demo/_semantic.tsx" simplify="true"></code>
## 主题变量Design Token
<ComponentTokenTable component="QRCode"></ComponentTokenTable>

View File

@@ -19,6 +19,7 @@ export type StatusRenderInfo = {
onRefresh?: () => void;
};
type SemanticName = 'root' | 'cover';
export interface QRCodeProps extends QRProps, React.HTMLAttributes<HTMLDivElement> {
type?: 'canvas' | 'svg';
className?: string;
@@ -31,4 +32,6 @@ export interface QRCodeProps extends QRProps, React.HTMLAttributes<HTMLDivElemen
status?: QRStatus;
onRefresh?: () => void;
statusRender?: (info: StatusRenderInfo) => ReactNode;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
}

View File

@@ -20,9 +20,9 @@ interface QRCodeToken extends FullToken<'QRCode'> {
QRCodeTextColor: string;
/**
* @desc QRCode 遮罩背景颜色
* @descEN Mask background color of QRCode
* @descEN Cover background color of QRCode
*/
QRCodeMaskBackgroundColor: string;
QRCodeCoverBackgroundColor: string;
}
const genQRCodeStyle: GenerateStyle<QRCodeToken> = (token) => {
@@ -40,7 +40,7 @@ const genQRCodeStyle: GenerateStyle<QRCodeToken> = (token) => {
position: 'relative',
overflow: 'hidden',
[`& > ${componentCls}-mask`]: {
[`& > ${componentCls}-cover`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
@@ -53,7 +53,7 @@ const genQRCodeStyle: GenerateStyle<QRCodeToken> = (token) => {
height: '100%',
color: token.colorText,
lineHeight: token.lineHeight,
background: token.QRCodeMaskBackgroundColor,
background: token.QRCodeCoverBackgroundColor,
textAlign: 'center',
[`& > ${componentCls}-expired, & > ${componentCls}-scanned`]: {
color: token.QRCodeTextColor,
@@ -80,7 +80,7 @@ const genQRCodeStyle: GenerateStyle<QRCodeToken> = (token) => {
};
export const prepareComponentToken: GetDefaultToken<'QRCode'> = (token) => ({
QRCodeMaskBackgroundColor: new FastColor(token.colorBgContainer).setA(0.96).toRgbString(),
QRCodeCoverBackgroundColor: new FastColor(token.colorBgContainer).setA(0.96).toRgbString(),
});
export default genStyleHooks<'QRCode'>(