React Accordion UI Component

I wanted to flex my react muscles by building a little component. It all began with this egghead tutorial.

Great way to get started and practice some ES6, however at the end of this brief guide I was left with only one list item, so I had some fun digging around to figure out how to render a full list of question and answers.

I also wanted to do a fun folding animation for the accordion, I drew from this really sweet widget! I'm not totally satisfied with the animation result, if anyone has thoughts on how to finesses the fold I'm all ears! :)

Made with

Html
Css/SCSS
Javascript

Html

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <pattern id="pattern"
           x="0" y="0" width="24" height="24"
           patternUnits="userSpaceOnUse" >
    <rect fill="rgba(159, 188, 191, 0.15)" x="0" width="20" height="20" y="0"/>
    <rect fill="rgba(159, 188, 191, 0.15)" x="20" width="20" height="20" y="20"/>
  </pattern>
  <rect fill="url(#pattern)" x="0" y="0" width="100%" height="100%"/>
</svg>
<div id="accordion"></div>

Css

@import url(https://fonts.googleapis.com/css?family=Josefin+Slab:400,700);
@import url(https://fonts.googleapis.com/css?family=Maven+Pro);

$bodyBg: #3a3042;
$underLine: #ff784f;

body {
  background: $bodyBg;
  font-family: 'Maven Pro', sans-serif;
  margin: 0;
}

svg {
  position: absolute;
  width: 100%;
  height: 100%;
  z-index: -1;
  top: 0;
}

h1 {
  color: #fff;
  text-align: center;
  text-transform: uppercase;
  font-family: 'Josefin Slab', serif;
  font-size: 66px;
  margin-top: 10vh;
  letter-spacing: 10px;
}

.accordion-container {
  width: 60%;
  min-width: 500px;
  margin: 5vh auto;
  border-radius: 5px;
  background-color: #fff;
  box-shadow:-2px 1px 2px 2px #212121;
  
  div {
    border-bottom: 3px solid;
    border-color: $underLine;
    transition: border-color 0.5s ease-in;
  }
}

.answer, .summary {
  display: inline-block;
}

.answer {
  background: $bodyBg;
  color: #fff;
}

.summary {
  font-family: 'Josefin Slab', serif;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 25px;
  padding: 15px;
  &:before {
    content: '>\0000a0';
  } 
}

.folding-pannel {
  display: block;
  transition: all 0.2s ease-in;
  line-height: 40px;
  border-top: 2px solid $bodyBg;
}

.active .summary {
  border-color: transparent;
  transition: border-color 0.2s ease-out;
}

.inactive .folding-pannel {
  transform-origin: 50% 0%;
  transform: perspective(250px) rotateX(-90deg);
  transition: all 0.4s ease-in-out;
  height: 0;

}
.active .folding-pannel {
  transform: perspective(350px) rotateX(0deg);
  transition: all 0.4s ease-in-out;
  height: 50px;
  line-height: 50px;
  text-indent: 40px;
}

Javascript

class AccordionItem extends React.Component {
  constructor() {
    super();
    this.state = {
      active: false
    };
    this.toggle = this.toggle.bind(this);
  }
  toggle() {
    this.setState({
      active: !this.state.active,
      className: "active"
    });
  }
  render() {
    const activeClass = this.state.active ? "active" : "inactive";
    const question = this.props.details;
    return (
            <div className={activeClass} onClick={this.toggle}>
              <span className="summary">{question.summary}</span>
              <span className="folding-pannel answer">{question.answer}</span>
            </div>
    );
  }
}

class Accordion extends React.Component {
  constructor() {
    super();
    this.state = {
      questions: sampleQuestions,
    };
    this.renderQuestion = this.renderQuestion.bind(this);
  }
  renderQuestion(key) {
    return <AccordionItem key={key} index={key} details={this.state.questions[key]} />
  }
  render() {
    return(
      <div>
        <h1>What is...</h1>
        <div className="accordion-container">{Object.keys(this.state.questions).map(this.renderQuestion)} </div>
      </div>    
    )
  }
}
const sampleQuestions = {
  question1: {summary:'the capital of Canada?', answer:'Ottawa baby!!'},
  question2: {summary:'the life span of a bowhead whale?', answer:'Over 200 years!!'},
  question3: {summary:'the most visited city in the world?', answer:'London, groovy baby!!'},
  question4: {summary:'the warmest ocean?', answer:'Indian Ocean, it\'s a hottie!'},
  question5: {summary:'the one thing ron swanson hates more than lying?', answer:'Skim milk, which is water that\'s lying about being milk'}
};
ReactDOM.render(
  <Accordion />,
  document.getElementById('accordion')
);

Author

Tobi Weinstock

Demo

See the Pen React Accordion UI Component by Tobi Weinstock (@tvweinstock) on CodePen.