cult-ui
Layout
Display
Navigation

side-panel

A side panel that triggers an expansion animation.

animated
button
flex
form
motion
positioning
transition

Source Code

Files
video-section
1"use client"
2 
3import React, {
4  ReactNode,
5  createContext,
6  forwardRef,
7  useContext,
8  useState,
9} from "react"
10import { AnimatePresence, MotionConfig, motion } from "motion/react"
11import ReactPlayer from "react-player/lazy"
12import useMeasure from "react-use-measure"
13 
14import { cn } from "@/lib/utils"
15import { Button } from "@/components/ui/button"
16 
17import { SidePanel } from "../ui/side-panel"
18 
19// Theme Context for Styling
20type ThemeContextType = {
21  panelClass?: string
22}
23 
24// Create the context with the type
25const ThemeContext = createContext<ThemeContextType>({})
26 
27// Custom Hook for Measuring and Animations
28const useCustomMeasure = () => {
29  const [ref, bounds] = useMeasure()
30  const animateProps = {
31    animate: { height: bounds.height > 0 ? bounds.height : 0.1 },
32    transition: { type: "spring", bounce: 0.02, duration: 0.65 },
33  }
34  return { ref, animateProps }
35}
36 
37// ResizablePanel Component
38type ResizablePanelProps = {
39  children: ReactNode
40  className?: string
41}
42 
43// ResizablePanel Component
44const ResizablePanel = forwardRef<HTMLDivElement, ResizablePanelProps>(
45  ({ children, className, ...props }, ref) => {
46    const transition = { type: "ease", ease: "easeInOut", duration: 0.4 }
47 
48    return (
49      <MotionConfig transition={transition}>
50        <div
51          ref={ref}
52          className={cn("flex w-full flex-col items-start", className)}
53          {...props}
54        >
55          <div className="mx-auto w-full">
56            <div
57              className={cn(
58                children ? "rounded-r-none" : "rounded-sm",
59                "relative overflow-hidden"
60              )}
61            >
62              {children}
63            </div>
64          </div>
65        </div>
66      </MotionConfig>
67    )
68  }
69)
70 
71ResizablePanel.displayName = "ResizablePanel"
72 
73// VideoPowerButton Component
74type VideoPowerButtonProps = {
75  handleVideoOpen: () => void
76}
77 
78// VideoPowerButton Component
79const VideoPowerButton = forwardRef<HTMLInputElement, VideoPowerButtonProps>(
80  ({ handleVideoOpen }, ref) => {
81    return (
82      <div className="flex flex-col items-center py-2 mx-3">
83        <input
84          ref={ref}
85          className="pb-4"
86          id="checkbox"
87          type="checkbox"
88          onChange={handleVideoOpen}
89        />
90        <label className="switch" htmlFor="checkbox">
91          <svg
92            xmlns="http://www.w3.org/2000/svg"
93            viewBox="0 0 512 512"
94            className="slider"
95          >
96            {/* SVG path here */}
97          </svg>
98        </label>
99      </div>
100    )
101  }
102)
103 
104VideoPowerButton.displayName = "VideoPowerButton"
105 
106type YoutubeVideoProps = {
107  videoOpen: boolean
108  url: string
109}
110 
111const YoutubeVideo = forwardRef<HTMLDivElement, YoutubeVideoProps>(
112  ({ videoOpen, url }, ref) => {
113    return (
114      <AnimatePresence>
115        {videoOpen && (
116          <motion.div
117            ref={ref}
118            className="md:flex md:justify-center py-1 px-1 md:py-8 md:px-8 w-full h-[300px] md:h-[800px] md:aspect-video rounded-2xl bg-black"
119            initial="hidden"
120            animate="visible"
121            exit="hidden"
122          >
123            <ReactPlayer
124              width="100%"
125              height="100%"
126              controls={false}
127              color="white"
128              url={url}
129            />
130          </motion.div>
131        )}
132      </AnimatePresence>
133    )
134  }
135)
136 
137YoutubeVideo.displayName = "YoutubeVideo"
138 
139type VideoContentProps = {
140  url: string
141  videoOpen: boolean
142}
143 
144// Define the VideoContent component
145const VideoContent: React.FC<VideoContentProps> = ({ url, videoOpen }) => {
146  // Define the animation variants
147  const videoVariants = {
148    hidden: { opacity: 0, scale: 0.9, y: 30 },
149    visible: { opacity: 1, scale: 1, y: 0 },
150  }
151 
152  // Define transition properties
153  const transition = {
154    duration: 0.2,
155    ease: [0.04, 0.62, 0.23, 0.98], // Custom cubic-bezier easing
156    delay: 0.3,
157  }
158 
159  return (
160    <AnimatePresence>
161      {videoOpen && (
162        <motion.div
163          className="md:flex md:justify-center py-1 px-1 md:py-8 md:px-8 w-full h-[300px] md:h-[800px] md:aspect-video rounded-2xl bg-black"
164          initial="hidden"
165          animate="visible"
166          exit="hidden"
167          variants={videoVariants}
168          transition={transition}
169        >
170          <ReactPlayer
171            width="100%"
172            height="100%"
173            controls={false}
174            color="white"
175            url={url}
176          />
177        </motion.div>
178      )}
179    </AnimatePresence>
180  )
181}
182 
183// Define the main VideoSection component
184type VideoSectionProps = {
185  videoOpen: boolean
186  handleVideoOpen: () => void
187  className?: string
188  videoUrl: string
189  children?: ReactNode // Add this line
190}
191 
192const VideoSection: React.FC<VideoSectionProps> = ({
193  videoOpen,
194  handleVideoOpen,
195  className,
196  videoUrl,
197  children,
198}) => {
199  const theme = useContext(ThemeContext)
200 
201  return (
202    <ResizablePanel className={cn(className, theme.panelClass)}>
203      {children ? (
204        children
205      ) : (
206        <>
207          <VideoPowerButton handleVideoOpen={handleVideoOpen} />
208          <VideoContent url={videoUrl} videoOpen={videoOpen} />
209        </>
210      )}
211    </ResizablePanel>
212  )
213}
214 
215export {
216  VideoContent,
217  VideoSection,
218  YoutubeVideo,
219  VideoPowerButton,
220  ResizablePanel,
221  useCustomMeasure,
222}
223 
224export function VideoSectionDemo() {
225  const [videoOpen, setVideoOpen] = useState(false)
226 
227  const handleVideoOpen = () => {
228    setVideoOpen(!videoOpen)
229  }
230 
231  const renderVideoButton = (handleToggle: () => void) => (
232    <div
233      className={cn(
234        "flex items-center w-full justify-start pr-4 md:pl-4 py-1 md:py-1",
235        videoOpen ? "pr-3" : ""
236      )}
237    >
238      <p className="text-xl font-black tracking-tight text-gray-900 sm:text-3xl">
239        <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">
240          video
241        </span>
242      </p>
243      <Button
244        className="rounded-r-[33px] py-8 ml-2 "
245        onClick={handleVideoOpen}
246        variant="secondary"
247      >
248        {videoOpen ? "close" : "open"}
249      </Button>
250    </div>
251  )
252 
253  return (
254    <div className="w-full max-w-4xl">
255      <div className="min-h-[500px]  flex flex-col justify-center border border-dashed rounded-lg space-y-4">
256        <SidePanel
257          panelOpen={videoOpen}
258          handlePanelOpen={handleVideoOpen}
259          renderButton={renderVideoButton}
260        >
261          <YoutubeVideo
262            videoOpen={videoOpen}
263            url={"https://youtu.be/ta6m_l3lZvQ?si=1CPubGeqxLVG0i2L"}
264          />
265        </SidePanel>
266      </div>
267    </div>
268  )
269}