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(
+
+
+
+
+ ,
+ );
+
+ 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 (