cssreactjsreact-typescript

Trying to replicate pure CSS stacking cards with react tsx, not sure how to proceed


I'm trying to recreate a component I've mad ein pure css in a react tsx component, but im not sure how to proceed.

Here is the html/css that does exaclty what I want.

<body>
    <div class="left-content">
        <h1>Headings</h1>
        <p>This section contains the headings and occupies the left 50% of the screen.</p>
    </div>
    <div class="container">
        <ul id="cards">
            <li class="card" id="card1">
                <div class="card-body">
                    <h2>Card 1</h2>
                </div>
            </li>
            <li class="card" id="card2">
                <div class="card-body">
                    <h2>Card 2</h2>
                </div>
            </li>
            <li class="card" id="card3">
                <div class="card-body">
                    <h2>Card 3</h2>
                </div>
            </li>
            <li class="card" id="card4">
                <div class="card-body">
                    <h2>Card 4</h2>
                </div>
            </li>
        </ul>
    </div>
  
</body>

css:

body {
            background-color: #2E3537;
            font-family: 'Poppins', sans-serif;
        }

        :root {
            --cards: 5;
            --cardHeight: 87vh;
            --cardTopPadding: 0em;
            --cardMargin: 4vw;
        }

        .container {
            width: 50%;
            margin: 0;
            position: absolute;
            right: 0;
        }

        #cards {
            list-style: none;
            padding-left: 0;
            display: grid;
            grid-template-columns: 1fr;
            grid-template-rows: repeat(var(--cards), var(--cardHeight));
            gap: var(--cardMargin);
            padding-bottom: calc(var(--cards) * var(--cardTopPadding));
            margin-bottom: var(--cardMargin);
        }

        .card {
            position: sticky;
            top: 0;
            padding-top: calc(var(--index) * var(--cardTopPadding));
        }

        #card1 .card-body {
            background-color: #52B2CF;
        }
        #card2 .card-body {
            background-color: #E5A36F;
        }
        #card3 .card-body {
            background-color: #9CADCE;
        }
        #card4 .card-body {
            background-color: #D4AFB9;
        }

        .card-body {
            box-sizing: border-box;
            padding: 30px;
            border-radius: 50px;
            box-shadow: 0 0 30px 0 rgba(0,0,0,0.3);
            height: var(--cardHeight);
            display: flex;
            justify-content: center;
            align-items: center;
            transition: all 0.5s;
        }

        h2 {
            font-size: 8em;
        }

        .left-content {
            width: 50%;
            float: left;
            padding: 20px;
            color: white;
            box-sizing: border-box;
          position:fixed;
        }

        .left-content h1 {
            font-size: 3em;
        }

        .left-content p {
            font-size: 1.5em;
}

and this is my attaempt at a reatc tsx component:


import React, { useEffect, useState } from 'react';
import { styled } from '@mui/system';
import { motion, useAnimation } from 'framer-motion';
import { useInView } from 'react-intersection-observer';
import placeholderImage from './placeholder.png'; // Assume you have a placeholder image
import './ThirdSection.css';

const BigRow = styled('div')({
    backgroundColor: '#00E186',
    color: 'white',
    minHeight: '100vh',
    display: 'flex',
    flexDirection: 'row',
    padding: '0',
    margin: '0',
    overflow: 'hidden',
});

const TextSection = styled('div')({
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    flex: '1',
    width: '100%',
    paddingTop: '60px', // Adjust padding to account for the fixed navbar
    padding: '0',
    margin: '0',
    position: 'relative',
});

const StackedCardsContainer = styled('div')({
    flex: '1 1 60%',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    height: '100vh',
    // overflow: 'hidden',
    margin: '20px',
    position: 'relative',
});

const Image = styled(motion.img)({
    width: '80%',
    height: '80%',
    objectFit: 'cover',
});

const TextContainer = styled('div')({
    flex: '40%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    height: '100vh',
    padding: '2rem',
    textAlign: 'center',
});

const AnimatedText = styled(motion.h1)({
    fontSize: '5rem',
    fontWeight: 'bold',
});

const HorizontalLine = styled('hr')({
    width: '100%',
    borderTop: '1px solid white',
    margin: '2rem 0', 

});

const AnimatedH5 = styled('h5')({
    fontSize: '1.5rem',
    fontWeight: 'normal',
    margin: '0rem 0',
});

const ThirdSection: React.FC = () => {
    const [displayText, setDisplayText] = useState('');
    const text = '  Subheading goes here.';
    const controls = useAnimation();
    const [ref, inView] = useInView({
        threshold: 0.2,
        triggerOnce: true,
    });

    useEffect(() => {
        if (inView) {
            controls.start('visible');
        }
    }, [controls, inView]);

    const textVariants = {
        hidden: { opacity: 0, y: 20 },
        visible: { opacity: 1, y: 0, transition: { duration: 2 } },
    };

 

    useEffect(() => {
        let index = 0;
        const interval = setInterval(() => {
            setDisplayText((prev) => {
                if (index < text.length) {
                    return prev + text[index];
                }
                clearInterval(interval);
                return prev;
            });
            index++;
        }, 2);
        return () => clearInterval(interval);
    }, [text]);

    return (
        <BigRow ref={ref}>
            <TextSection>
                <TextContainer>
                    <AnimatedText
                        variants={textVariants}
                        initial="hidden"
                        animate={controls}
                    >
                        Big heading here
                    </AnimatedText>
                    <HorizontalLine />
                    <AnimatedH5>{displayText}</AnimatedH5>
                </TextContainer>
                <StackedCardsContainer>
                    <div className="container">
                        <ul id="cards">
                            <li className="card" id="card1">
                                <div className="card-body">
                                    <h2>Card 1</h2>
                                </div>
                            </li>
                            <li className="card" id="card2">
                                <div className="card-body">
                                    <h2>Card 2</h2>
                                </div>
                            </li>
                            <li className="card" id="card3">
                                <div className="card-body">
                                    <h2>Card 3</h2>
                                </div>
                            </li>
                            <li className="card" id="card4">
                                <div className="card-body">
                                    <h2>Card 4</h2>
                                </div>
                            </li>
                        </ul>
                    </div>
                </StackedCardsContainer>
            </TextSection>
        </BigRow>

    );
};

export default ThirdSection;

Where ThirdSection.css is the same stylesheet as before.

The main issue is that overflow:hidden is required for the bigrow parent, sinc otherwise the cards overflow ino the parent, but have overflow:hidden breaks the animation.

The partial solution is having overflow:scroll for the StackCardsContainer, but that doesn't look very good, as you have 2 scrolling bars, and also the textSection is then not fixed.

Any suggestions on how I can fix this? Any help would be greatly appreciated!


Solution

  • I've changed the code a bit to work the way the html one is working. Hope it helps.

    Edited:

    tsx:

    const BigRow = styled('div')({
        height:'100%',
        backgroundColor: '#00E186',
        color: 'white',
        display: 'flex',
        flexDirection: 'row',
        padding: '0',
        margin: '0',
        overflow: 'hidden',
    });
    
    const TextSection = styled('div')({
        overflow: 'auto',
        width: '100%',
        position: 'relative',
    });
    
    const StackedCardsContainer = styled('div')({
        flex: '1 1 60%',
        alignItems: 'center',
        justifyContent: 'center',
        position: 'relative',
        height:'100%'
    });
    
    const Image = styled(motion.img)({
        width: '80%',
        height: '80%',
        objectFit: 'cover',
    });
    
    const TextContainer = styled('div')({
        flex: '40%',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        padding: '2rem',
        textAlign: 'center',
        position: 'fixed',
        top: '50%',
        left: '0',
        transform:'translate(0, -50%)',
        pointerEvents:"none",
    });
    
    const AnimatedText = styled(motion.h1)({
        fontSize: '5rem',
        fontWeight: 'bold',
    });
    
    const HorizontalLine = styled('hr')({
        width: '100%',
        borderTop: '1px solid white',
        margin: '2rem 0',
    
    });
    
    const AnimatedH5 = styled('h5')({
        fontSize: '1.5rem',
        fontWeight: 'normal',
        margin: '0rem 0',
    });
    
    export const ThirdSection: React.FC = () => {
        const [displayText, setDisplayText] = useState('');
        const text = '  Subheading goes here.';
        const controls = useAnimation();
        const [ref, inView] = useInView({
            threshold: 0.2,
            triggerOnce: true,
        });
    
        useEffect(() => {
            if (inView) {
                controls.start('visible');
            }
        }, [controls, inView]);
    
        const textVariants = {
            hidden: { opacity: 0, y: 20 },
            visible: { opacity: 1, y: 0, transition: { duration: 2 } },
        };
    
    
    
        useEffect(() => {
            let index = 0;
            const interval = setInterval(() => {
                setDisplayText((prev) => {
                    if (index < text.length) {
                        return prev + text[index];
                    }
                    clearInterval(interval);
                    return prev;
                });
                index++;
            }, 2);
            return () => clearInterval(interval);
        }, [text]);
    
        return (
            <BigRow ref={ref}>
                <TextSection>
                    <TextContainer>
                        <AnimatedText
                            variants={textVariants}
                            initial="hidden"
                            animate={controls}
                        >
                            Big heading here
                        </AnimatedText>
                        <HorizontalLine />
                        <AnimatedH5>{displayText}</AnimatedH5>
                    </TextContainer>
                    <StackedCardsContainer>
                        <div className="container">
                            <ul id="cards">
                                <li className="card" id="card1">
                                    <div className="card-body">
                                        <h2>Card 1</h2>
                                    </div>
                                </li>
                                <li className="card" id="card2">
                                    <div className="card-body">
                                        <h2>Card 2</h2>
                                    </div>
                                </li>
                                <li className="card" id="card3">
                                    <div className="card-body">
                                        <h2>Card 3</h2>
                                    </div>
                                </li>
                                <li className="card" id="card4">
                                    <div className="card-body">
                                        <h2>Card 4</h2>
                                    </div>
                                </li>
                            </ul>
                        </div>
                    </StackedCardsContainer>
                </TextSection>
            </BigRow>
    
        );
    };
    

    css:

    html,body,#root,.App{
        height: 100%;
    }
    
    body {
        background-color: #2E3537;
        font-family: 'Poppins', sans-serif;
    }
    
    :root {
        --cards: 5;
        --cardHeight: 87vh;
        --cardTopPadding: 0em;
        --cardMargin: 4vw;
    }
    
    .container {
        width: 40%;
        margin: 0;
        position: absolute;
        right: 0;
        height: 100vh;
        padding: 20px;
    }
    
    #cards {
        list-style: none;
        padding-left: 0;
        display: grid;
        grid-template-columns: 1fr;
        grid-template-rows: repeat(var(--cards), var(--cardHeight));
        gap: var(--cardMargin);
        padding-bottom: calc(var(--cards) * var(--cardTopPadding));
        margin-bottom: var(--cardMargin);
    }
    
    .card {
        position: sticky;
        top: 0;
        padding-top: calc(var(--index) * var(--cardTopPadding));
    }
    
    #card1 .card-body {
        background-color: #52B2CF;
    }
    #card2 .card-body {
        background-color: #E5A36F;
    }
    #card3 .card-body {
        background-color: #9CADCE;
    }
    #card4 .card-body {
        background-color: #D4AFB9;
    }
    
    .card-body {
        box-sizing: border-box;
        padding: 30px;
        border-radius: 50px;
        box-shadow: 0 0 30px 0 rgba(0,0,0,0.3);
        height: var(--cardHeight);
        display: flex;
        justify-content: center;
        align-items: center;
        transition: all 0.5s;
    }
    
    h2 {
        font-size: 8em;
    }
    
    .left-content {
        width: 50%;
        float: left;
        padding: 20px;
        color: white;
        box-sizing: border-box;
        position:fixed;
    }
    
    .left-content h1 {
        font-size: 3em;
    }
    
    .left-content p {
        font-size: 1.5em;
    }