diff --git a/components/alert/__tests__/index.test.tsx b/components/alert/__tests__/index.test.tsx index c25f6191da..8bebb5ecb0 100644 --- a/components/alert/__tests__/index.test.tsx +++ b/components/alert/__tests__/index.test.tsx @@ -1,13 +1,12 @@ import React from 'react'; -import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import Button from '../../button'; -import Tooltip from '../../tooltip'; -import Popconfirm from '../../popconfirm'; -import rtlTest from '../../../tests/shared/rtlTest'; -import accessibilityTest from '../../../tests/shared/accessibilityTest'; -import { sleep } from '../../../tests/utils'; import Alert from '..'; +import accessibilityTest from '../../../tests/shared/accessibilityTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { fireEvent, render, sleep } from '../../../tests/utils'; +import Button from '../../button'; +import Popconfirm from '../../popconfirm'; +import Tooltip from '../../tooltip'; const { ErrorBoundary } = Alert; @@ -25,7 +24,7 @@ describe('Alert', () => { it('could be closed', () => { const onClose = jest.fn(); - const wrapper = mount( + const { container } = render( { onClose={onClose} />, ); + act(() => { jest.useFakeTimers(); - wrapper.find('.ant-alert-close-icon').simulate('click'); + fireEvent.click(container.querySelector('.ant-alert-close-icon')!); jest.runAllTimers(); jest.useRealTimers(); }); @@ -44,7 +44,7 @@ describe('Alert', () => { describe('action of Alert', () => { it('custom action', () => { - const wrapper = mount( + const { container } = render( { closable />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); }); it('support closeIcon', () => { - const wrapper = mount( + const { container } = render( close} @@ -70,26 +70,26 @@ describe('Alert', () => { type="warning" />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); describe('data and aria props', () => { it('sets data attributes on input', () => { - const wrapper = mount(); - const input = wrapper.find('.ant-alert').getDOMNode(); + const { container } = render(); + const input = container.querySelector('.ant-alert')!; expect(input.getAttribute('data-test')).toBe('test-id'); expect(input.getAttribute('data-id')).toBe('12345'); }); it('sets aria attributes on input', () => { - const wrapper = mount(); - const input = wrapper.find('.ant-alert').getDOMNode(); + const { container } = render(); + const input = container.querySelector('.ant-alert')!; expect(input.getAttribute('aria-describedby')).toBe('some-label'); }); it('sets role attribute on input', () => { - const wrapper = mount(); - const input = wrapper.find('.ant-alert').getDOMNode(); + const { container } = render(); + const input = container.querySelector('.ant-alert')!; expect(input.getAttribute('role')).toBe('status'); }); }); @@ -101,13 +101,13 @@ describe('Alert', () => { // @ts-expect-error // eslint-disable-next-line react/jsx-no-undef const ThrowError = () => ; - const wrapper = mount( + const { container } = render( , ); // eslint-disable-next-line jest/no-standalone-expect - expect(wrapper.text()).toContain('ReferenceError: NotExisted is not defined'); + expect(container.textContent).toContain('ReferenceError: NotExisted is not defined'); // eslint-disable-next-line no-console (console.error as any).mockRestore(); }); @@ -115,7 +115,7 @@ describe('Alert', () => { it('could be used with Tooltip', async () => { const ref = React.createRef(); jest.useRealTimers(); - const wrapper = mount( + const { container } = render( { /> , ); - wrapper.find('.ant-alert').simulate('mouseenter'); + // wrapper.find('.ant-alert').simulate('mouseenter'); + fireEvent.mouseEnter(container.querySelector('.ant-alert')!); await sleep(0); expect(ref.current.getPopupDomNode()).toBeTruthy(); jest.useFakeTimers(); @@ -132,7 +133,7 @@ describe('Alert', () => { it('could be used with Popconfirm', async () => { const ref = React.createRef(); jest.useRealTimers(); - const wrapper = mount( + const { container } = render( { /> , ); - wrapper.find('.ant-alert').simulate('click'); + fireEvent.click(container.querySelector('.ant-alert')!); await sleep(0); expect(ref.current.getPopupDomNode()).toBeTruthy(); jest.useFakeTimers(); }); it('could accept none react element icon', () => { - const wrapper = mount(); - expect(wrapper.render()).toMatchSnapshot(); + const { container } = render( + , + ); + expect(container.firstChild).toMatchSnapshot(); }); it('should not render message div when no message', () => { - const wrapper = mount(); - expect(wrapper.exists('.ant-alert-message')).toBe(false); + const { container } = render(); + expect(!!container.querySelector('.ant-alert-message')).toBe(false); }); }); diff --git a/components/card/Card.tsx b/components/card/Card.tsx new file mode 100644 index 0000000000..e2c71f8c36 --- /dev/null +++ b/components/card/Card.tsx @@ -0,0 +1,175 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import omit from 'rc-util/lib/omit'; +import Tabs from '../tabs'; +import Grid from './Grid'; +import { ConfigContext } from '../config-provider'; +import SizeContext from '../config-provider/SizeContext'; +import type { TabsProps } from '../tabs'; +import Skeleton from '../skeleton'; + +export type CardType = 'inner'; +export type CardSize = 'default' | 'small'; + +export interface CardTabListType { + key: string; + tab: React.ReactNode; + disabled?: boolean; +} + +export interface CardProps extends Omit, 'title'> { + prefixCls?: string; + title?: React.ReactNode; + extra?: React.ReactNode; + bordered?: boolean; + headStyle?: React.CSSProperties; + bodyStyle?: React.CSSProperties; + style?: React.CSSProperties; + loading?: boolean; + hoverable?: boolean; + children?: React.ReactNode; + id?: string; + className?: string; + size?: CardSize; + type?: CardType; + cover?: React.ReactNode; + actions?: React.ReactNode[]; + tabList?: CardTabListType[]; + tabBarExtraContent?: React.ReactNode; + onTabChange?: (key: string) => void; + activeTabKey?: string; + defaultActiveTabKey?: string; + tabProps?: TabsProps; +} + +function getAction(actions: React.ReactNode[]) { + const actionList = actions.map((action, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  • + {action} +
  • + )); + return actionList; +} + +const Card = React.forwardRef((props: CardProps, ref: React.Ref) => { + const { getPrefixCls, direction } = React.useContext(ConfigContext); + const size = React.useContext(SizeContext); + + const onTabChange = (key: string) => { + props.onTabChange?.(key); + }; + + const isContainGrid = () => { + let containGrid; + React.Children.forEach(props.children, (element: JSX.Element) => { + if (element && element.type && element.type === Grid) { + containGrid = true; + } + }); + return containGrid; + }; + + const { + prefixCls: customizePrefixCls, + className, + extra, + headStyle = {}, + bodyStyle = {}, + title, + loading, + bordered = true, + size: customizeSize, + type, + cover, + actions, + tabList, + children, + activeTabKey, + defaultActiveTabKey, + tabBarExtraContent, + hoverable, + tabProps = {}, + ...others + } = props; + + const prefixCls = getPrefixCls('card', customizePrefixCls); + + const loadingBlock = ( + + {children} + + ); + + const hasActiveTabKey = activeTabKey !== undefined; + const extraProps = { + ...tabProps, + [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey + ? activeTabKey + : defaultActiveTabKey, + tabBarExtraContent, + }; + + let head: React.ReactNode; + const tabs = + tabList && tabList.length ? ( + + {tabList.map(item => ( + + ))} + + ) : null; + if (title || extra || tabs) { + head = ( +
    +
    + {title &&
    {title}
    } + {extra &&
    {extra}
    } +
    + {tabs} +
    + ); + } + const coverDom = cover ?
    {cover}
    : null; + const body = ( +
    + {loading ? loadingBlock : children} +
    + ); + const actionDom = + actions && actions.length ? ( +
      {getAction(actions)}
    + ) : null; + const divProps = omit(others, ['onTabChange']); + const mergedSize = customizeSize || size; + const classString = classNames( + prefixCls, + { + [`${prefixCls}-loading`]: loading, + [`${prefixCls}-bordered`]: bordered, + [`${prefixCls}-hoverable`]: hoverable, + [`${prefixCls}-contain-grid`]: isContainGrid(), + [`${prefixCls}-contain-tabs`]: tabList && tabList.length, + [`${prefixCls}-${mergedSize}`]: mergedSize, + [`${prefixCls}-type-${type}`]: !!type, + [`${prefixCls}-rtl`]: direction === 'rtl', + }, + className, + ); + + return ( +
    + {head} + {coverDom} + {body} + {actionDom} +
    + ); +}); + +export default Card; diff --git a/components/card/__tests__/type.test.tsx b/components/card/__tests__/type.test.tsx new file mode 100644 index 0000000000..19eae9739f --- /dev/null +++ b/components/card/__tests__/type.test.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import Card from '../index'; + +describe('Card.typescript', () => { + it('ref', () => { + function Demo() { + const cardRef = React.useRef(null); + + return ; + } + + expect(Demo).toBeTruthy(); + }); +}); diff --git a/components/card/index.tsx b/components/card/index.tsx index e4e2776b17..75c561a9f2 100644 --- a/components/card/index.tsx +++ b/components/card/index.tsx @@ -1,185 +1,19 @@ -import * as React from 'react'; -import classNames from 'classnames'; -import omit from 'rc-util/lib/omit'; import Grid from './Grid'; import Meta from './Meta'; -import type { TabsProps } from '../tabs'; -import Tabs from '../tabs'; -import { ConfigContext } from '../config-provider'; -import SizeContext from '../config-provider/SizeContext'; -import Skeleton from '../skeleton'; - -function getAction(actions: React.ReactNode[]) { - const actionList = actions.map((action, index) => ( - // eslint-disable-next-line react/no-array-index-key -
  • - {action} -
  • - )); - return actionList; -} +import InternalCard from './Card'; export { CardGridProps } from './Grid'; export { CardMetaProps } from './Meta'; +export { CardProps, CardTabListType } from './Card'; -export type CardType = 'inner'; -export type CardSize = 'default' | 'small'; +type InternalCardType = typeof InternalCard; -export interface CardTabListType { - key: string; - tab: React.ReactNode; - disabled?: boolean; -} - -export interface CardProps extends Omit, 'title'> { - prefixCls?: string; - title?: React.ReactNode; - extra?: React.ReactNode; - bordered?: boolean; - headStyle?: React.CSSProperties; - bodyStyle?: React.CSSProperties; - style?: React.CSSProperties; - loading?: boolean; - hoverable?: boolean; - children?: React.ReactNode; - id?: string; - className?: string; - size?: CardSize; - type?: CardType; - cover?: React.ReactNode; - actions?: React.ReactNode[]; - tabList?: CardTabListType[]; - tabBarExtraContent?: React.ReactNode; - onTabChange?: (key: string) => void; - activeTabKey?: string; - defaultActiveTabKey?: string; - tabProps?: TabsProps; -} - -export interface CardInterface extends React.ForwardRefExoticComponent { +export interface CardInterface extends InternalCardType { Grid: typeof Grid; Meta: typeof Meta; } -const Card = React.forwardRef((props: CardProps, ref: React.Ref) => { - const { getPrefixCls, direction } = React.useContext(ConfigContext); - const size = React.useContext(SizeContext); - - const onTabChange = (key: string) => { - props.onTabChange?.(key); - }; - - const isContainGrid = () => { - let containGrid; - React.Children.forEach(props.children, (element: JSX.Element) => { - if (element && element.type && element.type === Grid) { - containGrid = true; - } - }); - return containGrid; - }; - - const { - prefixCls: customizePrefixCls, - className, - extra, - headStyle = {}, - bodyStyle = {}, - title, - loading, - bordered = true, - size: customizeSize, - type, - cover, - actions, - tabList, - children, - activeTabKey, - defaultActiveTabKey, - tabBarExtraContent, - hoverable, - tabProps = {}, - ...others - } = props; - - const prefixCls = getPrefixCls('card', customizePrefixCls); - - const loadingBlock = ( - - {children} - - ); - - const hasActiveTabKey = activeTabKey !== undefined; - const extraProps = { - ...tabProps, - [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey - ? activeTabKey - : defaultActiveTabKey, - tabBarExtraContent, - }; - - let head: React.ReactNode; - const tabs = - tabList && tabList.length ? ( - - {tabList.map(item => ( - - ))} - - ) : null; - if (title || extra || tabs) { - head = ( -
    -
    - {title &&
    {title}
    } - {extra &&
    {extra}
    } -
    - {tabs} -
    - ); - } - const coverDom = cover ?
    {cover}
    : null; - const body = ( -
    - {loading ? loadingBlock : children} -
    - ); - const actionDom = - actions && actions.length ? ( -
      {getAction(actions)}
    - ) : null; - const divProps = omit(others, ['onTabChange']); - const mergedSize = customizeSize || size; - const classString = classNames( - prefixCls, - { - [`${prefixCls}-loading`]: loading, - [`${prefixCls}-bordered`]: bordered, - [`${prefixCls}-hoverable`]: hoverable, - [`${prefixCls}-contain-grid`]: isContainGrid(), - [`${prefixCls}-contain-tabs`]: tabList && tabList.length, - [`${prefixCls}-${mergedSize}`]: mergedSize, - [`${prefixCls}-type-${type}`]: !!type, - [`${prefixCls}-rtl`]: direction === 'rtl', - }, - className, - ); - - return ( -
    - {head} - {coverDom} - {body} - {actionDom} -
    - ); -}) as CardInterface; +const Card = InternalCard as CardInterface; Card.Grid = Grid; Card.Meta = Meta; diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md index f630a35675..5e03aef6a4 100644 --- a/components/collapse/index.zh-CN.md +++ b/components/collapse/index.zh-CN.md @@ -27,7 +27,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/IxH16B9RD/Collapse.svg | defaultActiveKey | 初始化选中面板的 key | string\[] \| string
    number\[] \| number | - | | | destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | false | | | expandIcon | 自定义切换图标 | (panelProps) => ReactNode | - | | -| expandIconPosition | 设置图标位置 | `start` \| `end` | - | | +| expandIconPosition | 设置图标位置 | `start` \| `end` | - | 4.21.0 | | ghost | 使折叠面板透明且无边框 | boolean | false | 4.4.0 | | onChange | 切换面板的回调 | function | - | | diff --git a/components/popover/style/index.less b/components/popover/style/index.less index 6cc7c294be..1b3d10417e 100644 --- a/components/popover/style/index.less +++ b/components/popover/style/index.less @@ -3,7 +3,7 @@ @popover-prefix-cls: ~'@{ant-prefix}-popover'; -@popover-arrow-rotate-width: sqrt(@popover-arrow-width * @popover-arrow-width * 2); +@popover-arrow-rotate-width: sqrt(@popover-arrow-width * @popover-arrow-width * 2) + 6px; @popover-arrow-offset-vertical: 12px; @popover-arrow-offset-horizontal: 16px; @@ -21,6 +21,10 @@ cursor: auto; user-select: text; + &-content { + position: relative; + } + &::after { position: absolute; background: fade(@white, 1%); @@ -144,7 +148,8 @@ &-placement-top &-arrow, &-placement-topLeft &-arrow, &-placement-topRight &-arrow { - bottom: @popover-distance - @popover-arrow-rotate-width; + bottom: 0; + transform: translateY(100%); &-content { box-shadow: 3px 3px 7px fade(@black, 7%); @@ -154,7 +159,7 @@ &-placement-top &-arrow { left: 50%; - transform: translateX(-50%); + transform: translateY(100%) translateX(-50%); } &-placement-topLeft &-arrow { @@ -168,7 +173,8 @@ &-placement-right &-arrow, &-placement-rightTop &-arrow, &-placement-rightBottom &-arrow { - left: @popover-distance - @popover-arrow-rotate-width; + left: 0; + transform: translateX(-100%); &-content { box-shadow: 3px 3px 7px fade(@black, 7%); @@ -178,7 +184,7 @@ &-placement-right &-arrow { top: 50%; - transform: translateY(-50%); + transform: translateX(-100%) translateY(-50%); } &-placement-rightTop &-arrow { @@ -192,7 +198,8 @@ &-placement-bottom &-arrow, &-placement-bottomLeft &-arrow, &-placement-bottomRight &-arrow { - top: @popover-distance - @popover-arrow-rotate-width; + top: 0; + transform: translateY(-100%); &-content { box-shadow: 2px 2px 5px fade(@black, 6%); @@ -202,7 +209,7 @@ &-placement-bottom &-arrow { left: 50%; - transform: translateX(-50%); + transform: translateY(-100%) translateX(-50%); } &-placement-bottomLeft &-arrow { @@ -216,7 +223,8 @@ &-placement-left &-arrow, &-placement-leftTop &-arrow, &-placement-leftBottom &-arrow { - right: @popover-distance - @popover-arrow-rotate-width; + right: 0; + transform: translateX(100%); &-content { box-shadow: 3px 3px 7px fade(@black, 7%); @@ -226,7 +234,7 @@ &-placement-left &-arrow { top: 50%; - transform: translateY(-50%); + transform: translateX(100%) translateY(-50%); } &-placement-leftTop &-arrow { diff --git a/components/select/demo/select-users.md b/components/select/demo/select-users.md index f667050d6f..27a00a0cc0 100644 --- a/components/select/demo/select-users.md +++ b/components/select/demo/select-users.md @@ -20,14 +20,14 @@ import debounce from 'lodash/debounce'; import React, { useMemo, useRef, useState } from 'react'; export interface DebounceSelectProps - extends Omit, 'options' | 'children'> { + extends Omit, 'options' | 'children'> { fetchOptions: (search: string) => Promise; debounceTimeout?: number; } function DebounceSelect< ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any, ->({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps) { +>({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps) { const [fetching, setFetching] = useState(false); const [options, setOptions] = useState([]); const fetchRef = useRef(0); @@ -54,7 +54,7 @@ function DebounceSelect< }, [fetchOptions, debounceTimeout]); return ( - +