Я использовал предложение @Cameron Downer, даже если я не был настолько искусен, чтобы клонировать детей, и, к моему удивлению, он работал отлично.Это мое решение:
import React from 'react';
import PropTypes from 'prop-types';
// import ExpandPanel from '../ExpandPanel';
let count = 0;
class ExpandGroup extends React.Component {
constructor(props) {
super(props);
this.id = `${this.props.id}_${count++}`;
this.state = {
openChildId: '',
};
this.styles = styles.bind(this);
this.updateOpenChildId = updateOpenChildId.bind(this);
this.panelPropsFix = panelPropsFix.bind(this);
this.checkStartOpen = checkStartOpen.bind(this);
};
render() {
return (
<div id={this.id} style={this.styles('root')}>
{this.panelPropsFix()}
</div>
);
};
};
function panelPropsFix() {
return React.Children.map(this.props.children,
(child, key) => {
let childClass = child.type.toString().split(' ')[1];
if (childClass === 'ExpandPanel') {
let childId = child.props.id;
return React.cloneElement(child, {
key: key,
startOpen: this.checkStartOpen(childId),
style: this.styles('child'),
openId: (this.props.singleOpen) ? this.state.openChildId : childId,
singleOpenCB: id => this.updateOpenChildId(id),
})
}
}
);
};
function checkStartOpen(childId) {
if (typeof this.props.openPanels === 'boolean') return this.props.openPanels;
else if (this.props.openPanels instanceof Array) {
return this.props.openPanels.includes(childId);
}
return false;
};
function updateOpenChildId(id) {
if (id === this.state.openChildId) {
this.setState({openChildId: ''});
} else {
this.setState({openChildId: id});
}
};
function styles(option) {
const styleObject = {
root: {
margin: this.props.margin,
padding: this.props.padding,
...this.props.style
},
child: {
...this.props.panelsStyle,
}
};
return styleObject[option]
};
ExpandGroup.defaultProps = {
id: 'ExpandGroup',
children: undefined,
singleOpen: false,
openPanels: false,
panelsStyle: {},
margin: 0,
padding: 0,
style: {},
};
ExpandGroup.propTypes = {
id: PropTypes.string,
// children: PropTypes.arrayOf(PropTypes.instanceOf(ExpandPanel)),
singleOpen: PropTypes.bool,
openPanels: PropTypes.oneOfType([ PropTypes.bool, PropTypes.array ]),
panelsStyle: PropTypes.object,
margin: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
padding: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
style: PropTypes.object,
};
export default ExpandGroup;
и
import React from 'react';
import PropTypes from 'prop-types';
import Label from 'Base/Components/Label';
import Column from 'Base/Layouts/Column';
import Row from 'Base/Layouts/Row';
class ExpandPanel extends React.Component {
constructor(props) {
super(props);
this.id = this.props.id;
this.colors = {
white: '#ffffff',
border: '#333333',
};
this.dim = {
minWidth: 40
}
this.styles = styles.bind(this);
this.getMaxHeight = getMaxHeight.bind(this);
this.handleClose = handleClose.bind(this);
this.handleOpenPanel = handleOpenPanel.bind(this);
this.handleToggle = handleToggle.bind(this);
this.state = {
isDrawerOpen: this.props.startOpen,
height: 'fit-content',
}
};
componentDidMount() {
setTimeout(() => {
this.setState({ height: this.getMaxHeight() });
});
};
componentWillReceiveProps(nextProps) {
if (nextProps && nextProps.openId !== this.id)
this.handleClose();
};
renderAnchor() {
return (
<Row id={'anchor'} height={this.props.anchorHeight} boxShadow={this.props.boxShadow} onClick={ this.handleToggle } style={ this.styles('anchor') } >
{this.props.anchor}
</Row>
);
};
renderPanel() {
let height = this.props.autoHeight ? 'auto' : this.props.panelHeight;
return (
<Column id={'panel'} height={height} boxShadow={this.props.boxShadow} style={ this.styles('panel') } >
{this.props.panel}
</Column>
)
};
render() {
return (
<Column id={this.id} width={'100%'} height={'auto'} boxShadow={this.props.boxShadow} style={this.styles('root')}>
{ this.renderAnchor() }
{ this.renderPanel() }
</Column>
);
};
};
function handleToggle(e) {
if (this.state.isDrawerOpen) this.handleClose();
else this.handleOpenPanel();
this.props.singleOpenCB(this.id);
this.props.anchorClickCB({id: this.id, isDrawerOpen: !this.state.isDrawerOpen });
};
function handleClose() {
this.setState({isDrawerOpen: false}, this.props.onClose);
};
function handleOpenPanel() {
this.setState({isDrawerOpen: true}, this.props.onOpen);
};
function getMaxHeight() {
let panel = document.getElementById(this.panelId);
if (this.props.autoHeight && panel) {
return (panel.clientHeight + this.props.anchorHeight);
}
return (this.props.panelHeight + this.props.anchorHeight);
};
function styles(option) {
let rootMargin = this.props.showMargin && this.state.isDrawerOpen ? 10 : 0;
let panelHeightToogle = this.state.isDrawerOpen ? this.state.height : this.props.anchorHeight;
let anchorHeight = this.props.anchorHeight < this.dim.minWidth ? this.dim.minWidth : this.props.anchorHeight;
const styleObject = {
root: {
maxHeight: panelHeightToogle,
minHeight: anchorHeight,
transition: `max-height ${this.props.transition}s linear`,
overflow: 'hidden',
...this.props.style
},
anchor: {
cursor: 'pointer',
minHeight: anchorHeight,
...this.props.anchorStyle
},
panel: {
width: "100%",
overflow: 'auto',
...this.props.panelStyle
},
margin:{
marginBottom: rootMargin,
marginTop: rootMargin,
transition: `margin 0.15s linear`,
}
};
return styleObject[option]
};
ExpandPanel.defaultProps = {
id: 0,
anchor: <Row><Label text={'Default Anchor'}/></Row>,
anchorClickCB: ()=>{},
singleOpenCB: ()=>{}, // for ExpandGroup internal use
openId: '', // for ExpandGroup internal use
anchorHeight: 35,
anchorStyle: {},
panel: <Column><Label text={'Default Panel'}/></Column>,
onOpen: ()=>{},
onClose: ()=>{},
autoHeight: false,
panelHeight: 200,
panelStyle: {},
startOpen: false,
boxShadow: true,
transition: 0.45,
style: {},
};
ExpandPanel.propTypes = {
id: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
anchor: PropTypes.object,
anchorClickCB: PropTypes.func,
singleOpenCB: PropTypes.func, // for ExpandGroup internal use
openId: PropTypes.string, // for ExpandGroup internal use
anchorHeight: PropTypes.number,
anchorStyle: PropTypes.object,
panel: PropTypes.object,
onOpen: PropTypes.func,
onClose: PropTypes.func,
autoHeight: PropTypes.bool,
panelHeight: PropTypes.number,
panelStyle: PropTypes.object,
startOpen: PropTypes.bool,
boxShadow: PropTypes.bool,
transition: PropTypes.number,
style: PropTypes.object,
};
export default ExpandPanel;
, поэтому вызов может быть таким:
<ExpandGroup singleOpen={true}>
<ExpandPanel id={'bloc-01'}/>
<ExpandPanel id={'bloc-02'}/>
<ExpandPanel id={'bloc-03'}/>
<ExpandPanel id={'bloc-04'}/>
<ExpandPanel id={'bloc-05'}/>
</ExpandGroup>