Docs
SidePanel
SidePanel
A side panel that triggers an expansion animation. Useful for video components or learn more etc.
video
Installation
Copy and paste the following code into your project.
"use client"
import React, { ReactNode, forwardRef } from "react"
import { AnimatePresence, MotionConfig, motion } from "motion/react"
import useMeasure from "react-use-measure"
import { cn } from "@/lib/utils"
type PanelContainerProps = {
panelOpen: boolean
handlePanelOpen: () => void
className?: string
videoUrl?: string
renderButton?: (handleToggle: () => void) => ReactNode
children: ReactNode
}
const sectionVariants = {
open: {
width: "97%",
transition: {
duration: 0.3,
ease: "easeInOut",
delayChildren: 0.3,
staggerChildren: 0.2,
},
},
closed: {
transition: { duration: 0.2, ease: "easeInOut" },
},
}
const sharedTransition = { duration: 0.6, ease: "easeInOut" }
export const SidePanel = forwardRef<HTMLDivElement, PanelContainerProps>(
({ panelOpen, handlePanelOpen, className, renderButton, children }, ref) => {
const [measureRef, bounds] = useMeasure()
return (
<ResizablePanel>
<motion.div
className={cn(
"bg-neutral-900 rounded-r-[44px] w-[160px] md:w-[260px]",
className
)}
animate={panelOpen ? "open" : "closed"}
variants={sectionVariants}
transition={{ duration: 0.2, ease: "easeInOut" }}
>
<motion.div
animate={{ height: bounds.height > 0 ? bounds.height : 0.1 }}
className="h-auto"
transition={{ type: "spring", bounce: 0.02, duration: 0.65 }}
>
<div ref={measureRef}>
<AnimatePresence mode="popLayout">
<motion.div
exit={{ opacity: 0 }}
transition={{
...sharedTransition,
duration: sharedTransition.duration / 2,
}}
key="form"
>
<div
className={cn(
"flex items-center w-full justify-start pl-4 md:pl-4 py-1 md:py-3",
panelOpen ? "pr-3" : ""
)}
>
{renderButton && renderButton(handlePanelOpen)}
</div>
{panelOpen && (
<motion.div
exit={{ opacity: 0 }}
transition={sharedTransition}
>
{children}
</motion.div>
)}
</motion.div>
</AnimatePresence>
</div>
</motion.div>
</motion.div>
</ResizablePanel>
)
}
)
SidePanel.displayName = "SidePanel"
export SidePanel
type ResizablePanelProps = {
children: React.ReactNode
}
const ResizablePanel = React.forwardRef<HTMLDivElement, ResizablePanelProps>(
({ children }, ref) => {
const transition = { type: "ease", ease: "easeInOut", duration: 0.4 }
return (
<MotionConfig transition={transition}>
<div className="flex w-full flex-col items-start">
<div className="mx-auto w-full">
<div
ref={ref}
className={cn(
children ? "rounded-r-none" : "rounded-sm",
"relative overflow-hidden"
)}
>
{children}
</div>
</div>
</div>
</MotionConfig>
)
}
)
ResizablePanel.displayName = "ResizablePanel"
Update the import paths to match your project setup.
Usage
import { SidePanel } from "@/components/ui/side-panel"
export default function SidePanelExample() {
const [isOpen, setIsOpen] = useState(false)
const handleIsOpen = () => {
setIsOpen(!isOpen)
}
const renderOpenButton = (handleToggle: () => void) => (
<div
className={cn(
"flex items-center w-full justify-start pr-4 md:pl-4 py-1 md:py-1",
isOpen ? "pr-3" : ""
)}
>
<p className="text-xl font-black tracking-tight text-gray-900 sm:text-3xl">
<span className="bg-gradient-to-t from-neutral-200 to-stone-300 bg-clip-text font-brand text-xl font-bold text-transparent sm:text-6xl">
Open
</span>
</p>
<Button
className="rounded-r-[33px] py-8 ml-2 "
onClick={handleIsOpen}
variant="secondary"
>
{isOpen ? "close" : "open"}
</Button>
</div>
)
return (
<div className="w-full max-w-4xl">
<div className="min-h-[500px] flex flex-col justify-center border border-dashed rounded-lg space-y-4">
<SidePanel
panelOpen={isOpen}
handlePanelOpen={handleIsOpen}
renderButton={renderVideoButton}
>
<div className="h-16 w-full">
<div>Content Here</div>
</div>
</SidePanel>
</div>
</div>
)
}