Skip to content

Instantly share code, notes, and snippets.

@mesabuca
Created April 17, 2025 08:35
Show Gist options
  • Save mesabuca/30912cdd92d3b9bd838c0b43a7c7d695 to your computer and use it in GitHub Desktop.
Save mesabuca/30912cdd92d3b9bd838c0b43a7c7d695 to your computer and use it in GitHub Desktop.
Collapse, framer motion, tailwind
'use client';
import React, { useState, createContext, useContext, ReactNode } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import clsx from 'clsx';
interface CollapseContextProps {
isOpen: boolean;
toggle: () => void;
}
const CollapseContext = createContext<CollapseContextProps | undefined>(undefined);
interface CollapseProps {
children: ReactNode;
className?: string;
}
export const Collapse = ({ children, className }: CollapseProps) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(prev => !prev);
return (
<CollapseContext.Provider value={{ isOpen, toggle }}>
<div className={clsx('w-full', className)}>
{children}
</div>
</CollapseContext.Provider>
);
};
interface CollapseTitleProps {
children: ReactNode;
className?: string;
}
export const CollapseTitle = ({ children, className }: CollapseTitleProps) => {
const context = useContext(CollapseContext);
if (!context) throw new Error('CollapseTitle must be used within Collapse');
return (
<button
onClick={context.toggle}
className={clsx('w-full text-left cursor-pointer flex items-center justify-between gap-2', className)}
>
<span>{children}</span>
<motion.span
animate={{ rotate: context.isOpen ? 180 : 0 }}
transition={{ duration: 0.3 }}
className="transform"
>
</motion.span>
</button>
);
};
interface CollapseAreaProps {
children: ReactNode;
className?: string;
}
export const CollapseArea = ({ children, className }: CollapseAreaProps) => {
const context = useContext(CollapseContext);
if (!context) throw new Error('CollapseArea must be used within Collapse');
return (
<AnimatePresence initial={false}>
{context.isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
className={clsx('overflow-hidden', className)}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment