Created
April 17, 2025 08:35
-
-
Save mesabuca/30912cdd92d3b9bd838c0b43a7c7d695 to your computer and use it in GitHub Desktop.
Collapse, framer motion, tailwind
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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