diff --git a/README.md b/README.md index 34bc87f432..7f98b61ca7 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,11 @@ An enterprise-class UI design language and React UI library. -[![CI status][github-action-image]][github-action-url] -[![codecov][codecov-image]][codecov-url] -[![NPM version][npm-image]][npm-url] -[![NPM downloads][download-image]][download-url] +[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url] -[![Total alerts][lgtm-image]][lgtm-url] -[![][bundlephobia-image]][bundlephobia-url] -[![][bundlesize-js-image]][unpkg-js-url] -[![FOSSA Status][fossa-image]][fossa-url] +[![Total alerts][lgtm-image]][lgtm-url] [![][bundlephobia-image]][bundlephobia-url] [![][bundlesize-js-image]][unpkg-js-url] [![FOSSA Status][fossa-image]][fossa-url] -[![Follow Twitter][twitter-image]][twitter-url] -[![Renovate status][renovate-image]][renovate-dashboard-url] -[![][issues-helper-image]][issues-helper-url] -[![Issues need help][help-wanted-image]][help-wanted-url] +[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url] [npm-image]: http://img.shields.io/npm/v/antd.svg?style=flat-square [npm-url]: http://npmjs.org/package/antd diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap index 740ef46f1f..a0b933818a 100644 --- a/components/__tests__/__snapshots__/index.test.ts.snap +++ b/components/__tests__/__snapshots__/index.test.ts.snap @@ -5,6 +5,7 @@ exports[`antd exports modules correctly 1`] = ` "Affix", "Alert", "Anchor", + "App", "AutoComplete", "Avatar", "BackTop", @@ -40,6 +41,7 @@ exports[`antd exports modules correctly 1`] = ` "Popconfirm", "Popover", "Progress", + "QRCode", "Radio", "Rate", "Result", diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 1122dfcb78..6ca13b0701 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -6,10 +6,18 @@ import type { ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider'; import getScroll from '../_util/getScroll'; import scrollTo from '../_util/scrollTo'; +import warning from '../_util/warning'; import AnchorContext from './context'; +import type { AnchorLinkBaseProps } from './AnchorLink'; +import AnchorLink from './AnchorLink'; import useStyle from './style'; +export interface AnchorLinkItemProps extends AnchorLinkBaseProps { + key: React.Key; + children?: AnchorLinkItemProps[]; +} + export type AnchorContainer = HTMLElement | Window; function getDefaultContainer() { @@ -45,6 +53,9 @@ export interface AnchorProps { prefixCls?: string; className?: string; style?: React.CSSProperties; + /** + * @deprecated Please use `items` instead. + */ children?: React.ReactNode; offsetTop?: number; bounds?: number; @@ -61,6 +72,7 @@ export interface AnchorProps { targetOffset?: number; /** Listening event when scrolling change active link */ onChange?: (currentActiveLink: string) => void; + items?: AnchorLinkItemProps[]; } interface InternalAnchorProps extends AnchorProps { @@ -100,6 +112,7 @@ const AnchorContent: React.FC = (props) => { affix = true, showInkInFixed = false, children, + items, bounds, targetOffset, onClick, @@ -108,6 +121,11 @@ const AnchorContent: React.FC = (props) => { getCurrentAnchor, } = props; + // =================== Warning ===================== + if (process.env.NODE_ENV !== 'production') { + warning(!children, 'Anchor', '`Anchor children` is deprecated. Please use `items` instead.'); + } + const [links, setLinks] = React.useState([]); const [activeLink, setActiveLink] = React.useState(null); const activeLinkRef = React.useRef(activeLink); @@ -257,13 +275,22 @@ const AnchorContent: React.FC = (props) => { ...style, }; + const createNestedLink = (options?: AnchorLinkItemProps[]) => + Array.isArray(options) + ? options.map((item) => ( + + {createNestedLink(item.children)} + + )) + : null; + const anchorContent = (
- {children} + {'items' in props ? createNestedLink(items) : children}
); diff --git a/components/anchor/AnchorLink.tsx b/components/anchor/AnchorLink.tsx index 5afba519b0..0ea729e0bd 100644 --- a/components/anchor/AnchorLink.tsx +++ b/components/anchor/AnchorLink.tsx @@ -5,15 +5,18 @@ import { ConfigConsumer } from '../config-provider'; import type { AntAnchor } from './Anchor'; import AnchorContext from './context'; -export interface AnchorLinkProps { +export interface AnchorLinkBaseProps { prefixCls?: string; href: string; target?: string; title: React.ReactNode; - children?: React.ReactNode; className?: string; } +export interface AnchorLinkProps extends AnchorLinkBaseProps { + children?: React.ReactNode; +} + const AnchorLink: React.FC = (props) => { const { href = '#', title, prefixCls: customizePrefixCls, children, className, target } = props; diff --git a/components/anchor/__tests__/Anchor.test.tsx b/components/anchor/__tests__/Anchor.test.tsx index aa47e56c3c..c4468742d5 100644 --- a/components/anchor/__tests__/Anchor.test.tsx +++ b/components/anchor/__tests__/Anchor.test.tsx @@ -432,4 +432,64 @@ describe('Anchor Render', () => { }).not.toThrow(); }); }); + + it('renders items correctly', () => { + const { container, asFragment } = render( + , + ); + expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(5); + const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!) + .slice(1) + .map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title')); + expect((linkTitles[0] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic'); + expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static'); + expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#api'); + expect(asFragment().firstChild).toMatchSnapshot(); + expect( + ( + container.querySelector( + '.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link-title', + ) as HTMLAnchorElement + )?.href, + ).toContain('#anchor-props'); + expect( + ( + container.querySelector( + '.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link .ant-anchor-link-title', + ) as HTMLAnchorElement + )?.href, + ).toContain('#link-props'); + }); }); diff --git a/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap b/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap new file mode 100644 index 0000000000..9fddec2866 --- /dev/null +++ b/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Anchor Render renders items correctly 1`] = ` + +`; diff --git a/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap index 582b236f77..fe72574f59 100644 --- a/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -154,6 +154,86 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx extend context `; +exports[`renders ./components/anchor/demo/legacy-anchor.tsx extend context correctly 1`] = ` + +`; + exports[`renders ./components/anchor/demo/onChange.tsx extend context correctly 1`] = `
`; +exports[`renders ./components/anchor/demo/legacy-anchor.tsx correctly 1`] = ` + +`; + exports[`renders ./components/anchor/demo/onChange.tsx correctly 1`] = `
( - - - - - - - - + ); export default App; diff --git a/components/anchor/demo/customizeHighlight.tsx b/components/anchor/demo/customizeHighlight.tsx index e746c2518d..fed1ebb932 100644 --- a/components/anchor/demo/customizeHighlight.tsx +++ b/components/anchor/demo/customizeHighlight.tsx @@ -1,19 +1,42 @@ import React from 'react'; import { Anchor } from 'antd'; -const { Link } = Anchor; - const getCurrentAnchor = () => '#components-anchor-demo-static'; const App: React.FC = () => ( - - - - - - - - + ); export default App; diff --git a/components/anchor/demo/legacy-anchor.md b/components/anchor/demo/legacy-anchor.md new file mode 100644 index 0000000000..775eb63223 --- /dev/null +++ b/components/anchor/demo/legacy-anchor.md @@ -0,0 +1,7 @@ +## zh-CN + +Debug usage + +## en-US + +Debug usage diff --git a/components/anchor/demo/legacy-anchor.tsx b/components/anchor/demo/legacy-anchor.tsx new file mode 100644 index 0000000000..2280a74ec9 --- /dev/null +++ b/components/anchor/demo/legacy-anchor.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Anchor } from 'antd'; + +const { Link } = Anchor; + +const App: React.FC = () => ( + + + + + + + + +); + +export default App; diff --git a/components/anchor/demo/onChange.tsx b/components/anchor/demo/onChange.tsx index f87d1703bb..da2f7b587e 100644 --- a/components/anchor/demo/onChange.tsx +++ b/components/anchor/demo/onChange.tsx @@ -1,21 +1,44 @@ import React from 'react'; import { Anchor } from 'antd'; -const { Link } = Anchor; - const onChange = (link: string) => { console.log('Anchor:OnChange', link); }; const App: React.FC = () => ( - - - - - - - - + ); export default App; diff --git a/components/anchor/demo/onClick.tsx b/components/anchor/demo/onClick.tsx index ddf88d865f..4f9866572d 100644 --- a/components/anchor/demo/onClick.tsx +++ b/components/anchor/demo/onClick.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Anchor } from 'antd'; -const { Link } = Anchor; - const handleClick = ( e: React.MouseEvent, link: { @@ -15,14 +13,39 @@ const handleClick = ( }; const App: React.FC = () => ( - - - - - - - - + ); export default App; diff --git a/components/anchor/demo/static.tsx b/components/anchor/demo/static.tsx index a5dabdc510..be2d34c053 100644 --- a/components/anchor/demo/static.tsx +++ b/components/anchor/demo/static.tsx @@ -1,17 +1,39 @@ import React from 'react'; import { Anchor } from 'antd'; -const { Link } = Anchor; - const App: React.FC = () => ( - - - - - - - - + ); export default App; diff --git a/components/anchor/demo/targetOffset.tsx b/components/anchor/demo/targetOffset.tsx index aaf0227267..fd69e252d3 100644 --- a/components/anchor/demo/targetOffset.tsx +++ b/components/anchor/demo/targetOffset.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Anchor } from 'antd'; -const { Link } = Anchor; - const App: React.FC = () => { const [targetOffset, setTargetOffset] = useState(undefined); @@ -11,14 +9,38 @@ const App: React.FC = () => { }, []); return ( - - - - - - - - + ); }; diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md index ef91c2b1ad..5c2d64c898 100644 --- a/components/anchor/index.en-US.md +++ b/components/anchor/index.en-US.md @@ -28,6 +28,7 @@ For displaying anchor hyperlinks on page and jumping between them. Customize the anchor highlight Set Anchor scroll offset Listening for anchor link change +Deprecated JSX demo ## API @@ -44,6 +45,7 @@ For displaying anchor hyperlinks on page and jumping between them. | targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetOffset) | number | - | | | onChange | Listening for anchor link change | (currentActiveLink: string) => void | | | | onClick | Set the handler to handle `click` event | (e: MouseEvent, link: object) => void | - | | +| items | Data configuration option content, support nesting through children | { href, title, target, children }\[] | - | | ### Link Props diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md index bbad12aa92..c07fc8e13d 100644 --- a/components/anchor/index.zh-CN.md +++ b/components/anchor/index.zh-CN.md @@ -29,6 +29,7 @@ group: 自定义锚点高亮 设置锚点滚动偏移量 监听锚点链接改变 +废弃的 JSX 示例 ## API @@ -45,6 +46,7 @@ group: | targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetOffset) | number | - | | | onChange | 监听锚点链接改变 | (currentActiveLink: string) => void | - | | | onClick | `click` 事件的 handler | (e: MouseEvent, link: object) => void | - | | +| items | 数据化配置选项内容,支持通过 children 嵌套 | { href, title, target, children }\[] | - | | ### Link Props diff --git a/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap new file mode 100644 index 0000000000..a405386610 --- /dev/null +++ b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/app/demo/message.tsx extend context correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/modal.tsx extend context correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/notification.tsx extend context correctly 1`] = ` +
+ +
+`; diff --git a/components/app/__tests__/__snapshots__/demo.test.ts.snap b/components/app/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 0000000000..c8a98f5162 --- /dev/null +++ b/components/app/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/app/demo/message.tsx correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/modal.tsx correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/notification.tsx correctly 1`] = ` +
+ +
+`; diff --git a/components/app/__tests__/__snapshots__/index.test.tsx.snap b/components/app/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..9d7d65275a --- /dev/null +++ b/components/app/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`App rtl render component should be rendered correctly in RTL direction 1`] = ` +
+`; + +exports[`App single 1`] = ` +
+
+ Hello World +
+
+`; diff --git a/components/app/__tests__/demo-extend.test.ts b/components/app/__tests__/demo-extend.test.ts new file mode 100644 index 0000000000..6ef0a0d13d --- /dev/null +++ b/components/app/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('app'); diff --git a/components/app/__tests__/demo.test.ts b/components/app/__tests__/demo.test.ts new file mode 100644 index 0000000000..fbcb9a76c9 --- /dev/null +++ b/components/app/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('app'); diff --git a/components/app/__tests__/image.test.ts b/components/app/__tests__/image.test.ts new file mode 100644 index 0000000000..8d3a08346d --- /dev/null +++ b/components/app/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('app', () => { + imageDemoTest('app'); +}); diff --git a/components/app/__tests__/index.test.tsx b/components/app/__tests__/index.test.tsx new file mode 100644 index 0000000000..3ea627cc9c --- /dev/null +++ b/components/app/__tests__/index.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import App from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { render } from '../../../tests/utils'; + +describe('App', () => { + mountTest(App); + rtlTest(App); + + it('single', () => { + // Sub page + const MyPage = () => { + const { message } = App.useApp(); + React.useEffect(() => { + message.success('Good!'); + }, [message]); + + return
Hello World
; + }; + + // Entry component + const MyApp = () => ( + + + + ); + + const { getByText, container } = render(); + expect(getByText('Hello World')).toBeTruthy(); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/components/app/context.ts b/components/app/context.ts new file mode 100644 index 0000000000..823ae1ed7a --- /dev/null +++ b/components/app/context.ts @@ -0,0 +1,19 @@ +import React from 'react'; +import type { MessageInstance } from '../message/interface'; +import type { NotificationInstance } from '../notification/interface'; +import type { ModalStaticFunctions } from '../modal/confirm'; + +type ModalType = Omit; +export interface useAppProps { + message: MessageInstance; + notification: NotificationInstance; + modal: ModalType; +} + +const AppContext = React.createContext({ + message: {} as MessageInstance, + notification: {} as NotificationInstance, + modal: {} as ModalType, +}); + +export default AppContext; diff --git a/components/app/demo/message.md b/components/app/demo/message.md new file mode 100644 index 0000000000..4996404228 --- /dev/null +++ b/components/app/demo/message.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `message` 静态方法. + +## en-US + +Static method for `message`. diff --git a/components/app/demo/message.tsx b/components/app/demo/message.tsx new file mode 100644 index 0000000000..71ecffd5b9 --- /dev/null +++ b/components/app/demo/message.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { message } = App.useApp(); + + const showMessage = () => { + message.success('Success!'); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/demo/modal.md b/components/app/demo/modal.md new file mode 100644 index 0000000000..f07f87c7c8 --- /dev/null +++ b/components/app/demo/modal.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `modal` 静态方法. + +## en-US + +Static method for `modal`. diff --git a/components/app/demo/modal.tsx b/components/app/demo/modal.tsx new file mode 100644 index 0000000000..3fe95c848d --- /dev/null +++ b/components/app/demo/modal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { modal } = App.useApp(); + + const showModal = () => { + modal.warning({ + title: 'This is a warning message', + content: 'some messages...some messages...', + }); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/demo/notification.md b/components/app/demo/notification.md new file mode 100644 index 0000000000..28fa841aab --- /dev/null +++ b/components/app/demo/notification.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `notification` 静态方法. + +## en-US + +Static method for `notification`. diff --git a/components/app/demo/notification.tsx b/components/app/demo/notification.tsx new file mode 100644 index 0000000000..fe8613a7f4 --- /dev/null +++ b/components/app/demo/notification.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { notification } = App.useApp(); + + const showNotification = () => { + notification.info({ + message: `Notification topLeft`, + description: 'Hello, Ant Design!!', + placement: 'topLeft', + }); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md new file mode 100644 index 0000000000..0c9fbc2694 --- /dev/null +++ b/components/app/index.en-US.md @@ -0,0 +1,43 @@ +--- +category: Components +group: Other +title: App +cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg +demo: + cols: 2 +--- + +New App Component which provide global style & static function replacement. + +## When To Use + +Static function in React 18 concurrent mode will not well support. In v5, we recommend to use hooks for the static replacement. But it will make user manual work on define this. + +## Examples + + +message +notification +modal + +## How to use + +```javascript +import React from 'react'; +import { App } from 'antd'; +const MyPage = () => { + const { message, notification, modal } = App.useApp(); + message.success('Good!'); + notification.info({ message: 'Good' }); + modal.warning({ title: 'Good' }); + // .... + // other message,notification,modal static function + return
Hello word
; +}; + +const MyApp = () => ( + + + +); +``` diff --git a/components/app/index.tsx b/components/app/index.tsx new file mode 100644 index 0000000000..0e8add0cd9 --- /dev/null +++ b/components/app/index.tsx @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import type { ReactNode } from 'react'; +import classNames from 'classnames'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import useStyle from './style'; +import useMessage from '../message/useMessage'; +import useNotification from '../notification/useNotification'; +import useModal from '../modal/useModal'; +import AppContext from './context'; +import type { useAppProps } from './context'; + +export type AppProps = { + className?: string; + prefixCls?: string; + children?: ReactNode; +}; + +const useApp: () => useAppProps = () => React.useContext(AppContext); + +const App: React.ForwardRefRenderFunction & { + useApp: () => useAppProps; +} = (props) => { + const { prefixCls: customizePrefixCls, children, className } = props; + const { getPrefixCls } = useContext(ConfigContext); + const prefixCls = getPrefixCls('app', customizePrefixCls); + const [wrapSSR, hashId] = useStyle(prefixCls); + const customClassName = classNames(hashId, prefixCls, className); + + const [messageApi, messageContextHolder] = useMessage(); + const [notificationApi, notificationContextHolder] = useNotification(); + const [ModalApi, ModalContextHolder] = useModal(); + + const memoizedContextValue = React.useMemo( + () => ({ + message: messageApi, + notification: notificationApi, + modal: ModalApi, + }), + [messageApi, notificationApi, ModalApi], + ); + + return wrapSSR( + +
+ {ModalContextHolder} + {messageContextHolder} + {notificationContextHolder} + {children} +
+
, + ); +}; + +if (process.env.NODE_ENV !== 'production') { + App.displayName = 'App'; +} + +App.useApp = useApp; +export default App; diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md new file mode 100644 index 0000000000..2ac40509bf --- /dev/null +++ b/components/app/index.zh-CN.md @@ -0,0 +1,45 @@ +--- +category: Components +subtitle: 包裹组件 +group: 其他 +title: App +cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg +demo: + cols: 2 +--- + +新的包裹组件,提供重置样式和提供消费上下文的默认环境。 + +## 何时使用 + +- 提供可消费 React context 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。 +- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。 + +## 代码演示 + + +message +notification +modal + +## How to use + +```javascript +import React from 'react'; +import { App } from 'antd'; +const MyPage = () => { + const { message, notification, modal } = App.useApp(); + message.success('Good!'); + notification.info({ message: 'Good' }); + modal.warning({ title: 'Good' }); + // .... + // other message,notification,modal static function + return
Hello word
; +}; + +const MyApp = () => ( + + + +); +``` diff --git a/components/app/style/index.tsx b/components/app/style/index.tsx new file mode 100644 index 0000000000..700aa8098b --- /dev/null +++ b/components/app/style/index.tsx @@ -0,0 +1,22 @@ +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook } from '../../theme/internal'; + +export type ComponentToken = {}; + +interface AppToken extends FullToken<'App'> {} + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = (token) => { + const { componentCls, colorText, fontSize, lineHeight, fontFamily } = token; + return { + [componentCls]: { + color: colorText, + fontSize, + lineHeight, + fontFamily, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('App', (token) => [genBaseStyle(token)]); diff --git a/components/index.tsx b/components/index.tsx index 9043a3add8..806fdee89a 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -135,6 +135,7 @@ export { default as Tooltip } from './tooltip'; export type { TooltipProps } from './tooltip'; export { default as Tour } from './tour'; export type { TourProps, TourStepProps } from './tour/interface'; +export { default as App } from './app'; export { default as Transfer } from './transfer'; export type { TransferProps } from './transfer'; export { default as Tree } from './tree'; @@ -149,4 +150,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 3691222058..1c93703022 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 4b87557341..080e2ebcd9 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 6d155dde0a..ea97e256bf 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/__tests__/index.test.tsx b/components/mentions/__tests__/index.test.tsx index 01b874f02c..943bc8f065 100644 --- a/components/mentions/__tests__/index.test.tsx +++ b/components/mentions/__tests__/index.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Mentions from '..'; +import Mentions, { Option } from '..'; import focusTest from '../../../tests/shared/focusTest'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; @@ -85,6 +85,20 @@ describe('Mentions', () => { expect(wrapper.container.querySelectorAll('.bamboo-light').length).toBeTruthy(); }); + it('warning if use Mentions.Option', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + render( + + + + + , + ); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [antd: Mentions] `Mentions.Option` is deprecated. Please use `options` instead.', + ); + }); + it('do not lose label when use children Option', () => { const wrapper = render( diff --git a/components/mentions/index.en-US.md b/components/mentions/index.en-US.md index 16980f9636..8d607c0b34 100644 --- a/components/mentions/index.en-US.md +++ b/components/mentions/index.en-US.md @@ -26,6 +26,24 @@ When you need to mention someone or something. Status _InternalPanelDoNotUseOrYouWillBeFired +### Usage upgrade after 5.1.0 + +```__react +import Alert from '../alert'; +ReactDOM.render(, mountNode); +``` + +```jsx +// works when >=5.1.0, recommended ✅ +const options = [{ value: 'sample', label: 'sample' }]; +return ; + +// works when <5.1.0, deprecated when >=5.1.0 🙅🏻‍♀️ + + Sample +; +``` + ## API ### Mention @@ -61,10 +79,11 @@ When you need to mention someone or something. ### Option -| Property | Description | Type | Default | -| --------- | --------------------------- | ------------------- | ------- | -| label | Title of the option | React.ReactNode | - | -| key | The key value of the option | string | - | -| disabled | Optional | boolean | - | -| className | className | string | - | -| style | The style of the option | React.CSSProperties | - | + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| label | Title of the option | React.ReactNode | - | +| key | The key value of the option | string | - | +| disabled | Optional | boolean | - | +| className | className | string | - | +| style | The style of the option | React.CSSProperties | - | diff --git a/components/mentions/index.tsx b/components/mentions/index.tsx index cc823f63d4..9bd5109ea3 100644 --- a/components/mentions/index.tsx +++ b/components/mentions/index.tsx @@ -15,6 +15,7 @@ import genPurePanel from '../_util/PurePanel'; import Spin from '../spin'; import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; +import warning from '../_util/warning'; import useStyle from './style'; @@ -86,6 +87,16 @@ const InternalMentions: React.ForwardRefRenderFunction(); const mergedRef = composeRef(ref, innerRef); + + // =================== Warning ===================== + if (process.env.NODE_ENV !== 'production') { + warning( + !children, + 'Mentions', + '`Mentions.Option` is deprecated. Please use `options` instead.', + ); + } + const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext); const { status: contextStatus, diff --git a/components/mentions/index.zh-CN.md b/components/mentions/index.zh-CN.md index 8aff2bb778..ac12075066 100644 --- a/components/mentions/index.zh-CN.md +++ b/components/mentions/index.zh-CN.md @@ -27,6 +27,24 @@ demo: 自定义状态 _InternalPanelDoNotUseOrYouWillBeFired +### 5.1.0 用法升级 + +```__react +import Alert from '../alert'; +ReactDOM.render(, mountNode); +``` + +```jsx +// >=5.1.0 可用,推荐的写法 ✅ +const options = [{ value: 'sample', label: 'sample' }]; +return ; + +// <5.1.0 可用,>=5.1.0 时不推荐 🙅🏻‍♀️ + + Sample +; +``` + ## API ### Mentions diff --git a/components/menu/MenuDivider.tsx b/components/menu/MenuDivider.tsx index ae93440a87..1441481bec 100644 --- a/components/menu/MenuDivider.tsx +++ b/components/menu/MenuDivider.tsx @@ -10,12 +10,8 @@ export interface MenuDividerProps extends React.HTMLAttributes { dashed?: boolean; } -const MenuDivider: React.FC = ({ - prefixCls: customizePrefixCls, - className, - dashed, - ...restProps -}) => { +const MenuDivider: React.FC = (props) => { + const { prefixCls: customizePrefixCls, className, dashed, ...restProps } = props; const { getPrefixCls } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('menu', customizePrefixCls); diff --git a/components/menu/MenuItem.tsx b/components/menu/MenuItem.tsx index 14d781c254..47e6ac1ee6 100644 --- a/components/menu/MenuItem.tsx +++ b/components/menu/MenuItem.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames'; import type { MenuItemProps as RcMenuItemProps } from 'rc-menu'; import { Item } from 'rc-menu'; import toArray from 'rc-util/lib/Children/toArray'; +import omit from 'rc-util/lib/omit'; import * as React from 'react'; import type { SiderContextProps } from '../layout/Sider'; import { SiderContext } from '../layout/Sider'; @@ -17,15 +18,16 @@ export interface MenuItemProps extends Omit { title?: React.ReactNode; } -export default class MenuItem extends React.Component { - static contextType = MenuContext; - - context: MenuContextProps; - - renderItemChildren(inlineCollapsed: boolean) { - const { prefixCls, firstLevel } = this.context; - const { icon, children } = this.props; - +const MenuItem: React.FC = (props) => { + const { className, children, icon, title, danger } = props; + const { + prefixCls, + firstLevel, + direction, + disableMenuItemTitleTooltip, + inlineCollapsed: isInlineCollapsed, + } = React.useContext(MenuContext); + const renderItemChildren = (inlineCollapsed: boolean) => { const wrapNode = {children}; // inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span // ref: https://github.com/ant-design/ant-design/pull/23456 @@ -35,25 +37,16 @@ export default class MenuItem extends React.Component { } } return wrapNode; - } - - renderItem = ({ siderCollapsed }: SiderContextProps) => { - const { prefixCls, firstLevel, inlineCollapsed, direction, disableMenuItemTitleTooltip } = - this.context; - const { className, children } = this.props; - const { title, icon, danger, ...rest } = this.props; - + }; + const renderItem = ({ siderCollapsed }: SiderContextProps) => { let tooltipTitle = title; if (typeof title === 'undefined') { tooltipTitle = firstLevel ? children : ''; } else if (title === false) { tooltipTitle = ''; } - const tooltipProps: TooltipProps = { - title: tooltipTitle, - }; - - if (!siderCollapsed && !inlineCollapsed) { + const tooltipProps: TooltipProps = { title: tooltipTitle }; + if (!siderCollapsed && !isInlineCollapsed) { tooltipProps.title = null; // Reset `open` to fix control mode tooltip display not correct // ref: https://github.com/ant-design/ant-design/issues/16742 @@ -63,7 +56,7 @@ export default class MenuItem extends React.Component { let returnNode = ( { `${prefixCls}-item-icon`, ), })} - {this.renderItemChildren(inlineCollapsed)} + {renderItemChildren(isInlineCollapsed)} ); @@ -97,8 +90,7 @@ export default class MenuItem extends React.Component { return returnNode; }; + return {renderItem}; +}; - render() { - return {this.renderItem}; - } -} +export default MenuItem; diff --git a/components/menu/OverrideContext.tsx b/components/menu/OverrideContext.tsx index 70dd828efe..900feebca5 100644 --- a/components/menu/OverrideContext.tsx +++ b/components/menu/OverrideContext.tsx @@ -15,17 +15,14 @@ export interface OverrideContextProps { const OverrideContext = React.createContext(null); /** @internal Only used for Dropdown component. Do not use this in your production. */ -export const OverrideProvider = ({ - children, - ...restProps -}: OverrideContextProps & { children: React.ReactNode }) => { +export const OverrideProvider: React.FC = ( + props, +) => { + const { children, ...restProps } = props; const override = React.useContext(OverrideContext); - const context = React.useMemo( - () => ({ - ...override, - ...restProps, - }), + const context = React.useMemo( + () => ({ ...override, ...restProps }), [ override, restProps.prefixCls, diff --git a/components/menu/SubMenu.tsx b/components/menu/SubMenu.tsx index 972f19e74b..1ae6b50840 100644 --- a/components/menu/SubMenu.tsx +++ b/components/menu/SubMenu.tsx @@ -3,7 +3,7 @@ import { SubMenu as RcSubMenu, useFullPath } from 'rc-menu'; import omit from 'rc-util/lib/omit'; import * as React from 'react'; import { cloneElement, isValidElement } from '../_util/reactNode'; -import type { MenuTheme } from './MenuContext'; +import type { MenuContextProps, MenuTheme } from './MenuContext'; import MenuContext from './MenuContext'; interface TitleEventEntity { @@ -27,7 +27,7 @@ export interface SubMenuProps { theme?: MenuTheme; } -function SubMenu(props: SubMenuProps) { +const SubMenu: React.FC = (props) => { const { popupClassName, icon, title, theme: customTheme } = props; const context = React.useContext(MenuContext); const { prefixCls, inlineCollapsed, theme: contextTheme, mode } = context; @@ -60,11 +60,8 @@ function SubMenu(props: SubMenuProps) { ); } - const contextValue = React.useMemo( - () => ({ - ...context, - firstLevel: false, - }), + const contextValue = React.useMemo( + () => ({ ...context, firstLevel: false }), [context], ); @@ -84,6 +81,6 @@ function SubMenu(props: SubMenuProps) { /> ); -} +}; export default SubMenu; diff --git a/components/menu/index.tsx b/components/menu/index.tsx index 3381d96fa5..0c7d9ac189 100644 --- a/components/menu/index.tsx +++ b/components/menu/index.tsx @@ -22,9 +22,9 @@ export type MenuRef = { type CompoundedComponent = React.ForwardRefExoticComponent< MenuProps & React.RefAttributes > & { - Divider: typeof MenuDivider; Item: typeof Item; SubMenu: typeof SubMenu; + Divider: typeof MenuDivider; ItemGroup: typeof ItemGroup; }; @@ -33,18 +33,17 @@ const Menu = forwardRef((props, ref) => { const context = React.useContext(SiderContext); useImperativeHandle(ref, () => ({ + menu: menuRef.current, focus: (options) => { menuRef.current?.focus(options); }, - menu: menuRef.current, })); - return ; }) as CompoundedComponent; -Menu.Divider = MenuDivider; Menu.Item = Item; Menu.SubMenu = SubMenu; +Menu.Divider = MenuDivider; Menu.ItemGroup = ItemGroup; export default Menu; diff --git a/components/menu/menu.tsx b/components/menu/menu.tsx index c98a532687..1600476deb 100644 --- a/components/menu/menu.tsx +++ b/components/menu/menu.tsx @@ -16,7 +16,7 @@ import OverrideContext from './OverrideContext'; import useItems from './hooks/useItems'; import type { ItemType } from './hooks/useItems'; import MenuContext from './MenuContext'; -import type { MenuTheme } from './MenuContext'; +import type { MenuTheme, MenuContextProps } from './MenuContext'; export interface MenuProps extends Omit { theme?: MenuTheme; @@ -131,7 +131,7 @@ const InternalMenu = forwardRef((props, ref) => { } // ======================== Context ========================== - const contextValue = React.useMemo( + const contextValue = React.useMemo( () => ({ prefixCls, inlineCollapsed: mergedInlineCollapsed || false, 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 0000000000..74af4d455c --- /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 0000000000..51e8a82e70 --- /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 0000000000..6dcb91c4b3 --- /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 0000000000..79dea1e4fa --- /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 0000000000..1cb1d77dd4 --- /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 0000000000..df82d405e5 --- /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 0000000000..6de938dab5 --- /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 0000000000..48c076f08c --- /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 0000000000..342e1505d4 --- /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 0000000000..d7c5bd3ef0 --- /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 0000000000..5092f0f86e --- /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 0000000000..f682498030 --- /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 0000000000..8b61184614 --- /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 0000000000..08e60002d8 --- /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 0000000000..35b06fff45 --- /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 0000000000..9967cf7fa2 --- /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 0000000000..c66ce361cc --- /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 0000000000..1df7f09c46 --- /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 0000000000..0995a120a1 --- /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 0000000000..05a34f1d8c --- /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 0000000000..77a6218592 --- /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 0000000000..bc25a6993a --- /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 0000000000..3f0a57bca6 --- /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 0000000000..63c2df1a6c --- /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 0000000000..eec3f9e27f --- /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 0000000000..16d2d74025 --- /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 0000000000..3371e02035 --- /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 0000000000..0cb4e323d7 --- /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/style/index.tsx b/components/style/index.tsx index 743ac86d50..bcac9ee92a 100644 --- a/components/style/index.tsx +++ b/components/style/index.tsx @@ -103,15 +103,11 @@ export const genLinkStyle = (token: DerivativeToken): CSSObject => ({ }, }); -export const genCommonStyle = (token: DerivativeToken, componentPrefixCls: string): CSSObject => { - const { fontFamily, fontSize } = token; - +export const genCommonStyle = (componentPrefixCls: string): CSSObject => { const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`; return { [rootPrefixSelector]: { - fontFamily, - fontSize, boxSizing: 'border-box', '&::before, &::after': { diff --git a/components/table/index.zh-CN.md b/components/table/index.zh-CN.md index 6859a3e00f..3c00afd166 100644 --- a/components/table/index.zh-CN.md +++ b/components/table/index.zh-CN.md @@ -164,7 +164,7 @@ const columns = [ | 参数 | 说明 | 类型 | 默认值 | 版本 | -| --- | --- | --- | --- | --- | +| --- | --- | --- | --- | --- | --- | | align | 设置列的对齐方式 | `left` \| `right` \| `center` | `left` | | | className | 列样式类名 | string | - | | | colSpan | 表头列合并,设置为 0 时,不渲染 | number | - | | diff --git a/components/tabs/index.en-US.md b/components/tabs/index.en-US.md index 757d1bbf80..bc88b0bb1e 100644 --- a/components/tabs/index.en-US.md +++ b/components/tabs/index.en-US.md @@ -41,7 +41,7 @@ Ant Design has 3 types of Tabs for different situations. | Property | Description | Type | Default | Version | -| --- | --- | --- | --- | --- | +| --- | --- | --- | --- | --- | --- | | activeKey | Current TabPane's key | string | - | | | addIcon | Customize add icon | ReactNode | - | 4.4.0 | | animated | Whether to change tabs with animation. Only works while `tabPosition="top"` | boolean \| { inkBar: boolean, tabPane: boolean } | { inkBar: true, tabPane: false } | | diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 2e4a427f56..02678f801e 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -46,6 +46,8 @@ 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'; +import type { ComponentToken as AppComponentToken } from '../../app/style'; export interface ComponentTokenMap { Affix?: {}; @@ -108,4 +110,6 @@ export interface ComponentTokenMap { Space?: SpaceComponentToken; Progress?: ProgressComponentToken; Tour?: TourComponentToken; + QRCode?: QRCodeComponentToken; + App?: AppComponentToken; } diff --git a/components/theme/themes/seed.ts b/components/theme/themes/seed.ts index 032bded2b7..2abdf240e8 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 0a45b6db69..fee69167ce 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/genComponentStyleHook.ts b/components/theme/util/genComponentStyleHook.ts index db2bd218bb..7b5d172fbc 100644 --- a/components/theme/util/genComponentStyleHook.ts +++ b/components/theme/util/genComponentStyleHook.ts @@ -87,7 +87,7 @@ export default function genComponentStyleHook `; +exports[`Tour custom step pre btn & next btn className & style 1`] = ` +
+
+
+
+ + + +
+
+ Show in Center +
+
+
+ Here is the content of Tour. +
+ +
+
+
+
+`; + exports[`Tour rtl render component should be rendered correctly in RTL direction 1`] = `null`; exports[`Tour single 1`] = ` diff --git a/components/tour/__tests__/index.test.tsx b/components/tour/__tests__/index.test.tsx index 5dcc54b5d6..f55b0fad05 100644 --- a/components/tour/__tests__/index.test.tsx +++ b/components/tour/__tests__/index.test.tsx @@ -259,4 +259,44 @@ describe('Tour', () => { panelRender({ total: undefined, title:
test
}, 0, 'default'); }).not.toThrow(); }); + + it('custom step pre btn & next btn className & style', () => { + const App: React.FC = () => ( + + ), + }, + ]} + /> + ); + + const { container } = render(); + // className + expect( + screen.getByRole('button', { name: 'Next' }).className.includes('customClassName'), + ).toEqual(true); + // style + expect(screen.getByRole('button', { name: 'Next' }).style.backgroundColor).toEqual( + 'rgb(69, 69, 255)', + ); + expect(container.firstChild).toMatchSnapshot(); + }); }); diff --git a/components/tour/interface.ts b/components/tour/interface.ts index 1bcc1eef78..7143bc40d1 100644 --- a/components/tour/interface.ts +++ b/components/tour/interface.ts @@ -15,8 +15,18 @@ export interface TourProps extends Omit { export interface TourStepProps extends RCTourStepProps { cover?: ReactNode; // 展示的图片或者视频 - nextButtonProps?: { children?: ReactNode; onClick?: () => void }; - prevButtonProps?: { children?: ReactNode; onClick?: () => void }; + nextButtonProps?: { + children?: ReactNode; + onClick?: () => void; + className?: string; + style?: React.CSSProperties; + }; + prevButtonProps?: { + children?: ReactNode; + onClick?: () => void; + className?: string; + style?: React.CSSProperties; + }; stepRender?: (current: number, total: number) => ReactNode; type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色 } diff --git a/components/tour/panelRender.tsx b/components/tour/panelRender.tsx index 867780f997..cb9d777fdf 100644 --- a/components/tour/panelRender.tsx +++ b/components/tour/panelRender.tsx @@ -103,7 +103,7 @@ const panelRender = ( {...prevButtonProps} onClick={prevBtnClick} size="small" - className={`${prefixCls}-prev-btn`} + className={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)} > {prevButtonProps?.children ?? contextLocale.Previous} @@ -113,7 +113,7 @@ const panelRender = ( {...nextButtonProps} onClick={nextBtnClick} size="small" - className={`${prefixCls}-next-btn`} + className={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)} > {nextButtonProps?.children ?? (isLastStep ? contextLocale.Finish : contextLocale.Next)} diff --git a/components/tour/style/index.tsx b/components/tour/style/index.tsx index 3a892c2261..c32d4d93fc 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/components/tree/index.zh-CN.md b/components/tree/index.zh-CN.md index 5066acd18d..20e2a5db92 100644 --- a/components/tree/index.zh-CN.md +++ b/components/tree/index.zh-CN.md @@ -37,7 +37,7 @@ demo: | 参数 | 说明 | 类型 | 默认值 | 版本 | -| --- | --- | --- | --- | --- | +| --- | --- | --- | --- | --- | --- | | allowDrop | 是否允许拖拽时放置在该节点 | ({ dropNode, dropPosition }) => boolean | - | | | autoExpandParent | 是否自动展开父节点 | boolean | false | | | blockNode | 是否节点占据一行 | boolean | false | | diff --git a/package.json b/package.json index a18fbea8d9..3c3952b0cc 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", @@ -321,7 +322,7 @@ "bundlesize": [ { "path": "./dist/antd.min.js", - "maxSize": "377 kB" + "maxSize": "381 kB" } ], "tnpm": { diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index b867fa54bc..abcb505f0b 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -5,6 +5,7 @@ exports[`antd dist files exports modules correctly 1`] = ` "Affix", "Alert", "Anchor", + "App", "AutoComplete", "Avatar", "BackTop", @@ -40,6 +41,7 @@ exports[`antd dist files exports modules correctly 1`] = ` "Popconfirm", "Popover", "Progress", + "QRCode", "Radio", "Rate", "Result", diff --git a/tests/shared/imageTest.tsx b/tests/shared/imageTest.tsx index 777a3f6044..65a26b519b 100644 --- a/tests/shared/imageTest.tsx +++ b/tests/shared/imageTest.tsx @@ -7,6 +7,7 @@ import glob from 'glob'; import { configureToMatchImageSnapshot } from 'jest-image-snapshot'; import MockDate from 'mockdate'; import ReactDOMServer from 'react-dom/server'; +import { App } from '../../components'; const toMatchImageSnapshot = configureToMatchImageSnapshot({ customSnapshotsDir: `${process.cwd()}/imageSnapshots`, @@ -35,7 +36,9 @@ export default function imageTest(component: React.ReactElement) { const cache = createCache(); const html = ReactDOMServer.renderToString( - {component}, + + {component}, + , ); const styleStr = extractStyle(cache);