I want to make a kind of accordion expanding panel: but I don't want to have a component for accordion and another for a single expanding panel. my objective is to make something like this:
note: I have it working in another environment that uses named event listeners from a specific framework but I want to make it without frameworks, only react ES6
<ExpandingGroup id={'groupOne'} uniqOpen={true}>
<ExpandingPanel id={'panelOne'} />
<ExpandingPanel id={'panelTwo'} />
<ExpandingPanel id={'panelThree'}/>
<ExpandingPanel id={'panelFour'} />
</ExpandingGroup>
So when I click to expand the panel one, it expands it self and when I click on panel two (and uniqOpen flag is on) the panel two will open and panel one will be closed; My problem to play with props and callbacks is that I'll have to pollute the code and have two different components the do the same thing. Why not just nest a group of ExpandingPanels inside a ExpandingGroup scope.
this is my code for the ExpandingPanel: (ignore Row, Column and Label layouts)
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.groupId = undefined;
this.anchorId = `anchor_${this.id}`;
this.panelId = `panel_${this.id}`;
this.colors = {
white: '#ffffff',
border: '#333333',
};
this.dim = {
minWidth: 40
}
this.styles = styles.bind(this);
this.getMaxHeight = getMaxHeight.bind(this);
this.handleUniq = handleUniq.bind(this);
this.handleClose = handleClose.bind(this);
this.handleOpenPanel = handleOpenPanel.bind(this);
this.handleToggle = handleToggle.bind(this);
this.checkExpanded = checkExpanded.bind(this);
this.setMyGroup = setMyGroup.bind(this);
this.state = {
isDrawerOpen: this.props.startOpen,
height: 'fit-content',
}
};
componentDidMount() {
setTimeout(() => {
this.setState({ height: this.getMaxHeight() });
});
let myInstance = document.getElementById(this.anchorId);
this.setMyGroup(myInstance.parentNode.parentNode.parentNode.id);
window.addEventListener('click', this.checkExpanded);
};
componentWillUnmount() {
window.removeEventListener('click', this.checkExpanded);
};
renderAnchor() {
return (
<Row id={this.anchorId} height={this.props.anchorHeight} onClick={ this.handleToggle } style={ this.styles('anchor') } >
{this.props.anchor}
</Row>
);
};
renderPanel() {
let height = this.props.autoHeight ? 'auto' : this.props.panelHeight;
return (
<Column id={this.panelId} height={height} style={ this.styles('panel') } >
{this.props.panel}
</Column>
)
};
render() {
return (
<div style={this.styles('margin')}>
<Column id={'ExpandPanel'} width={'100%'} height={'auto'} boxShadow={true} style={this.styles('root')}>
{ this.renderAnchor() }
{ this.renderPanel() }
</Column>
</div>
);
};
};
function setMyGroup(id) {
this.groupId = id;
}
function checkExpanded(click) {
let groupNode = click.path.filter( c => c && c.id && c.id.includes('ExpandGroup_') )[0];
let groupName = '';
if (groupNode && groupNode.id) groupName = groupNode.id;
else {
console.log('Not ExpandGroup click');
return null;
}
let isUniq = groupName.split('_');
isUniq = !!(isUniq[isUniq.length -1]);
if (isUniq) {
let isMyGroup = groupName === this.groupId;
if (!isMyGroup) return null;
let anchorNode = click.path.filter( c => c && c.id && c.id.includes('ExpandPanel') )[0];
let anchorName = anchorNode.firstChild.id;
if (isMyGroup && anchorName !== this.anchorId) this.handleClose();
} else return null;
};
function handleToggle(e) {
if (this.state.isDrawerOpen) this.handleClose();
else this.handleOpenPanel();
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});
};
function handleUniq({paramId, paramGroup}) {
let sameGroup = this.props.uniqGroup === paramGroup;
let otherId = this.id !== paramId;
if ( sameGroup && otherId ) this.handleClose();
};
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,
uniqOpen: false,
uniqGroup: 'ExpandPanel',
anchor: <Row><Label text={'Default Anchor'}/></Row>,
anchorClickCB: ()=>{},
onClose: ()=>{},
anchorHeight: 50,
anchorStyle: {},
panel: <Column><Label text={'Default Panel'}/></Column>,
panelHeight: 200,
autoHeight: false,
panelStyle: {},
showMargin: false,
startOpen: false,
transition: 0.45,
style: {},
};
ExpandPanel.propTypes = {
id: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
uniqOpen: PropTypes.bool,
uniqGroup: PropTypes.string,
anchor: PropTypes.object,
anchorHeight: PropTypes.number,
anchorClickCB: PropTypes.func,
anchorStyle: PropTypes.object,
panel: PropTypes.object,
panelHeight: PropTypes.number,
autoHeight: PropTypes.bool,
panelStyle: PropTypes.object,
startOpen: PropTypes.bool,
transition: PropTypes.number,
style: PropTypes.object,
};
export default ExpandPanel;
and this is my code for the ExpandGroup:
import React from 'react';
import PropTypes from 'prop-types';
class ExpandGroup extends React.Component {
constructor(props) {
super(props);
this.id = `ExpandGroup_${this.props.id}_${this.props.uniqOpen}`;
};
render() {
return (
<div id={this.id}>
{this.props.children}
</div>
);
};
};
ExpandGroup.defaultProps = {
id: 'default',
uniqOpen: false,
};
ExpandGroup.propTypes = {
id: PropTypes.string,
uniqOpen: PropTypes.bool,
};
export default ExpandGroup;
and this is the call:
render() {
return (
<ExpandGroup id={'group-01'} uniqOpen={true}>
<ExpandPanel id={'bloc-01'}/>,
<ExpandPanel id={'bloc-02'}/>,
<ExpandPanel id={'bloc-03'}/>,
</ExpandGroup>
);
};