Press On this card
This Card Expands 🫡
The card shifts its layou smoothly on tap
Component Props
Expandable Card Wrapper
Prop | Type | Default |
---|---|---|
children | React.Node | - |
isOpen | boolean | false |
width | string | - |
Expandable Card Header
Prop | Type | Default |
---|---|---|
children | React.Node | - |
isOpen | boolean | false |
seIsOpen | function | ()=>void |
title | React.Node | string | - |
Expandable Card Body
Prop | Type | Default |
---|---|---|
children | React.Node | - |
isOpen | boolean | false |
Base Component
Copy and paste the following base component in your project
Make sure to install all the required dependencies
HoverButton.tsx
import { motion } from "framer-motion";
import { X } from "lucide-react";
import type { ReactNode } from "react";
interface ExpandableCardWrapperProps {
children: ReactNode;
isOpen: boolean;
width: string;
}
export const ExpandableCardWrapper: React.FC<ExpandableCardWrapperProps> = ({
children,
isOpen,
width,
}) => (
<motion.div
layout
className="group relative cursor-pointer flex flex-col bg-[#1A1A1A] overflow-hidden transition-shadow ease-out hover:shadow-md shadow-inner rounded-2xl p-4 gap-4"
initial="close"
animate={isOpen ? "open" : "close"}
variants={{
open: {
width: width,
transition: { type: "spring", damping: 15, stiffness: 100 },
},
close: {
width: "200px",
transition: { delay: 0.3, type: "spring", damping: 15, stiffness: 100 },
},
}}
>
{children}
</motion.div>
);
interface ExpandableCardHeaderProps {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
title: string | ReactNode;
children: ReactNode;
}
export const ExpandableCardHeader: React.FC<ExpandableCardHeaderProps> = ({
isOpen,
setIsOpen,
title,
children,
}) => {
return (
<motion.div
layout
onClick={() => setIsOpen(!isOpen)}
className="flex flex-col gap-4"
>
<motion.div
layout
className="flex flex-row items-center justify-between w-full"
>
<motion.div className="flex-grow">{title}</motion.div>
{isOpen && (
<motion.div className="bg-[#0DFFC5] p-1 rounded-full">
<X className="w-5 h-5 text-background" />
</motion.div>
)}
</motion.div>
<motion.div layout className="flex flex-col gap-1">
{children}
</motion.div>
</motion.div>
);
};
interface ExpandableCardBodyProps {
children: ReactNode;
isOpen: boolean;
}
export const ExpandableCardBody: React.FC<ExpandableCardBodyProps> = ({
children,
isOpen,
}) => (
<>
{isOpen && (
<motion.div
layout
initial="closed"
animate="open"
exit="closed"
variants={{
open: {
transition: {
staggerChildren: 0.05,
delayChildren: 0.3,
},
},
closed: {
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
},
},
}}
className="flex flex-col items-start gap-2"
>
{children}
</motion.div>
)}
</>
);