From f7b9039537cff8556df0096a6ee18f34f061d0d8 Mon Sep 17 00:00:00 2001 From: RThong <403216075@qq.com> Date: Mon, 9 May 2022 19:55:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0headerRender?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/custom-collapse-header.tsx | 158 ++++++++++++++++++++++++++++ src/Collapse.tsx | 22 +++- src/Panel.tsx | 21 ++-- src/interface.ts | 2 + tests/index.spec.tsx | 46 ++++++++ 5 files changed, 238 insertions(+), 11 deletions(-) create mode 100644 examples/custom-collapse-header.tsx diff --git a/examples/custom-collapse-header.tsx b/examples/custom-collapse-header.tsx new file mode 100644 index 0000000..af12edd --- /dev/null +++ b/examples/custom-collapse-header.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import Collapse, { Panel } from '../src'; +import motion from './_util/motionUtil'; +import '../assets/index.less'; + +const text = ` + A dog is a type of domesticated animal. + Known for its loyalty and faithfulness, + it can be found as a welcome guest in many households across the world. +`; + +function random() { + return parseInt((Math.random() * 10).toString(), 10) + 1; +} + +function customHeaderRender({ header, isActive, extra }) { + return ( +
+
+ {header} + {isActive ? '收起' : '展开'} +
+
{extra}
+
+ ); +} + +class Test extends React.Component { + state = { + time: random(), + accordion: false, + activeKey: ['4'], + collapsible: undefined, + }; + + onChange = (activeKey: string) => { + this.setState({ + activeKey, + }); + }; + + getItems() { + const items = []; + // eslint-disable-next-line no-plusplus + for (let i = 0, len = 3; i < len; i++) { + const key = i + 1; + items.push( + +

{text.repeat(this.state.time)}

+
, + ); + } + items.push( + + + +

{text}

+
+
+
, + ); + + items.push( + + + +
+ + +
+
+
+
, + ); + + items.push( + Extra Node}> +

Panel with extra

+
, + ); + + return items; + } + + setActivityKey = () => { + this.setState({ + activeKey: ['2'], + }); + }; + + reRender = () => { + this.setState({ + time: random(), + }); + }; + + toggle = () => { + const { accordion } = this.state; + this.setState({ + accordion: !accordion, + }); + }; + + handleCollapsibleChange = (e: any) => { + const values = [undefined, 'header', 'disabled']; + this.setState({ + collapsible: values[e.target.value], + }); + }; + + render() { + const { accordion, activeKey, collapsible } = this.state; + const btn = accordion ? 'Mode: accordion' : 'Mode: collapse'; + return ( +
+ + +

+ collapsible: + +

+
+
+ +
+
+ + {this.getItems()} + +
+ ); + } +} + +export default Test; diff --git a/src/Collapse.tsx b/src/Collapse.tsx index 3c5f647..7fc8194 100644 --- a/src/Collapse.tsx +++ b/src/Collapse.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import shallowEqual from 'shallowequal'; import toArray from 'rc-util/lib/Children/toArray'; import CollapsePanel from './Panel'; -import { CollapseProps, CollapsibleType } from './interface'; +import type { CollapseProps, CollapsibleType } from './interface'; function getActiveKeysArray(activeKey: React.Key | React.Key[]) { let currentActiveKey = activeKey; @@ -78,10 +78,23 @@ class Collapse extends React.Component { if (!child) return null; const { activeKey } = this.state; - const { prefixCls, openMotion, accordion, destroyInactivePanel: rootDestroyInactivePanel, expandIcon, collapsible } = this.props; + const { + prefixCls, + openMotion, + accordion, + destroyInactivePanel: rootDestroyInactivePanel, + expandIcon, + headerRender, + collapsible, + } = this.props; // If there is no key provide, use the panel order as default key const key = child.key || String(index); - const { header, headerClass, destroyInactivePanel, collapsible: childCollapsible } = child.props; + const { + header, + headerClass, + destroyInactivePanel, + collapsible: childCollapsible, + } = child.props; let isActive = false; if (accordion) { isActive = activeKey[0] === key; @@ -89,7 +102,7 @@ class Collapse extends React.Component { isActive = activeKey.indexOf(key) > -1; } - const mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible; + const mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible; const props = { key, @@ -104,6 +117,7 @@ class Collapse extends React.Component { children: child.props.children, onItemClick: mergeCollapsible === 'disabled' ? null : this.onClickItem, expandIcon, + headerRender, collapsible: mergeCollapsible, }; diff --git a/src/Panel.tsx b/src/Panel.tsx index 4b3391a..f07dc83 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -49,6 +49,7 @@ class CollapsePanel extends React.Component { forceRender, openMotion, expandIcon, + headerRender, extra, collapsible, } = this.props; @@ -98,15 +99,21 @@ class CollapsePanel extends React.Component { return (
- {showArrow && icon} - {collapsibleHeader ? ( - - {header} - + {headerRender && typeof headerRender === 'function' ? ( + headerRender(this.props) ) : ( - header + <> + {showArrow && icon} + {collapsibleHeader ? ( + + {header} + + ) : ( + header + )} + {ifExtraExist &&
{extra}
} + )} - {ifExtraExist &&
{extra}
}
React.ReactNode; collapsible?: CollapsibleType; children?: React.ReactNode; + headerRender?: (props: object) => React.ReactNode; } export interface CollapsePanelProps { @@ -34,6 +35,7 @@ export interface CollapsePanelProps { extra?: string | React.ReactNode; onItemClick?: (panelKey: string | number) => void; expandIcon?: (props: object) => React.ReactNode; + headerRender?: (props: object) => React.ReactNode; panelKey?: string | number; role?: string; collapsible?: CollapsibleType; diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 5637f13..6f8a2ed 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -559,4 +559,50 @@ describe('collapse', () => { expect(collapse.find('.rc-collapse-item-active').length).toBe(1); }); }); + + describe('customHeader', () => { + const customHeaderRender = ({ header, isActive, extra }) => { + return ( +
+
+ {header} + {isActive ? '收起' : '展开'} +
+
{extra}
+
+ ); + }; + + let collapse: ReactWrapper; + beforeEach(() => { + collapse = mount( + + + first + + ExtraSpan}> + second + + + third + + , + ); + }); + + it('should toggle show on panel', () => { + let header = collapse.find('.rc-collapse-header').at(1); + header.simulate('click'); + jest.runAllTimers(); + collapse.update(); + expect(collapse.find('.rc-collapse-content-active').length).toBe(1); + expect(collapse.find('.rc-collapse-item-active').length).toBe(1); + header = collapse.find('.rc-collapse-header').at(1); + header.simulate('click'); + jest.runAllTimers(); + collapse.update(); + expect(collapse.find('.rc-collapse-content-active').exists()).toBeFalsy(); + expect(collapse.find('.rc-collapse-item-active').exists()).toBeFalsy(); + }); + }); }); From 004bbb71596e7f2f0c1a1938507d017630dbe65e Mon Sep 17 00:00:00 2001 From: RThong <403216075@qq.com> Date: Sat, 18 Jun 2022 17:25:59 +0800 Subject: [PATCH 2/2] chore: resolve conflicts --- package.json | 2 +- src/Panel.tsx | 74 +++++++++++++++++++++++++++----------------- tests/index.spec.tsx | 21 ++++++++++--- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 5b75a5d..bce384e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-collapse", - "version": "3.2.0", + "version": "3.3.0", "description": "rc-collapse ui component for react", "keywords": [ "react", diff --git a/src/Panel.tsx b/src/Panel.tsx index f07dc83..d761b9b 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -19,7 +19,7 @@ class CollapsePanel extends React.Component { return !shallowEqual(this.props, nextProps); } - handleItemClick = () => { + onItemClick = () => { const { onItemClick, panelKey } = this.props; if (typeof onItemClick === 'function') { @@ -29,26 +29,57 @@ class CollapsePanel extends React.Component { handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) { - this.handleItemClick(); + this.onItemClick(); } }; + renderIcon = () => { + const { showArrow, expandIcon, prefixCls, collapsible } = this.props; + if (!showArrow) { + return null; + } + + const iconNode = + typeof expandIcon === 'function' ? expandIcon(this.props) : ; + + return ( + iconNode && ( +
+ {iconNode} +
+ ) + ); + }; + + renderTitle = () => { + const { header, prefixCls, collapsible } = this.props; + + return ( + + {header} + + ); + }; + render() { const { className, id, style, prefixCls, - header, headerClass, children, isActive, - showArrow, destroyInactivePanel, accordion, forceRender, openMotion, - expandIcon, headerRender, extra, collapsible, @@ -57,10 +88,6 @@ class CollapsePanel extends React.Component { const disabled = collapsible === 'disabled'; const collapsibleHeader = collapsible === 'header'; - const headerCls = classNames(`${prefixCls}-header`, { - [headerClass]: headerClass, - [`${prefixCls}-header-collapsible-only`]: collapsibleHeader, - }); const itemCls = classNames( { [`${prefixCls}-item`]: true, @@ -70,26 +97,21 @@ class CollapsePanel extends React.Component { className, ); - let icon: any = ; + const headerCls = classNames(`${prefixCls}-header`, { + [headerClass]: headerClass, + [`${prefixCls}-header-collapsible-only`]: collapsibleHeader, + }); /** header 节点属性 */ const headerProps: React.HTMLAttributes = { className: headerCls, 'aria-expanded': isActive, + 'aria-disabled': disabled, onKeyPress: this.handleKeyPress, }; - if (showArrow && typeof expandIcon === 'function') { - icon = expandIcon(this.props); - } - if (collapsibleHeader) { - icon = ( - this.handleItemClick()}> - {icon} - - ); - } else { - headerProps.onClick = this.handleItemClick; + if (!collapsibleHeader) { + headerProps.onClick = this.onItemClick; headerProps.role = accordion ? 'tab' : 'button'; headerProps.tabIndex = disabled ? -1 : 0; } @@ -103,14 +125,8 @@ class CollapsePanel extends React.Component { headerRender(this.props) ) : ( <> - {showArrow && icon} - {collapsibleHeader ? ( - - {header} - - ) : ( - header - )} + {this.renderIcon()} + {this.renderTitle()} {ifExtraExist &&
{extra}
} )} diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 6f8a2ed..a9c084d 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -139,8 +139,7 @@ describe('collapse', () => { activeKey: ['2'], }; - constructor(props: any) { - super(props); + componentDidMount(): void { this.setState({ activeKey: ['2'], }); @@ -495,7 +494,7 @@ describe('collapse', () => { , ); - expect(collapse.find('.rc-collapse-header-text').exists()).toBeFalsy(); + expect(collapse.find('.rc-collapse-header-text').exists()).toBeTruthy(); collapse.find('.rc-collapse-header').simulate('click'); expect(collapse.find('.rc-collapse-item-active').length).toBe(1); }); @@ -522,7 +521,7 @@ describe('collapse', () => { , ); - expect(collapse.find('.rc-collapse-header-text').exists()).toBeFalsy(); + expect(collapse.find('.rc-collapse-header-text').exists()).toBeTruthy(); expect(collapse.find('.rc-collapse-item-disabled').length).toBe(1); @@ -538,7 +537,7 @@ describe('collapse', () => { , ); - expect(collapse.find('.rc-collapse-header-text').exists()).toBeFalsy(); + expect(collapse.find('.rc-collapse-header-text').exists()).toBeTruthy(); expect(collapse.find('.rc-collapse-item-disabled').length).toBe(1); @@ -560,6 +559,18 @@ describe('collapse', () => { }); }); + it('!showArrow', () => { + const wrapper = mount( + + + first + + , + ); + + expect(wrapper.exists('.rc-collapse-expand-icon')).toBeFalsy(); + }); + describe('customHeader', () => { const customHeaderRender = ({ header, isActive, extra }) => { return (