diff --git a/docs/demo/fragment.md b/docs/demo/fragment.md deleted file mode 100644 index de859a4..0000000 --- a/docs/demo/fragment.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: fragment -nav: - title: Demo - path: /demo ---- - - diff --git a/docs/examples/custom-icon.tsx b/docs/examples/custom-icon.tsx index ea3df8e..96b24f7 100644 --- a/docs/examples/custom-icon.tsx +++ b/docs/examples/custom-icon.tsx @@ -1,6 +1,7 @@ -import Collapse, { Panel } from 'rc-collapse'; +import Collapse from 'rc-collapse'; import * as React from 'react'; import '../../assets/index.less'; +import type { ItemType } from '../../src/interface'; import motion from './_util/motionUtil'; const initLength = 3; @@ -48,31 +49,54 @@ const App: React.FC = () => { const time = random(); - const panelItems = Array.from({ length: initLength }, (_, i) => { + const panelItems = Array.from({ length: initLength }, (_, i) => { const key = i + 1; - return ( - -

{text.repeat(time)}

-
- ); + return { + key, + header: `This is panel header ${key}`, + children:

{text.repeat(time)}

, + }; }).concat( - - - -

{text}

-
-
-
, - - - -
- - -
-
-
-
, + { + key: initLength + 1, + header: `This is panel header ${initLength + 1}`, + children: ( + {text}

, + }, + ]} + /> + ), + }, + { + key: initLength + 2, + header: `This is panel header ${initLength + 2}`, + children: ( + + + + + ), + }, + ]} + /> + ), + }, ); const tools = ( @@ -104,9 +128,8 @@ const App: React.FC = () => { activeKey={activeKey} expandIcon={expandIcon} openMotion={motion} - > - {panelItems} - + items={panelItems} + /> ); }; diff --git a/docs/examples/fragment.tsx b/docs/examples/fragment.tsx deleted file mode 100644 index bb38c5b..0000000 --- a/docs/examples/fragment.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Collapse, { Panel } from 'rc-collapse'; -import * as React from 'react'; -import { Fragment } from 'react'; -import '../../assets/index.less'; - -const App = () => ( - - content - content - - content - content - - - - content - content - - - -); - -export default App; diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx index 5353415..2201d43 100644 --- a/docs/examples/simple.tsx +++ b/docs/examples/simple.tsx @@ -1,7 +1,8 @@ import type { CollapseProps } from 'rc-collapse'; -import Collapse, { Panel } from 'rc-collapse'; +import Collapse from 'rc-collapse'; import * as React from 'react'; import '../../assets/index.less'; +import type { ItemType } from '../../src/interface'; import motion from './_util/motionUtil'; const initLength = 3; @@ -50,38 +51,60 @@ const App: React.FC = () => { const time = random(); - const panelItems = Array.from({ length: initLength }, (_, i) => { + const panelItems = Array.from({ length: initLength }, (_, i) => { const key = i + 1; - return ( - -

{text.repeat(time)}

-
- ); + return { + key, + header: `This is panel header ${key}`, + children:

{text.repeat(time)}

, + }; }).concat( - - - -

{text}

-
-
-
, - - - -
- - -
-
-
-
, - Extra Node} - > -

Panel with extra

-
, + { + key: initLength + 1, + header: `This is panel header ${initLength + 1}`, + children: ( + {text}

, + }, + ]} + /> + ), + }, + { + key: initLength + 2, + header: `This is panel header ${initLength + 2}`, + children: ( + + + + + ), + }, + ]} + /> + ), + }, + { + key: initLength + 3, + header: `This is panel header ${initLength + 3}`, + extra: Extra Node, + children:

Panel with extra

, + }, ); const handleCollapsibleChange = (e: React.ChangeEvent) => { @@ -129,9 +152,8 @@ const App: React.FC = () => { expandIcon={expandIcon} openMotion={motion} collapsible={collapsible} - > - {panelItems} -
+ items={panelItems} + /> ); }; diff --git a/src/Collapse.tsx b/src/Collapse.tsx index 36267d2..52519ef 100644 --- a/src/Collapse.tsx +++ b/src/Collapse.tsx @@ -1,6 +1,5 @@ import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import warning from 'rc-util/lib/warning'; import React from 'react'; import useItems from './hooks/useItems'; import type { CollapseProps } from './interface'; @@ -23,7 +22,6 @@ const Collapse = React.forwardRef((props, ref) => style, accordion, className, - children, collapsible, openMotion, expandIcon, @@ -58,12 +56,7 @@ const Collapse = React.forwardRef((props, ref) => }); // ======================== Children ======================== - warning( - !children, - '`children` will be removed in next major version. Please use `items` instead.', - ); - - const mergedChildren = useItems(items, children, { + const mergedChildren = useItems(items, { prefixCls, accordion, openMotion, diff --git a/src/hooks/useItems.tsx b/src/hooks/useItems.tsx index 2bc5db7..cc39737 100644 --- a/src/hooks/useItems.tsx +++ b/src/hooks/useItems.tsx @@ -1,4 +1,3 @@ -import toArray from 'rc-util/lib/Children/toArray'; import React from 'react'; import type { CollapsePanelProps, CollapseProps, ItemType } from '../interface'; import CollapsePanel from '../Panel'; @@ -31,8 +30,6 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => { ...restProps } = item; - // You may be puzzled why you want to convert them all into strings, me too. - // Maybe: https://github.com/react-component/collapse/blob/aac303a8b6ff30e35060b4f8fecde6f4556fcbe2/src/Collapse.tsx#L15 const key = String(rawKey ?? index); const mergeCollapsible = rawCollapsible ?? collapsible; const mergeDestroyInactivePanel = rawDestroyInactivePanel ?? destroyInactivePanel; @@ -71,92 +68,12 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => { }); }; -/** - * @deprecated The next major version will be removed - */ -const getNewChild = ( - child: React.ReactElement, - index: number, - props: Props, -) => { - if (!child) return null; - - const { - prefixCls, - accordion, - collapsible, - destroyInactivePanel, - onItemClick, - activeKey, - openMotion, - expandIcon, - } = props; - - const key = child.key || String(index); - - const { - header, - headerClass, - destroyInactivePanel: childDestroyInactivePanel, - collapsible: childCollapsible, - onItemClick: childOnItemClick, - } = child.props; - - let isActive = false; - if (accordion) { - isActive = activeKey[0] === key; - } else { - isActive = activeKey.indexOf(key) > -1; - } - - const mergeCollapsible = childCollapsible ?? collapsible; - - const handleItemClick = (value: React.Key) => { - if (mergeCollapsible === 'disabled') return; - onItemClick(value); - childOnItemClick?.(value); - }; - - const childProps = { - key, - panelKey: key, - header, - headerClass, - isActive, - prefixCls, - destroyInactivePanel: childDestroyInactivePanel ?? destroyInactivePanel, - openMotion, - accordion, - children: child.props.children, - onItemClick: handleItemClick, - expandIcon, - collapsible: mergeCollapsible, - }; - - // https://github.com/ant-design/ant-design/issues/20479 - if (typeof child.type === 'string') { - return child; - } - - Object.keys(childProps).forEach((propName) => { - if (typeof childProps[propName] === 'undefined') { - delete childProps[propName]; - } - }); - - return React.cloneElement(child, childProps); -}; - -function useItems( - items?: ItemType[], - rawChildren?: React.ReactNode, - props?: Props, -): React.ReactElement[] { +function useItems(items?: ItemType[], props?: Props) { if (Array.isArray(items)) { return convertItemsToNodes(items, props); } - return toArray(rawChildren).map((child, index) => getNewChild(child, index, props)); + return null; } export default useItems; diff --git a/src/index.tsx b/src/index.tsx index 8e963c6..87e21e7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,2 @@ -import Collapse from './Collapse'; - +export { default } from './Collapse'; export type { CollapsePanelProps, CollapseProps } from './interface'; - -export default Collapse; - -/** - * @deprecated use `items` instead, will be removed in `v4.0.0` - */ -export const { Panel } = Collapse; diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 533df08..cd2a509 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -1,8 +1,8 @@ import type { RenderResult } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; -import React, { Fragment } from 'react'; -import Collapse, { Panel } from '../src/index'; +import React from 'react'; +import Collapse from '../src/index'; import type { CollapseProps, ItemType } from '../src/interface'; describe('collapse', () => { @@ -24,6 +24,27 @@ describe('collapse', () => { } } + const items: ItemType[] = [ + { + key: '1', + label: 'collapse 1', + collapsible: 'disabled', + children: 'first', + }, + { + key: '2', + label: 'collapse 2', + extra: ExtraSpan, + children: 'second', + }, + { + key: '3', + label: 'collapse 3', + className: 'important', + children: 'third', + }, + ]; + function runNormalTest(element: any) { let collapse: RenderResult; @@ -115,21 +136,7 @@ describe('collapse', () => { describe('collapse', () => { const expandIcon = () => test{'>'}; - const element = ( - - - first - - ExtraSpan}> - second - - - third - - - ); - - runNormalTest(element); + runNormalTest(); it('controlled', () => { const onChangeSpy = jest.fn(); @@ -143,17 +150,27 @@ describe('collapse', () => { }; return ( - - - first - - - second - - - third - - + ); }; @@ -170,31 +187,32 @@ describe('collapse', () => { }); describe('it should support number key', () => { - const expandIcon = () => test{'>'}; - const element = ( - - - first - - ExtraSpan}> - second - - - third - - + runNormalTest( + test{'>'}} + items={items.map((item) => ({ + ...item, + key: Number(item.key), + }))} + />, ); - - runNormalTest(element); }); it('shoule support extra whit number 0', () => { const { container } = render( - - - zero - - , + , ); const extraNodes = container.querySelectorAll('.rc-collapse-extra'); @@ -204,17 +222,14 @@ describe('collapse', () => { it('should support activeKey number 0', () => { const { container } = render( - - - zero - - - first - - - second - - , + ({ + ...item, + key: index, + }))} + />, ); // activeKey number 0, should open one item @@ -223,17 +238,7 @@ describe('collapse', () => { it('click should toggle panel state', () => { const { container } = render( - - - first - - - second - - - third - - , + , ); const header = container.querySelectorAll('.rc-collapse-header')?.[1]; @@ -313,59 +318,89 @@ describe('collapse', () => { describe('prop: accordion', () => { runAccordionTest( - - - first - - - second - - - third - - , + , ); }); describe('forceRender', () => { it('when forceRender is not supplied it should lazy render the panel content', () => { const { container } = render( - - - first - - - second - - , + , ); expect(container.querySelectorAll('.rc-collapse-content')).toHaveLength(0); }); it('when forceRender is FALSE it should lazy render the panel content', () => { const { container } = render( - - - first - - - second - - , + , ); expect(container.querySelectorAll('.rc-collapse-content')).toHaveLength(0); }); it('when forceRender is TRUE then it should render all the panel content to the DOM', () => { const { container } = render( - - - first - - - second - - , + , ); jest.runAllTimers(); @@ -388,17 +423,26 @@ describe('collapse', () => { }; const { container } = render( - - - first - - - second - - - second - - , + , ); fireEvent.keyPress(container.querySelectorAll('.rc-collapse-header')?.[2], myKeyEvent); @@ -423,46 +467,59 @@ describe('collapse', () => { ); }); - describe('wrapped in Fragment', () => { - const expandIcon = () => test{'>'}; - const element = ( - - - - first - - ExtraSpan}> - second - - - - third - - - - - ); - - runNormalTest(element); - }); + // TODO: 移除 Panel 后需要在 Ant Design 里面测试,待 Ant Design 稳定后再移除 assignee: @wxh16144 + // describe('wrapped in Fragment', () => { + // const expandIcon = () => test{'>'}; + // const element = ( + // + // + // + // first + // + // ExtraSpan}> + // second + // + // + // + // third + // + // + // + // + // ); + + // runNormalTest(element); + // }); it('should support return null icon', () => { const { container } = render( - null}> - - first - - , + null} + items={[ + { + key: '1', + label: 'title', + children: 'first', + }, + ]} + />, ); expect(container.querySelector('.rc-collapse-header')?.childNodes).toHaveLength(1); }); - it('should support custom child', () => { + // TODO: 需要在 Ant Design 里面测试,待 Ant Design 稳定后再移除 assignee: @wxh16144 + // 按理说我们不允许用户这样用,但是为了兼容性,我们还是保留这个测试 + it.skip('should support custom child', () => { const { container } = render( - - - first - + custom-child , ); @@ -472,44 +529,49 @@ describe('collapse', () => { // https://github.com/ant-design/ant-design/issues/36327 // https://github.com/ant-design/ant-design/issues/6179 // https://github.com/react-component/collapse/issues/73#issuecomment-323626120 - it('should support custom component', () => { - const PanelElement = (props) => ( - -

test

-
- ); - const { container } = render( - - - - second - - , - ); - - expect(container.querySelectorAll('.rc-collapse-content-active')).toHaveLength(1); - expect(container.querySelector('.rc-collapse-content')).toHaveClass( - 'rc-collapse-content-active', - ); - expect(container.querySelector('.rc-collapse-header')?.textContent).toBe('collapse 1'); - expect(container.querySelector('.rc-collapse-header')?.querySelectorAll('.arrow')).toHaveLength( - 1, - ); - fireEvent.click(container.querySelector('.rc-collapse-header')!); - expect(container.querySelectorAll('.rc-collapse-content-active')).toHaveLength(0); - expect(container.querySelector('.rc-collapse-content')).toHaveClass( - 'rc-collapse-content-inactive', - ); - }); + // TODO: 需要在 Ant Design 里面测试,待 Ant Design 稳定后再移除 assignee: @wxh16144 + // it('should support custom component', () => { + // const PanelElement = (props) => ( + // + //

test

+ //
+ // ); + // const { container } = render( + // + // + // + // second + // + // , + // ); + + // expect(container.querySelectorAll('.rc-collapse-content-active')).toHaveLength(1); + // expect(container.querySelector('.rc-collapse-content')).toHaveClass( + // 'rc-collapse-content-active', + // ); + // expect(container.querySelector('.rc-collapse-header')?.textContent).toBe('collapse 1'); + // expect(container.querySelector('.rc-collapse-header')?.querySelectorAll('.arrow')).toHaveLength( + // 1, + // ); + // fireEvent.click(container.querySelector('.rc-collapse-header')!); + // expect(container.querySelectorAll('.rc-collapse-content-active')).toHaveLength(0); + // expect(container.querySelector('.rc-collapse-content')).toHaveClass( + // 'rc-collapse-content-inactive', + // ); + // }); describe('prop: collapsible', () => { it('default', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelector('.rc-collapse-header-text')).toBeTruthy(); fireEvent.click(container.querySelector('.rc-collapse-header')!); @@ -517,11 +579,16 @@ describe('collapse', () => { }); it('should work when value is header', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelector('.rc-collapse-header-text')).toBeTruthy(); fireEvent.click(container.querySelector('.rc-collapse-header')!); @@ -531,11 +598,16 @@ describe('collapse', () => { }); it('should work when value is icon', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelector('.rc-collapse-expand-icon')).toBeTruthy(); fireEvent.click(container.querySelector('.rc-collapse-header')!); @@ -546,11 +618,16 @@ describe('collapse', () => { it('should disabled when value is disabled', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelector('.rc-collapse-header-text')).toBeTruthy(); expect(container.querySelectorAll('.rc-collapse-item-disabled')).toHaveLength(1); @@ -560,11 +637,17 @@ describe('collapse', () => { it('the value of panel should be read first', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelector('.rc-collapse-header-text')).toBeTruthy(); @@ -576,11 +659,16 @@ describe('collapse', () => { it('icon trigger when collapsible equal header', () => { const { container } = render( - - - first - - , + , ); fireEvent.click(container.querySelector('.rc-collapse-header .arrow')!); @@ -589,11 +677,16 @@ describe('collapse', () => { it('header not trigger when collapsible equal icon', () => { const { container } = render( - - - first - - , + , ); fireEvent.click(container.querySelector('.rc-collapse-header-text')!); @@ -603,11 +696,16 @@ describe('collapse', () => { it('!showArrow', () => { const { container } = render( - - - first - - , + , ); expect(container.querySelectorAll('.rc-collapse-expand-icon')).toHaveLength(0); @@ -616,46 +714,38 @@ describe('collapse', () => { it('Panel container dom can set event handler', () => { const clickHandler = jest.fn(); const { container } = render( - - -
Click this
-
-
, + Click this, + }, + ]} + />, ); fireEvent.click(container.querySelector('.target')!); expect(clickHandler).toHaveBeenCalled(); }); - it('falsy Panel', () => { - const { container } = render( - - {null} - -

Panel 1 content

-
- {0} - -

Panel 2 content

-
- {undefined} - {false} - {true} -
, - ); - - expect(container.querySelectorAll('.rc-collapse-item')).toHaveLength(2); - }); - it('ref should work', () => { const ref = React.createRef(); const panelRef = React.createRef(); const { container } = render( - - - first - - , + , ); expect(ref.current).toBe(container.firstChild); expect(panelRef.current).toBe(container.querySelector('.rc-collapse-item')); @@ -665,11 +755,16 @@ describe('collapse', () => { it('onItemClick should work', () => { const onItemClick = jest.fn(); const { container } = render( - - - first - - , + , ); fireEvent.click(container.querySelector('.rc-collapse-header')!); expect(onItemClick).toHaveBeenCalled(); @@ -678,11 +773,17 @@ describe('collapse', () => { it('onItemClick should not work when collapsible is disabled', () => { const onItemClick = jest.fn(); const { container } = render( - - - first - - , + , ); fireEvent.click(container.querySelector('.rc-collapse-header')!); expect(onItemClick).not.toHaveBeenCalled(); @@ -690,65 +791,24 @@ describe('collapse', () => { it('panel style should work', () => { const { container } = render( - - - first - - , - ); - expect(container.querySelector('.rc-collapse-item').style.color).toBe('red'); - }); - - describe('props items', () => { - const items: ItemType[] = [ - { - key: '1', - label: 'collapse 1', - children: 'first', - collapsible: 'disabled', - }, - { - key: '2', - label: 'collapse 2', - children: 'second', - extra: ExtraSpan, - }, - { - key: '3', - label: 'collapse 3', - className: 'important', - children: 'third', - }, - ]; - - runNormalTest( - test{'>'}} items={items} />, - ); - - runAccordionTest( , ); + expect(window.getComputedStyle(container.querySelector('.rc-collapse-item'))).toHaveProperty( + 'color', + 'red', + ); + }); + describe('props items', () => { it('should work with onItemClick', () => { const onItemClick = jest.fn(); const { container } = render(