Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why won't my React accordion animation work?

I've implemented my own responsive accordion in React, and I can't get it to animate the opening of a fold.

This is especially odd because I can get the icon before the title to animate up and down, and, other than the icon being a pseudo-element, I can't seem to see the difference between the two.

JS:

class Accordion extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      active: -1
    };
  }
  /***
   * Selects the given fold, and deselects if it has been clicked again by setting "active to -1" 
   * */
  selectFold = foldNum => {
    const current = this.state.active === foldNum ? -1 : foldNum;
    this.setState(() => ({ active: current }));
  };

  render() {
    return (
      <div className="accordion">
        {this.props.contents.map((content, i) => {
          return (
            <Fold
              key={`${i}-${content.title}`}
              content={content}
              handle={() => this.selectFold(i)}
              active={i === this.state.active}
            />
          );
        })}
      </div>
    );
  }
}

class Fold extends React.Component {
  render() {
    return (
      <div className="fold">
        <button
          className={`fold_trigger ${this.props.active ? "open" : ""}`}
          onClick={this.props.handle}
        >
          {this.props.content.title}
        </button>
          <div
            key="content"
            className={`fold_content ${this.props.active ? "open" : ""}`}
          >
            {this.props.active ? this.props.content.inner : null}
          </div>
      </div>
    );
  }
}

CSS:

$line-color: rgba(34, 36, 38, 0.35);

.accordion {
  width: 100%;
  padding: 1rem 2rem;
  display: flex;
  flex-direction: column;
  border-radius: 10%;
  overflow-y: auto;
}

.fold {
  .fold_trigger {
    &:before {
      font-family: FontAwesome;
      content: "\f107";
      display: block;
      float: left;
      padding-right: 1rem;
      transition: transform 400ms;
      transform-origin: 20%;
      color: $line-color;
    }

    text-align: start;
    width: 100%;
    padding: 1rem;
    border: none;
    outline: none;
    background: none;
    cursor: pointer;
    border-bottom: 1px solid $line-color;

    &.open {
      &:before {
        transform: rotateZ(-180deg);
      }
    }
  }

  .fold_content {
    display: none;
    max-height: 0;
    opacity: 0;
    transition: max-height 400ms linear;

    &.open {
      display: block;
      max-height: 400px;
      opacity: 1;
    }
  }
  border-bottom: 1px solid $line-color;
}

Here's the CodePen: https://codepen.io/renzyq19/pen/bovZKj

like image 264
renzyq19 Avatar asked Oct 09 '17 15:10

renzyq19


People also ask

How do you use React accordion?

Step 1: Create a React application using the following command. Step 2: After creating your project folder i.e. foldername, move to it using the following command. Step 3: After creating the ReactJS application, Install the material-ui modules using the following command. App.

How do you keep accordion open by default in React?

To keep single pane open always in React Accordion component By default, all Accordion panels are collapsible. You can customize the Accordion to keep one panel as expanded state always. This is applicable for Single expand mode.


1 Answers

I wouldn't conditionally render the content if you want a smooth transition. It will make animating a slide-up especially tricky.


I would change this:

{this.props.active ? this.props.content.inner : null}

to this:

{this.props.content.inner}

and use this scss:

.fold_content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 400ms ease;

  &.open {
    max-height: 400px;
  }
}

Try the snippet below or see the forked CodePen Demo.

class Accordion extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      active: -1
    };
  }
  /***
   * Selects the given fold, and deselects if it has been clicked again by setting "active to -1" 
   * */
  selectFold = foldNum => {
    const current = this.state.active === foldNum ? -1 : foldNum;
    this.setState(() => ({ active: current }));
  };

  render() {
    return (
      <div className="accordion">
        {this.props.contents.map((content, i) => {
          return (
            <Fold
              key={`${i}-${content.title}`}
              content={content}
              handle={() => this.selectFold(i)}
              active={i === this.state.active}
            />
          );
        })}
      </div>
    );
  }
}

class Fold extends React.Component {
  render() {
    return (
      <div className="fold">
        <button
          className={`fold_trigger ${this.props.active ? "open" : ""}`}
          onClick={this.props.handle}
        >
          {this.props.content.title}
        </button>
          <div
            key="content"
            className={`fold_content ${this.props.active ? "open" : ""}`}
          >
            {this.props.content.inner}
          </div>
      </div>
    );
  }
}

const pictures = [
  "http://unsplash.it/200",
  "http://unsplash.it/200",
  "http://unsplash.it/200",
];
var test = (title, text, imageURLs) => {
  const images=
    <div className='test-images' >
      {imageURLs.map((url,i) => <img key={i} src={url} />)}
    </div>;

  const inner =
    <div className='test-content' >
      <p>{text} </p>
      {images} 
    </div>;
  
  return {title, inner};
};

const testData = [
  test('Title', 'Content',pictures ),
  test('Title', 'Content',pictures ),
  test('Title', 'Content',pictures ),
  test('Title', 'Content',pictures ),
  test('Title', 'Content',pictures ),
];

ReactDOM.render(<Accordion contents={testData} />, document.getElementById('root'));
.accordion {
  width: 100%;
  padding: 1rem 2rem;
  display: flex;
  flex-direction: column;
  border-radius: 10%;
  overflow-y: auto;
}

.fold {
  border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}

.fold .fold_trigger {
  text-align: start;
  width: 100%;
  padding: 1rem;
  border: none;
  outline: none;
  background: none;
  cursor: pointer;
  border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}

.fold .fold_trigger:before {
  font-family: FontAwesome;
  content: "\f107";
  display: block;
  float: left;
  padding-right: 1rem;
  transition: transform 400ms;
  transform-origin: 20%;
  color: rgba(34, 36, 38, 0.35);
}

.fold .fold_trigger.open:before {
  transform: rotateZ(-180deg);
}

.fold .fold_content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 400ms ease;
}

.fold .fold_content.open {
  max-height: 400px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" />

<div id='root'></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Note:

I used ease instead of linear on the transition because I think it's a nicer effect. But that's just personal taste. linear will work as well.

Also, you can continue to conditionally render the content. A slide-down animation is possible, but a slide-up can't be easily achieved. There are some transition libraries that you could explore as well.

However, I think it's easiest to use the state just for conditional classes (as you are doing with the open class). I think conditionally rendering content to the DOM makes your life difficult if you're trying to do CSS animations.

like image 70
Dan Kreiger Avatar answered Sep 30 '22 06:09

Dan Kreiger