Search code examples
reactjstailwind-cssexpandslidedownclsx

Click and display child component delay/animation in React


I'm trying to create a component that, upon clicked, will display it's children component(s).

I have this code:

type ExpandCollapseProps = ComponentProps<"div"> & VariantProps<typeof expandCollapseStyles> & {
    previewText?: string;
    children: React.ReactNode;
}

export const ExpandCollapse = forwardRef<HTMLDivElement, ExpandCollapseProps>(({ variant, className, children, previewText, ...props }, ref) => {

    const [expanded, setExpanded] = useState<boolean>(false);

    return (
        <div className={cn(expandCollapseStyles({ variant, className }))} ref={ref} {...props} onClick={() => setExpanded(!expanded)}>
            <div className="space-y-2">
                <p className="font-medium">{previewText}</p>

                {expanded &&
                    <div className="">
                        {children}
                    </div>
                }

            </div>
        </div>
    )
});

Which works fine, clicking it will in fact expand/collapse the desired component. However, I would like to make this a smooth transition whereby the component slides down and/or fades into visibility.

What would be the best way to approach this?

Side information:

These are the packages I'm using for CSS/styling (in case anyone knows a solution perhaps utilizing these):

⚠️ I do not want to install an external component package that will complete the task for me. I'm trying to learn how to create reusable UI components on my own.

Thanks!


Solution

  • Short Explanation about Animating Conditionally rendered elements

    Unmounting/Mounting the component makes it harder to animate the element. While you can technically achieve it by setting a key to the element and writing a CSS animation for the element, it's hard to animate it when the element is unmounted.

    While you can also look into Framer Motion to animate exit and entry of a component.

    Here is another way you can approach this.


    Instead of mounting and unmounting the children. You can wrap them in a container and style them properly to match your conditions of being visible and hidden. You can still render the children without the condition but you change the styles based on the condition.

    Here is a simple example that uses Tailwind and CLSX to conditionally apply styles to the children. You can customise to achieve your desired animation.

    import clsx from 'clsx';
    
    function Component() {
        const [isExpanded, setExpanded] = useState<boolean>(false);
    
        return (
          <div>
            {/* Other siblings */}
                <div className={clsx('max-h-0 transition-all ...', // default classes
                    isExpanded && 'max-h-[100px]' // classes when expanded
                )}>{children}</div>
          </div>
        );
    }
    

    We are using max height property to animate the expand/collapse of the component. Here is an example from W3 Schools on how to create an animated collapsible: https://www.w3schools.com/howto/howto_js_collapsible.asp.

    It has a good approach to setting the max height. Rather than choosing a fixed value, it is much more dynamic and uses the scroll height of the content.