From 240210a281e1eba4f3a6f849e88232c6a722ff09 Mon Sep 17 00:00:00 2001 From: lijianan <574980606@qq.com> Date: Mon, 5 Dec 2022 14:15:26 +0800 Subject: [PATCH] feat: New Component QRCode (#38948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: qrcode * chore: code clean * feat: New Component Qr-Code (#38891) * feat: QrCode * fix * fix * fix: fix bug * fix: fix bug * fix * fix * fix * delete * delete * test case * fix lint * bundlesize * demo * fix: fix test * remove dep * update snap * en docs * refactor: rename tests dir * Update components/qr-code/demo/base.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/base.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/download.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/download.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/download.tsx Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/logo.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/index.tsx Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qr-code/demo/logo.md Co-authored-by: MadCcc <1075746765@qq.com> * rename * fix * adjust text * rename * fix title * rename * rename * fix: snap * fix * bundlesize * update demo * update docs * add demo * add docs * add docs * test: add warning * update demo * bundlesize * update test case * update demo * feat: add onRefresh、add status * fix: fix demo * fix: fix demo * add locale * add locale * add test case * update snap * fix demo * update demo * update demo * update demo * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/interface.ts Co-authored-by: MadCcc <1075746765@qq.com> * Update components/qrcode/style/index.ts Co-authored-by: MadCcc <1075746765@qq.com> * fix * Update components/qrcode/index.tsx Co-authored-by: MadCcc <1075746765@qq.com> * fix * fix * fix * add decs * fix * fix * fix type * fix * fix demo * fix lint * fix lint * add test case for bordered * prettier-ignore Co-authored-by: MadCcc <1075746765@qq.com> Co-authored-by: 栗嘉男 --- .../__snapshots__/index.test.ts.snap | 1 + components/index.tsx | 2 + components/locale-provider/index.tsx | 4 + components/locale/en_US.tsx | 4 + components/locale/zh_CN.tsx | 4 + components/mentions/index.en-US.md | 3 +- .../__snapshots__/demo-extend.test.ts.snap | 403 ++++++++++++++++++ .../__tests__/__snapshots__/demo.test.ts.snap | 363 ++++++++++++++++ .../__snapshots__/index.test.tsx.snap | 20 + .../qrcode/__tests__/demo-extend.test.ts | 3 + components/qrcode/__tests__/demo.test.ts | 3 + components/qrcode/__tests__/image.test.ts | 5 + components/qrcode/__tests__/index.test.tsx | 82 ++++ components/qrcode/demo/Popover.md | 7 + components/qrcode/demo/Popover.tsx | 12 + components/qrcode/demo/base.md | 7 + components/qrcode/demo/base.tsx | 6 + components/qrcode/demo/customColor.md | 7 + components/qrcode/demo/customColor.tsx | 24 ++ components/qrcode/demo/customSize.md | 7 + components/qrcode/demo/customSize.tsx | 48 +++ components/qrcode/demo/download.md | 7 + components/qrcode/demo/download.tsx | 26 ++ components/qrcode/demo/errorlevel.md | 7 + components/qrcode/demo/errorlevel.tsx | 19 + components/qrcode/demo/icon.md | 7 + components/qrcode/demo/icon.tsx | 11 + components/qrcode/demo/status.md | 7 + components/qrcode/demo/status.tsx | 11 + components/qrcode/index.en-US.md | 56 +++ components/qrcode/index.tsx | 92 ++++ components/qrcode/index.zh-CN.md | 57 +++ components/qrcode/interface.ts | 33 ++ components/qrcode/style/index.ts | 59 +++ components/theme/interface/components.ts | 2 + components/theme/themes/seed.ts | 14 +- components/theme/util/alias.ts | 10 +- components/theme/util/getAlphaColor.ts | 3 +- components/tour/style/index.tsx | 12 +- package.json | 3 +- tests/__snapshots__/index.test.ts.snap | 1 + 41 files changed, 1431 insertions(+), 21 deletions(-) create mode 100644 components/qrcode/__tests__/__snapshots__/demo-extend.test.ts.snap create mode 100644 components/qrcode/__tests__/__snapshots__/demo.test.ts.snap create mode 100644 components/qrcode/__tests__/__snapshots__/index.test.tsx.snap create mode 100644 components/qrcode/__tests__/demo-extend.test.ts create mode 100644 components/qrcode/__tests__/demo.test.ts create mode 100644 components/qrcode/__tests__/image.test.ts create mode 100644 components/qrcode/__tests__/index.test.tsx create mode 100644 components/qrcode/demo/Popover.md create mode 100644 components/qrcode/demo/Popover.tsx create mode 100644 components/qrcode/demo/base.md create mode 100644 components/qrcode/demo/base.tsx create mode 100644 components/qrcode/demo/customColor.md create mode 100644 components/qrcode/demo/customColor.tsx create mode 100644 components/qrcode/demo/customSize.md create mode 100644 components/qrcode/demo/customSize.tsx create mode 100644 components/qrcode/demo/download.md create mode 100644 components/qrcode/demo/download.tsx create mode 100644 components/qrcode/demo/errorlevel.md create mode 100644 components/qrcode/demo/errorlevel.tsx create mode 100644 components/qrcode/demo/icon.md create mode 100644 components/qrcode/demo/icon.tsx create mode 100644 components/qrcode/demo/status.md create mode 100644 components/qrcode/demo/status.tsx create mode 100644 components/qrcode/index.en-US.md create mode 100644 components/qrcode/index.tsx create mode 100644 components/qrcode/index.zh-CN.md create mode 100644 components/qrcode/interface.ts create mode 100644 components/qrcode/style/index.ts diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap index 740ef46f1fa8..9364bf329bad 100644 --- a/components/__tests__/__snapshots__/index.test.ts.snap +++ b/components/__tests__/__snapshots__/index.test.ts.snap @@ -40,6 +40,7 @@ exports[`antd exports modules correctly 1`] = ` "Popconfirm", "Popover", "Progress", + "QRCode", "Radio", "Rate", "Result", diff --git a/components/index.tsx b/components/index.tsx index 9043a3add8a9..a9848329c2e1 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -149,4 +149,6 @@ export { default as Typography } from './typography'; export type { TypographyProps } from './typography'; export { default as Upload } from './upload'; export type { UploadFile, UploadProps } from './upload'; +export { default as QRCode } from './qrcode'; +export type { QRCodeProps, QRPropsCanvas } from './qrcode/interface'; export { default as version } from './version'; diff --git a/components/locale-provider/index.tsx b/components/locale-provider/index.tsx index 3691222058d7..1c937030227f 100644 --- a/components/locale-provider/index.tsx +++ b/components/locale-provider/index.tsx @@ -46,6 +46,10 @@ export interface Locale { Image?: { preview: string; }; + QRCode?: { + expired: string; + refresh: string; + }; } export interface LocaleProviderProps { diff --git a/components/locale/en_US.tsx b/components/locale/en_US.tsx index 4b87557341e5..080e2ebcd9d2 100644 --- a/components/locale/en_US.tsx +++ b/components/locale/en_US.tsx @@ -136,6 +136,10 @@ const localeValues: Locale = { Image: { preview: 'Preview', }, + QRCode: { + expired: 'QRCode is expired', + refresh: 'click refresh', + }, }; export default localeValues; diff --git a/components/locale/zh_CN.tsx b/components/locale/zh_CN.tsx index 6d155dde0aec..ea97e256bfa6 100644 --- a/components/locale/zh_CN.tsx +++ b/components/locale/zh_CN.tsx @@ -136,6 +136,10 @@ const localeValues: Locale = { Image: { preview: '预览', }, + QRCode: { + expired: '二维码过期', + refresh: '点击刷新', + }, }; export default localeValues; diff --git a/components/mentions/index.en-US.md b/components/mentions/index.en-US.md index 638e0e5bdbcc..8d607c0b34cc 100644 --- a/components/mentions/index.en-US.md +++ b/components/mentions/index.en-US.md @@ -68,7 +68,7 @@ return ; | onResize | The callback function that is triggered when textarea resize | function({ width, height }) | - | | | onSearch | Trigger when prefix hit | (text: string, prefix: string) => void | - | | | onSelect | Trigger when user select the option | (option: OptionProps, prefix: string) => void | - | | -| options | Option Configuration | [Options](#Option) | \[] | 5.1.0 | +| options | Option Configuration | [Options](#Option) | \[] | 5.1.0 | ### Mention methods @@ -79,6 +79,7 @@ return ; ### Option + | Property | Description | Type | Default | | --- | --- | --- | --- | | label | Title of the option | React.ReactNode | - | diff --git a/components/qrcode/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/qrcode/__tests__/__snapshots__/demo-extend.test.ts.snap new file mode 100644 index 000000000000..74af4d455c9e --- /dev/null +++ b/components/qrcode/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -0,0 +1,403 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/qrcode/demo/Popover.tsx extend context correctly 1`] = ` +Array [ + icon, +
+
+
+
+ +
+ +
+
+
, +] +`; + +exports[`renders ./components/qrcode/demo/base.tsx extend context correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/qrcode/demo/customColor.tsx extend context correctly 1`] = ` +
+
+
+ +
+
+
+
+ +
+
+
+`; + +exports[`renders ./components/qrcode/demo/customSize.tsx extend context correctly 1`] = ` +Array [ +
+ + +
, +
+ + +
, +] +`; + +exports[`renders ./components/qrcode/demo/download.tsx extend context correctly 1`] = ` +
+
+ +
+ +
+`; + +exports[`renders ./components/qrcode/demo/errorlevel.tsx extend context correctly 1`] = ` +Array [ +
+ +
, +
+
+ + + + +
+
, +] +`; + +exports[`renders ./components/qrcode/demo/icon.tsx extend context correctly 1`] = ` +
+ + +
+`; + +exports[`renders ./components/qrcode/demo/status.tsx extend context correctly 1`] = ` +
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+

+ QRCode is expired +

+ +
+ +
+
+
+`; diff --git a/components/qrcode/__tests__/__snapshots__/demo.test.ts.snap b/components/qrcode/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 000000000000..51e8a82e7078 --- /dev/null +++ b/components/qrcode/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,363 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/qrcode/demo/Popover.tsx correctly 1`] = ` +icon +`; + +exports[`renders ./components/qrcode/demo/base.tsx correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/qrcode/demo/customColor.tsx correctly 1`] = ` +
+
+
+ +
+
+
+
+ +
+
+
+`; + +exports[`renders ./components/qrcode/demo/customSize.tsx correctly 1`] = ` +Array [ +
+ + +
, +
+ + +
, +] +`; + +exports[`renders ./components/qrcode/demo/download.tsx correctly 1`] = ` +
+
+ +
+ +
+`; + +exports[`renders ./components/qrcode/demo/errorlevel.tsx correctly 1`] = ` +Array [ +
+ +
, +
+
+ + + + +
+
, +] +`; + +exports[`renders ./components/qrcode/demo/icon.tsx correctly 1`] = ` +
+ + +
+`; + +exports[`renders ./components/qrcode/demo/status.tsx correctly 1`] = ` +
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+

+ QRCode is expired +

+ +
+ +
+
+
+`; diff --git a/components/qrcode/__tests__/__snapshots__/index.test.tsx.snap b/components/qrcode/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000000..6dcb91c4b332 --- /dev/null +++ b/components/qrcode/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`QRCode test rtl render component should be rendered correctly in RTL direction 1`] = `null`; + +exports[`QRCode test should correct render 1`] = ` +
+
+ +
+
+`; + +exports[`QRCode test should render \`null\` and console Error when value not exist 1`] = `null`; diff --git a/components/qrcode/__tests__/demo-extend.test.ts b/components/qrcode/__tests__/demo-extend.test.ts new file mode 100644 index 000000000000..79dea1e4faf5 --- /dev/null +++ b/components/qrcode/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('qrcode'); diff --git a/components/qrcode/__tests__/demo.test.ts b/components/qrcode/__tests__/demo.test.ts new file mode 100644 index 000000000000..1cb1d77dd4fd --- /dev/null +++ b/components/qrcode/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('qrcode'); diff --git a/components/qrcode/__tests__/image.test.ts b/components/qrcode/__tests__/image.test.ts new file mode 100644 index 000000000000..df82d405e561 --- /dev/null +++ b/components/qrcode/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('QRCode image', () => { + imageDemoTest('qrcode'); +}); diff --git a/components/qrcode/__tests__/index.test.tsx b/components/qrcode/__tests__/index.test.tsx new file mode 100644 index 000000000000..6de938dab5a5 --- /dev/null +++ b/components/qrcode/__tests__/index.test.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import QRCode from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { fireEvent, render } from '../../../tests/utils'; +import type { QRCodeProps } from '../interface'; + +describe('QRCode test', () => { + mountTest(QRCode); + rtlTest(QRCode); + + it('should correct render', () => { + const { container } = render(); + expect( + container + ?.querySelector('.ant-qrcode') + ?.querySelector('canvas'), + ).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + it('should render `null` and console Error when value not exist', () => { + const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const { container } = render(); + expect(container.firstChild).toBe(null); + expect(container.firstChild).toMatchSnapshot(); + expect(errSpy).toHaveBeenCalledWith('Warning: [antd: QRCode] need to receive `value` props'); + errSpy.mockRestore(); + }); + + it('support custom icon', () => { + const { container } = render(); + expect( + container + ?.querySelector('.ant-qrcode') + ?.querySelector('img'), + ).toBeTruthy(); + }); + + it('support custom size', () => { + const { container } = render(); + const wapper = container.querySelector('.ant-qrcode'); + expect(wapper?.style?.width).toBe('100px'); + expect(wapper?.style?.height).toBe('100px'); + }); + + it('support refresh', () => { + const refresh = jest.fn(); + const { container } = render(); + fireEvent.click( + container + ?.querySelector('.ant-qrcode') + ?.querySelector('button.ant-btn-link')!, + ); + expect(refresh).toHaveBeenCalled(); + }); + + it('support loading', () => { + const Demo: React.FC = () => { + const [status, setStatus] = useState('active'); + return ( + <> + + + + ); + }; + const { container } = render(); + expect(container.querySelector('.ant-spin-spinning')).toBeFalsy(); + fireEvent.click(container?.querySelector('button')!); + expect(container.querySelector('.ant-spin-spinning')).toBeTruthy(); + }); + + it('support bordered', () => { + const { container } = render(); + expect(container?.querySelector('.ant-qrcode')).toHaveClass( + 'ant-qrcode-borderless', + ); + }); +}); diff --git a/components/qrcode/demo/Popover.md b/components/qrcode/demo/Popover.md new file mode 100644 index 000000000000..48c076f08cf6 --- /dev/null +++ b/components/qrcode/demo/Popover.md @@ -0,0 +1,7 @@ +## zh-CN + +带气泡卡片的例子。 + +## en-US + +With Popover. diff --git a/components/qrcode/demo/Popover.tsx b/components/qrcode/demo/Popover.tsx new file mode 100644 index 000000000000..342e1505d402 --- /dev/null +++ b/components/qrcode/demo/Popover.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { QRCode, Popover } from 'antd'; + +const src = 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'; + +const App: React.FC = () => ( + }> + icon + +); + +export default App; diff --git a/components/qrcode/demo/base.md b/components/qrcode/demo/base.md new file mode 100644 index 000000000000..d7c5bd3ef03d --- /dev/null +++ b/components/qrcode/demo/base.md @@ -0,0 +1,7 @@ +## zh-CN + +基本用法。 + +## en-US + +Basic Usage. diff --git a/components/qrcode/demo/base.tsx b/components/qrcode/demo/base.tsx new file mode 100644 index 000000000000..5092f0f86e1a --- /dev/null +++ b/components/qrcode/demo/base.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { QRCode } from 'antd'; + +const App: React.FC = () => ; + +export default App; diff --git a/components/qrcode/demo/customColor.md b/components/qrcode/demo/customColor.md new file mode 100644 index 000000000000..f6824980303d --- /dev/null +++ b/components/qrcode/demo/customColor.md @@ -0,0 +1,7 @@ +## zh-CN + +通过设置 `color` 自定义二维码颜色,通过设置 `style` 自定义背景颜色。 + +## en-US + +Custom Color. diff --git a/components/qrcode/demo/customColor.tsx b/components/qrcode/demo/customColor.tsx new file mode 100644 index 000000000000..8b6118461439 --- /dev/null +++ b/components/qrcode/demo/customColor.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { QRCode, Space, theme } from 'antd'; + +const { useToken } = theme; + +const App: React.FC = () => { + const { token } = useToken(); + return ( + + + + + ); +}; + +export default App; diff --git a/components/qrcode/demo/customSize.md b/components/qrcode/demo/customSize.md new file mode 100644 index 000000000000..08e60002d8c3 --- /dev/null +++ b/components/qrcode/demo/customSize.md @@ -0,0 +1,7 @@ +## zh-CN + +自定义尺寸 + +## en-US + +Custom Size. diff --git a/components/qrcode/demo/customSize.tsx b/components/qrcode/demo/customSize.tsx new file mode 100644 index 000000000000..35b06fff456f --- /dev/null +++ b/components/qrcode/demo/customSize.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; +import { QRCode, Button } from 'antd'; + +const App: React.FC = () => { + const [size, setSize] = useState(160); + + const increase = () => { + setSize((prevSize) => { + const newSize = prevSize + 10; + if (newSize > 300) { + return 300; + } + return newSize; + }); + }; + + const decline = () => { + setSize((prevSize) => { + const newSize = prevSize - 10; + if (newSize < 48) { + return 48; + } + return newSize; + }); + }; + + return ( + <> + + + + + + + ); +}; + +export default App; diff --git a/components/qrcode/demo/download.md b/components/qrcode/demo/download.md new file mode 100644 index 000000000000..9967cf7fa2f1 --- /dev/null +++ b/components/qrcode/demo/download.md @@ -0,0 +1,7 @@ +## zh-CN + +下载二维码的简单实现。 + +## en-US + +A way to download QRCode. diff --git a/components/qrcode/demo/download.tsx b/components/qrcode/demo/download.tsx new file mode 100644 index 000000000000..c66ce361cc65 --- /dev/null +++ b/components/qrcode/demo/download.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { QRCode, Button } from 'antd'; + +const downloadQRCode = () => { + const canvas = document.getElementById('myqrcode')?.querySelector('canvas'); + if (canvas) { + const url = canvas.toDataURL(); + const a = document.createElement('a'); + a.download = 'QRCode.png'; + a.href = url; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } +}; + +const App: React.FC = () => ( +
+ + +
+); + +export default App; diff --git a/components/qrcode/demo/errorlevel.md b/components/qrcode/demo/errorlevel.md new file mode 100644 index 000000000000..1df7f09c4606 --- /dev/null +++ b/components/qrcode/demo/errorlevel.md @@ -0,0 +1,7 @@ +## zh-CN + +通过设置 errorLevel 调整不同的容错等级。 + +## en-US + +set Error Level. diff --git a/components/qrcode/demo/errorlevel.tsx b/components/qrcode/demo/errorlevel.tsx new file mode 100644 index 000000000000..0995a120a10f --- /dev/null +++ b/components/qrcode/demo/errorlevel.tsx @@ -0,0 +1,19 @@ +import React, { useState } from 'react'; +import type { QRCodeProps } from 'antd'; +import { Segmented, QRCode } from 'antd'; + +const App: React.FC = () => { + const [level, setLevel] = useState('L'); + return ( + <> + + + + ); +}; + +export default App; diff --git a/components/qrcode/demo/icon.md b/components/qrcode/demo/icon.md new file mode 100644 index 000000000000..05a34f1d8cb6 --- /dev/null +++ b/components/qrcode/demo/icon.md @@ -0,0 +1,7 @@ +## zh-CN + +带 Icon 的二维码。 + +## en-US + +QRCode with Icon. diff --git a/components/qrcode/demo/icon.tsx b/components/qrcode/demo/icon.tsx new file mode 100644 index 000000000000..77a62185921e --- /dev/null +++ b/components/qrcode/demo/icon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { QRCode } from 'antd'; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/components/qrcode/demo/status.md b/components/qrcode/demo/status.md new file mode 100644 index 000000000000..bc25a6993a3d --- /dev/null +++ b/components/qrcode/demo/status.md @@ -0,0 +1,7 @@ +## zh-CN + +可以通过 `status` 的值控制二维码的状态。 + +## en-US + +The status can be controlled by the value `status`. diff --git a/components/qrcode/demo/status.tsx b/components/qrcode/demo/status.tsx new file mode 100644 index 000000000000..3f0a57bca60c --- /dev/null +++ b/components/qrcode/demo/status.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { QRCode, Space } from 'antd'; + +const App: React.FC = () => ( + + + console.log('refresh')} /> + +); + +export default App; diff --git a/components/qrcode/index.en-US.md b/components/qrcode/index.en-US.md new file mode 100644 index 000000000000..63c2df1a6c3d --- /dev/null +++ b/components/qrcode/index.en-US.md @@ -0,0 +1,56 @@ +--- +category: Components +title: QRCode +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*cJopQrf0ncwAAAAAAAAAAAAADrJ8AQ/original +demo: + cols: 2 +group: + title: Data Display + order: 5 +--- + +Components that can convert links into QR codes, and support custom color and logo. Available since `antd@5.1.0`. + + + +## When To Use + +Used when the link needs to be converted into a QR Code. + +## Examples + + +base +With Icon +other statu +Custom Size +Custom Color +Download QRCode +Error Level +Advanced Usage + +## API + +> This component is available since `antd@5.1.0` + +| Property | Description | Type | Default | +| :-- | :-- | :-- | :-- | +| value | scanned link | string | - | +| icon | include image url (only image link are supported) | string | - | +| size | QRCode size | number | 128 | +| iconSize | include image size | number | 32 | +| color | QRCode Color | string | `#000` | +| bordered | Whether has border style | boolean | `true` | +| errorLevel | Error Code Level | `'L' \| 'M' \| 'Q' \| 'H' ` | `M` | +| status | QRCode statu | `active \| expired \| loading ` | `active` | +| onRefresh | callback | `() => void` | - | + +## FAQ + +### About QRCode ErrorLevel + +The ErrorLevel means that the QR code can be scanned normally after being blocked, and the maximum area that can be blocked is the error correction rate. + +Generally, the QR code is divided into 4 error correction levels: Level `L` can correct about `7%` errors, Level `M` can correct about `15%` errors, Level `Q` can correct about `25%` errors, and Level `H` can correct about `30%` errors. When the content encoding of the QR code carries less information, in other words, when the value link is short, set different error correction levels, and the generated image will not change. + +> For more information, see the: [https://www.qrcode.com/en/about/error_correction](https://www.qrcode.com/en/about/error_correction.html) diff --git a/components/qrcode/index.tsx b/components/qrcode/index.tsx new file mode 100644 index 000000000000..eec3f9e27f10 --- /dev/null +++ b/components/qrcode/index.tsx @@ -0,0 +1,92 @@ +import React, { useMemo, useContext } from 'react'; +import { QRCodeCanvas } from 'qrcode.react'; +import classNames from 'classnames'; +import { ReloadOutlined } from '@ant-design/icons'; +import { ConfigContext } from '../config-provider'; +import LocaleReceiver from '../locale-provider/LocaleReceiver'; +import type { ConfigConsumerProps } from '../config-provider'; +import type { QRCodeProps, QRPropsCanvas } from './interface'; +import warning from '../_util/warning'; +import useStyle from './style/index'; +import Spin from '../spin'; +import Button from '../button'; +import theme from '../theme'; + +const { useToken } = theme; + +const QRCode: React.FC = (props) => { + const { + value, + icon = '', + size = 160, + iconSize = 40, + color = '#000', + errorLevel = 'M', + status = 'active', + bordered = true, + onRefresh, + style, + className, + prefixCls: customizePrefixCls, + } = props; + const { getPrefixCls } = useContext(ConfigContext); + const prefixCls = getPrefixCls('qrcode', customizePrefixCls); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { token } = useToken(); + const qrCodeProps = useMemo(() => { + const imageSettings: QRCodeProps['imageSettings'] = { + src: icon, + x: undefined, + y: undefined, + height: iconSize, + width: iconSize, + excavate: true, + }; + return { + value, + size: size - (token.paddingSM + token.lineWidth) * 2, + level: errorLevel, + bgColor: 'transparent', + fgColor: color, + imageSettings: icon ? imageSettings : undefined, + }; + }, [errorLevel, color, icon, iconSize, size, value]); + + if (!value) { + if (process.env.NODE_ENV !== 'production') { + warning(false, 'QRCode', 'need to receive `value` props'); + } + return null; + } + + const cls = classNames(prefixCls, className, hashId, { + [`${prefixCls}-borderless`]: !bordered, + }); + + return wrapSSR( + + {(locale) => ( +
+ {status !== 'active' && ( +
+ {status === 'loading' && } + {status === 'expired' && ( + <> +

{locale.expired}

+ {typeof onRefresh === 'function' && ( + + )} + + )} +
+ )} + +
+ )} +
, + ); +}; + +export default QRCode; diff --git a/components/qrcode/index.zh-CN.md b/components/qrcode/index.zh-CN.md new file mode 100644 index 000000000000..16d2d7402545 --- /dev/null +++ b/components/qrcode/index.zh-CN.md @@ -0,0 +1,57 @@ +--- +category: Components +subtitle: 二维码 +title: QRCode +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*cJopQrf0ncwAAAAAAAAAAAAADrJ8AQ/original +demo: + cols: 2 +group: + title: 数据展示 + order: 5 +--- + +能够将链接转换生成二维码的组件,支持自定义配色和 Logo 配置,自 `antd@5.1.0` 版本开始提供该组件。 + + + +## 何时使用 + +当需要将链接转换成为二维码时使用。 + +## 代码演示 + + +基本使用 +带 Icon 的例子 +不同的状态 +自定义尺寸 +自定义颜色 +下载二维码 +纠错比例 +高级用法 + +## API + +> 自 `antd@5.1.0` 版本开始提供该组件。 + +| 参数 | 说明 | 类型 | 默认值 | +| :-- | :-- | :-- | :-- | +| value | 扫描后的地址 | string | - | +| icon | 二维码中图片的地址(目前只支持图片地址) | string | - | +| size | 二维码大小 | number | 160 | +| iconSize | 二维码中图片的大小 | number | 40 | +| color | 二维码颜色 | string | `#000` | +| bordered | 是否有边框 | boolean | `true` | +| errorLevel | 二维码纠错等级 | `'L' \| 'M' \| 'Q' \| 'H' ` | `M` | +| status | 二维码状态 | `active \| expired \| loading ` | `active` | +| onRefresh | 点击"点击刷新"的回调 | `() => void` | - | + +## FAQ + +### 关于二维码纠错等级 + +纠错等级也叫纠错率,就是指二维码可以被遮挡后还能正常扫描,而这个能被遮挡的最大面积就是纠错率。 + +通常情况下二维码分为 4 个纠错级别:`L级` 可纠正约 `7%` 错误、`M级` 可纠正约 `15%` 错误、`Q级` 可纠正约 `25%` 错误、`H级` 可纠正约`30%` 错误。并不是所有位置都可以缺损,像最明显的三个角上的方框,直接影响初始定位。中间零散的部分是内容编码,可以容忍缺损。当二维码的内容编码携带信息比较少的时候,也就是链接比较短的时候,设置不同的纠错等级,生成的图片不会发生变化。 + +> 有关更多信息,可参阅相关资料:[https://www.qrcode.com/zh/about/error_correction](https://www.qrcode.com/zh/about/error_correction.html) diff --git a/components/qrcode/interface.ts b/components/qrcode/interface.ts new file mode 100644 index 000000000000..3371e0203521 --- /dev/null +++ b/components/qrcode/interface.ts @@ -0,0 +1,33 @@ +import type { CSSProperties } from 'react'; + +interface ImageSettings { + src: string; + height: number; + width: number; + excavate: boolean; + x?: number; + y?: number; +} + +interface QRProps { + value: string; + size?: number; + level?: string; + color?: string; + style?: CSSProperties; + includeMargin?: boolean; + imageSettings?: ImageSettings; +} + +export type QRPropsCanvas = QRProps & React.CanvasHTMLAttributes; + +export interface QRCodeProps extends QRProps { + className?: string; + prefixCls?: string; + icon?: string; + iconSize?: number; + bordered?: boolean; + errorLevel?: 'L' | 'M' | 'Q' | 'H'; + status?: 'active' | 'expired' | 'loading'; + onRefresh?: () => void; +} diff --git a/components/qrcode/style/index.ts b/components/qrcode/style/index.ts new file mode 100644 index 000000000000..0cb4e323d740 --- /dev/null +++ b/components/qrcode/style/index.ts @@ -0,0 +1,59 @@ +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { mergeToken, genComponentStyleHook } from '../../theme/internal'; +import { resetComponent } from '../../style'; + +export interface ComponentToken {} + +interface QRCodeToken extends FullToken<'QRCode'> { + QRCodeMaskBackgroundColor: string; +} + +const genQRCodeStyle: GenerateStyle = (token) => { + const { componentCls } = token; + return { + [componentCls]: { + ...resetComponent(token), + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + padding: token.paddingSM, + borderRadius: token.borderRadiusLG, + border: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`, + position: 'relative', + width: '100%', + height: '100%', + overflow: 'hidden', + [`& > ${componentCls}-mask`]: { + position: 'absolute', + insetBlockStart: 0, + insetInlineStart: 0, + zIndex: 10, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + color: token.colorText, + lineHeight: token.lineHeight, + background: token.QRCodeMaskBackgroundColor, + textAlign: 'center', + }, + '&-icon': { + marginBlockEnd: token.marginXS, + fontSize: token.controlHeight, + }, + }, + [`${componentCls}-borderless`]: { + borderColor: 'transparent', + }, + }; +}; + +export default genComponentStyleHook<'QRCode'>('QRCode', (token) => + genQRCodeStyle( + mergeToken(token, { + QRCodeMaskBackgroundColor: 'rgba(255, 255, 255, 0.96)', + }), + ), +); diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 2e4a427f56b3..5ac94683989e 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -46,6 +46,7 @@ import type { ComponentToken as TransferComponentToken } from '../../transfer/st import type { ComponentToken as TypographyComponentToken } from '../../typography/style'; import type { ComponentToken as UploadComponentToken } from '../../upload/style'; import type { ComponentToken as TourComponentToken } from '../../tour/style'; +import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style'; export interface ComponentTokenMap { Affix?: {}; @@ -108,4 +109,5 @@ export interface ComponentTokenMap { Space?: SpaceComponentToken; Progress?: ProgressComponentToken; Tour?: TourComponentToken; + QRCode?: QRCodeComponentToken; } diff --git a/components/theme/themes/seed.ts b/components/theme/themes/seed.ts index 032bded2b731..2abdf240e85a 100644 --- a/components/theme/themes/seed.ts +++ b/components/theme/themes/seed.ts @@ -43,14 +43,14 @@ const seedToken: SeedToken = { // Motion motionUnit: 0.1, motionBase: 0, - motionEaseOutCirc: `cubic-bezier(0.08, 0.82, 0.17, 1)`, - motionEaseInOutCirc: `cubic-bezier(0.78, 0.14, 0.15, 0.86)`, + motionEaseOutCirc: 'cubic-bezier(0.08, 0.82, 0.17, 1)', + motionEaseInOutCirc: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)', motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)', - motionEaseInOut: `cubic-bezier(0.645, 0.045, 0.355, 1)`, - motionEaseOutBack: `cubic-bezier(0.12, 0.4, 0.29, 1.46)`, - motionEaseInBack: `cubic-bezier(0.71, -0.46, 0.88, 0.6)`, - motionEaseInQuint: `cubic-bezier(0.645, 0.045, 0.355, 1)`, - motionEaseOutQuint: `cubic-bezier(0.23, 1, 0.32, 1)`, + motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)', + motionEaseOutBack: 'cubic-bezier(0.12, 0.4, 0.29, 1.46)', + motionEaseInBack: 'cubic-bezier(0.71, -0.46, 0.88, 0.6)', + motionEaseInQuint: 'cubic-bezier(0.645, 0.045, 0.355, 1)', + motionEaseOutQuint: 'cubic-bezier(0.23, 1, 0.32, 1)', // Radius borderRadius: 6, diff --git a/components/theme/util/alias.ts b/components/theme/util/alias.ts index 0a45b6db69ff..fee69167ce3c 100644 --- a/components/theme/util/alias.ts +++ b/components/theme/util/alias.ts @@ -175,7 +175,7 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken screenXXLMin: screenXXL, // FIXME: component box-shadow, should be removed - boxShadowPopoverArrow: `3px 3px 7px rgba(0, 0, 0, 0.1)`, + boxShadowPopoverArrow: '3px 3px 7px rgba(0, 0, 0, 0.1)', boxShadowCard: ` 0 1px 2px -2px ${new TinyColor('rgba(0, 0, 0, 0.16)').toRgbString()}, 0 3px 6px 0 ${new TinyColor('rgba(0, 0, 0, 0.12)').toRgbString()}, @@ -201,10 +201,10 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken 0 -3px 6px -4px rgba(0, 0, 0, 0.12), 0 -9px 28px 8px rgba(0, 0, 0, 0.05) `, - boxShadowTabsOverflowLeft: `inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)`, - boxShadowTabsOverflowRight: `inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)`, - boxShadowTabsOverflowTop: `inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)`, - boxShadowTabsOverflowBottom: `inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)`, + boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)', + boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)', + boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)', + boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)', // Override AliasToken ...overrideTokens, diff --git a/components/theme/util/getAlphaColor.ts b/components/theme/util/getAlphaColor.ts index a0adc212f9da..7cd1d3fdbbeb 100644 --- a/components/theme/util/getAlphaColor.ts +++ b/components/theme/util/getAlphaColor.ts @@ -16,8 +16,9 @@ function getAlphaColor(frontColor: string, backgroundColor: string): string { const r = Math.round((fR - bR * (1 - fA)) / fA); const g = Math.round((fG - bG * (1 - fA)) / fA); const b = Math.round((fB - bB * (1 - fA)) / fA); - if (isStableColor(r) && isStableColor(g) && isStableColor(b)) + if (isStableColor(r) && isStableColor(g) && isStableColor(b)) { return new TinyColor({ r, g, b, a: Math.round(fA * 100) / 100 }).toRgbString(); + } } // fallback diff --git a/components/tour/style/index.tsx b/components/tour/style/index.tsx index 3a892c226160..c32d4d93fc55 100644 --- a/components/tour/style/index.tsx +++ b/components/tour/style/index.tsx @@ -209,12 +209,12 @@ const genBaseStyle: GenerateStyle = (token) => { // =========== Limit left and right placement radius ============== [[ - `&-placement-left`, - `&-placement-leftTop`, - `&-placement-leftBottom`, - `&-placement-right`, - `&-placement-rightTop`, - `&-placement-rightBottom`, + '&-placement-left', + '&-placement-leftTop', + '&-placement-leftBottom', + '&-placement-right', + '&-placement-rightTop', + '&-placement-rightBottom', ].join(',')]: { [`${componentCls}-inner`]: { borderRadius: diff --git a/package.json b/package.json index b492e7a83b86..dcf8025a9666 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "copy-to-clipboard": "^3.2.0", "dayjs": "^1.11.1", "lodash": "^4.17.21", + "qrcode.react": "^3.1.0", "rc-cascader": "~3.7.0", "rc-checkbox": "~2.3.0", "rc-collapse": "~3.4.2", @@ -322,7 +323,7 @@ "bundlesize": [ { "path": "./dist/antd.min.js", - "maxSize": "377.5 kB" + "maxSize": "381 kB" } ], "tnpm": { diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index b867fa54bcd4..6d5e6564cf6e 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -40,6 +40,7 @@ exports[`antd dist files exports modules correctly 1`] = ` "Popconfirm", "Popover", "Progress", + "QRCode", "Radio", "Rate", "Result",