Skip to content

Instantly share code, notes, and snippets.

@latviancoder
Created October 9, 2018 17:45
Show Gist options
  • Save latviancoder/a5054fe3d97594fa4c215c491d3e1b81 to your computer and use it in GitHub Desktop.
Save latviancoder/a5054fe3d97594fa4c215c491d3e1b81 to your computer and use it in GitHub Desktop.
Accordion
import React, { Component } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import classNames from 'classnames';
import { Icon } from 'office-ui-fabric-react';
import ContentBlock from '../contentBlock/ContentBlock.component';
import s from './accordion.scss';
type AccordionRenderParams = {
currentOpenedPanelIndex?: number;
onAccordionPanelClicked?: (ref: React.RefObject<HTMLDivElement>, panelIndex: number) => void;
};
interface AccordionProps {
children: (params: AccordionRenderParams) => JSX.Element | Array<JSX.Element>;
defaultOpenedPanelIndex?: number;
}
interface AccordionState {
currentOpenedPanelIndex?: number;
}
class Accordion extends Component<AccordionProps, AccordionState> {
state: AccordionState = {
// We can initialize accordion with one panel already opened
currentOpenedPanelIndex: this.props.defaultOpenedPanelIndex || undefined
};
onAccordionPanelClicked = (ref: React.RefObject<HTMLDivElement>, panelIndex: number) => {
this.setState(
{
currentOpenedPanelIndex: (panelIndex === this.state.currentOpenedPanelIndex)
? undefined
: panelIndex
},
() => {
// Scroll to opened accordion panel if it is not visible on screen and try to position it on the top of the screen
if (ref.current) {
scrollIntoView(ref.current, { block: 'nearest', scrollMode: 'if-needed' });
}
}
);
}
render() {
return <div>
{this.props.children({
currentOpenedPanelIndex: this.state.currentOpenedPanelIndex,
onAccordionPanelClicked: this.onAccordionPanelClicked
})}
</div>;
}
}
interface AccordionPanelProps {
title: string;
panelIndex: number;
isInvalid?: boolean;
onAccordionPanelClicked?: (ref: React.RefObject<HTMLDivElement>, panelIndex: number) => void;
currentOpenedPanelIndex?: number;
}
export class AccordionPanel extends Component<AccordionPanelProps> {
containerRef: React.RefObject<HTMLDivElement>;
constructor(props: AccordionPanelProps) {
super(props);
// Create reference to underlying DOM element
this.containerRef = React.createRef();
}
render() {
const isOpen = this.props.currentOpenedPanelIndex === this.props.panelIndex;
return <div ref={this.containerRef}>
<ContentBlock
className={s.panel}
padding={false}
innerClassName={(!isOpen && this.props.isInvalid) ? s.invalidBlock : undefined}
>
<div
className={classNames(
s.title,
isOpen && s.titleIsOpened
)}
// Pass reference to DOM element to onClick callback
onClick={() => this.props.onAccordionPanelClicked
&& this.props.onAccordionPanelClicked(this.containerRef, this.props.panelIndex)}
>
{this.props.title}
<Icon iconName={isOpen ? 'ChevronUp' : 'ChevronDown'}/>
</div>
{isOpen && <div className={s.content}>
{this.props.children}
</div>}
</ContentBlock>
</div>;
}
}
export default Accordion;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment