0

Can someone help me on how I can animate the height of the card container, when toggling between different titles, since each description's size varies -- hence the height varies too?

`import React, { useState, useRef, useEffect } from 'react';
import './Experience.css';

const experiences = [
  {
    title: 'Software Developer Intern',
    date: 'Aug 2020 - Sept 2020',
    points: [
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
    ],
  },
  {
    title: 'Front-End Engineer',
    date: 'Oct 2020 - Dec 2020',
    points: [
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
    ],
  },
  {
    title: 'Full-Stack Developer',
    date: 'Jan 2021 - Present',
    points: [
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
      'word filler word filler word filler word filler word filler word filler word filler',
    ],
  },
];

const Experience = () => {
  const [selectedExperience, setSelectedExperience] = useState(experiences[0]);
  const [clickedIndex, setClickedIndex] = useState(null);
  const [fadeIn, setFadeIn] = useState(true);

  const handleTitleClick = (experience, index) => {
    setFadeIn(false);
    setClickedIndex(index);
    setTimeout(() => {
      setSelectedExperience(experience);
      setFadeIn(true);
    }, 250);
  };

  
  return (
    <React.Fragment>
      <div id="experience-card">
        <div id="experience-header">
          <span className="experience-highlighted-text">EXPERIENCE</span>
        </div>
        <div id="experience-container">
          <div className="experience-titles">
            {experiences.map((experience, index) => (
              <div
                key={index}
                className={`experience-title ${clickedIndex === index ? 'clicked' : ''}`}
                onClick={() => handleTitleClick(experience, index)}
              >
                {experience.title}
              </div>
            ))}
          </div>
          <div className={fadeIn ? 'experience-description fade-in' : 'experience-description'}>
            <h3 className="experience-position">{selectedExperience.title}</h3>
            <p className="experience-date">{selectedExperience.date}</p>
            {selectedExperience.points && (
              <ul className="experience-points">
                {selectedExperience.points.map((point, index) => (
                  <li key={index}>{point}</li>
                ))}
              </ul>
            )}
          </div>
        </div>
      </div>
    </React.Fragment>
  );
};

export default Experience;`
#experience-container {
    width: 60%; 
    margin: auto; 
    background-color: rgba(255, 255, 255, 0.8); 
    border-radius: 10px;
    padding: 20px;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    overflow: hidden; 
    transition: height 0.3s ease-out; 

}

#experience-header {
    text-align: center;
    font-family: 'Montserrat', sans-serif;
    font-weight: bolder;
    font-size: 40px;
    z-index: 1;
    position: relative;
    margin-bottom: 40px;
    margin-top: 100px;
}

.experience-highlighted-text {
    position: relative; /* Add relative positioning to the text */
    z-index: 1; /* Ensure the text is above the highlight */
}


.experience-highlighted-text::before {
    content: "";
    position: absolute;
    top: 0px; 
    left: -5px; 
    right: -5px; 
    bottom: 0px; 
    background-color:   #FF7F50;
    z-index: -1;
    border-radius: 3px;
    transform: rotate(-1deg);
    background-color: #FF7F50;
    box-shadow:
        2px 2px 0 #FF7F50,
        4px 1px 0 #FF7F50,
        -2px 2px 0 #FF7F50,
        -4px 1px 0 #FF7F50,
        0 4px 2px rgba(255, 127, 80, 0.4), 
        0 -4px 2px rgba(255, 127, 80, 0.4),
        0 0 5px rgba(0, 0, 0, 0.1); 
}
  
  .experience-titles {
    display: flex;
    justify-content: center;
    gap: 30px; /* Spacing between titles */
    padding: 15px 0;
  
  }
  
  .experience-title {
    cursor: pointer; 
    padding: 10px 23px;
    border-bottom: 2px solid transparent; 
    border-radius: 30px; 
    transition: all 0.3s ease;
    font-family: 'League Spartan', sans-serif;
    font-weight: 500;
    font-size: 20px;
    opacity: 0.4;
  }
  
  .experience-title:hover {
    background-color: #f0f0f0; 
    cursor: pointer; 
}

  .experience-title:active {
      background-color: #e0e0e0; 
  }
  .experience-title.clicked {
    background-color: #e0e0e0;
    opacity: 1;
}

  
  .experience-description {
    margin-top: 20px;
    font-family: 'Open Sans', sans-serif;
    font-weight: normal;
    font-size: 16px;
    padding-left: 28px;
    opacity: 0;
    transition: opacity 0.25s ease-in-out;

    
  }

  .experience-description.fade-in {
    opacity: 1;
}

  .experience-position {
    font-family: 'Montserrat', sans-serif;
    font-size: 20px;
    font-weight: 600;
  }
  
  .experience-date {
    font-family: 'Montserrat', sans-serif;
    font-size: 16px;
    font-weight: 600;
    margin-bottom: 12px;
  }
  
  .experience-points {
    font-family: 'Montserrat', sans-serif;
    font-size: 16px;
    font-weight: 400;
    padding-left: 14px; 
  }

  .experience-points li {
    margin-bottom: 10px; 
}
Md. Rakibul Islam
  • 2,140
  • 1
  • 7
  • 14

1 Answers1

0

Change your css like this:

.experience-description {
    // other styles
    opacity: 0;
    max-height: 0;
    transition: opacity 0.25s ease-in-out, max-height 0.25s ease-in-out;
}

.experience-description.fade-in {
    opacity: 1;
    max-height: 1000px;
}

Note that height property is not animatable and you must use max-height. Another important note is that you should calculate max-height using a better method (using js for example) because 1000px is not an accurate value for the largest available column data and the larger number you use instead of 1000 the faster animation you'll get.

Maybe the better way:

.experience-description {
    // other styles
    opacity: 0;
    clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
    transition: opacity 0.25s ease-in-out, clip-path 0.25s ease-in-out;
}

.experience-description.fade-in {
    opacity: 1;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

Because clip-path property is calculated based on the node itself (and not its parent) it could solve the 1000px being non-accurate issue and it works for all situations.