3
\$\begingroup\$

I’ve built a working animated panel menu in React using Framer Motion. Only one section expands at a time, making the animation smooth and visually clean.

testmenu in action

However, I’d like help improving or simplifying the implementation, especially around layout, maxHeight logic, and content rendering. I feel there might be a much better way to achieve this visual effect.

I am also trying to remove the "fold" effect (best seen when expanding Section 3), and would prefer it if other sections slid off the panel rather than shrinking down.

Code:

testmenu.jsx:

import React, { useState } from 'react';
import { motion } from 'framer-motion';
import './testmenu.css';
const App = () => {
 const [expandedSection, setExpandedSection] = useState(null);
 const sections = [
 {
 key: 'section1',
 title: 'Section 1',
 content: <div>This is Section 1 content.</div>
 },
 {
 key: 'section2',
 title: 'Section 2',
 content: (
 <ul>
 <li>Item A</li>
 <li>Item B</li>
 <li>Item C</li>
 </ul>
 )
 },
 {
 key: 'section3',
 title: 'Section 3',
 content: (
 <div>
 <p>This is a third section with some text and a button:</p>
 <button style={{ marginTop: '0.5rem' }}>Click Me</button>
 </div>
 )
 }
 ];
 const expandedIndex = sections.findIndex(s => s.key === expandedSection);
 return (
 <div className="panel-wrapper">
 <motion.div layout className="menu-panel">
 <div className="panel-title">Panel Title</div>
 <hr className="divider" />
 <motion.div layout className="section-stack">
 {sections.map(({ key, title, content }, index) => {
 const isExpanded = expandedSection === key;
 const isAnyExpanded = expandedSection !== null;
 const isAbove = isAnyExpanded && index < expandedIndex;
 const isBelow = isAnyExpanded && index > expandedIndex;
 let maxHeight = '60px';
 if (isAnyExpanded) {
 if (isExpanded) maxHeight = '600px';
 else if (isAbove || isBelow) maxHeight = '0px';
 }
 return (
 <motion.div
 key={key}
 layout
 layoutId={key}
 className="section"
 animate={{ maxHeight }}
 style={{
 display: 'flex',
 flexDirection: 'column',
 flexGrow: isExpanded ? 999 : 0,
 minHeight: 0,
 overflow: 'hidden',
 pointerEvents: !isAnyExpanded || isExpanded ? 'auto' : 'none',
 transformOrigin: isAbove ? 'top' : 'bottom',
 position: 'relative'
 }}
 transition={{ duration: 0.5, ease: [0.33, 1, 0.68, 1] }}
 >
 {/* WRAPPED HEADER to prevent motion dip */}
 <motion.div layout="position">
 <div
 className="section-header"
 onClick={() =>
 setExpandedSection(isExpanded ? null : key)
 }
 style={{
 height: '60px',
 display: 'flex',
 alignItems: 'center'
 }}
 >
 {title} {isExpanded ? '▼' : '▶'}
 </div>
 </motion.div>
 {/* Absolutely positioned content */}
 <div
 className="section-content"
 style={{
 position: 'absolute',
 top: '60px',
 left: 0,
 right: 0,
 display: isExpanded ? 'block' : 'none'
 }}
 >
 {content}
 </div>
 </motion.div>
 );
 })}
 </motion.div>
 </motion.div>
 </div>
 );
};
export default App;

testmenu.css:

body, html, #root {
 margin: 0;
 padding: 0;
 font-family: sans-serif;
 background: #111;
 color: white;
 height: 100vh;
 width: 100vw;
 display: flex;
 justify-content: center;
 align-items: center;
 }
 
 .panel-wrapper {
 position: fixed;
 top: 50%;
 left: 50%;
 transform: translate(-50%, -50%);
 width: 320px;
 height: 240px;
 display: flex;
 justify-content: center;
 align-items: center;
 }
 
 .menu-panel {
 background: #1e1e2a;
 border-radius: 8px;
 padding: 1rem;
 width: 30vw;
 height: 30vh;
 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
 display: flex;
 flex-direction: column;
 }
 
 .panel-title {
 text-align: center;
 font-weight: bold;
 cursor: pointer;
 padding: 0.5rem 0;
 }
 
 .section-header {
 font-weight: bold;
 cursor: pointer;
 }
 
 .section-content {
 font-size: 0.95rem;
 color: #ddd;
 margin: 0;
 padding: 0;
 }
 
 .section-content > *:first-child,
 .section-content p:first-child {
 margin-top: 0;
 }
 
 .section-content > *:last-child,
 .section-content p:last-child {
 margin-bottom: 0;
 }
 
 .section-stack {
 display: flex;
 flex-direction: column;
 flex-grow: 1;
 min-height: 0;
 overflow: hidden;
 }
 
 .section {
 position: relative;
 }
 
 .divider {
 border: none;
 border-top: 1px solid #444;
 margin: 0.5rem 0 0 0;
 }

I would appreciate any help towards this.

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Apr 17 at 8:41
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.