AppVideoPlayerDialog
Copy-and-own application wrapper for video dialog primitives, media controls, playlists, and automatic advancement.
Recipe Source
"use client";import { MediaPlayer, MediaPlayerControls, MediaPlayerControlsOverlay, MediaPlayerError, MediaPlayerFullscreen, MediaPlayerPlay, MediaPlayerSeek, MediaPlayerTime, MediaPlayerVideo, MediaPlayerVolume,} from "@giddaa-housing/ui/media-player";import { VideoPlayerDialog, VideoPlayerDialogContent, VideoPlayerDialogDescription, VideoPlayerDialogDetails, VideoPlayerDialogMedia, VideoPlayerDialogPlaylist, VideoPlayerDialogPlaylistHeader, VideoPlayerDialogPlaylistIndicator, VideoPlayerDialogPlaylistItem, VideoPlayerDialogPlaylistList, type VideoPlayerDialogSize, type VideoPlayerDialogTheme, VideoPlayerDialogTitle, VideoPlayerDialogTrigger,} from "@giddaa-housing/ui/video-player-dialog";import type { ComponentProps, ReactElement, ReactNode } from "react";import { useEffect, useState } from "react";export interface AppVideoItem { id: string; title: ReactNode; description?: ReactNode; src: string; poster?: string; duration?: ReactNode; videoChildren?: ReactNode;}interface AppVideoPlayerDialogProps extends Omit<ComponentProps<typeof VideoPlayerDialog>, "children"> { trigger: ReactElement; size?: VideoPlayerDialogSize; theme?: VideoPlayerDialogTheme; title?: ReactNode; description?: ReactNode; src?: string; poster?: string; videoChildren?: ReactNode; playlist?: AppVideoItem[]; autoPlayNext?: boolean;}/** * Example application wrapper. Copy this into the consuming app and adapt the * controls, analytics, data mapping, and playback policy locally. */export function AppVideoPlayerDialog({ trigger, size = "md", theme = "light", title, description, src = "", poster, videoChildren, playlist, autoPlayNext = false, ...props}: AppVideoPlayerDialogProps) { const items: AppVideoItem[] = playlist?.length ? playlist : [{ id: "video", title, description, src, poster, videoChildren }]; const [activeIndex, setActiveIndex] = useState(0); const [autoPlayItemId, setAutoPlayItemId] = useState<string>(); const activeItem = items[activeIndex] ?? items[0]; useEffect(() => { if (activeItem?.id === autoPlayItemId) setAutoPlayItemId(undefined); }, [activeItem?.id, autoPlayItemId]); const playNext = () => { if (!autoPlayNext || activeIndex >= items.length - 1) return; const next = items[activeIndex + 1]; if (!next) return; setAutoPlayItemId(next.id); setActiveIndex(activeIndex + 1); }; if (!activeItem) return null; return ( <VideoPlayerDialog {...props}> <VideoPlayerDialogTrigger render={trigger} /> <VideoPlayerDialogContent size={size} theme={theme}> <VideoPlayerDialogMedia> <MediaPlayer> <MediaPlayerVideo key={activeItem.id} autoPlay={activeItem.id === autoPlayItemId} src={activeItem.src} poster={activeItem.poster} onEnded={playNext} > {activeItem.videoChildren} </MediaPlayerVideo> <MediaPlayerError /> <MediaPlayerControls className="inset-0 flex-col items-stretch justify-end gap-0 p-3 sm:p-4"> <MediaPlayerControlsOverlay /> {items.length > 1 ? ( <VideoPlayerDialogPlaylistIndicator current={activeIndex + 1} total={items.length} /> ) : null} <MediaPlayerPlay className="absolute top-1/2 left-1/2 size-14 -translate-x-1/2 -translate-y-1/2 bg-black/50 [&_svg]:size-5" /> <MediaPlayerSeek className="h-5" withTime={false} /> <div className="flex items-center gap-1.5"> <MediaPlayerPlay /> <MediaPlayerVolume /> <MediaPlayerTime className="px-1 text-gdt-xs font-semibold text-white" /> <span className="flex-1" /> <MediaPlayerFullscreen /> </div> </MediaPlayerControls> </MediaPlayer> </VideoPlayerDialogMedia> {activeItem.title || activeItem.description ? ( <VideoPlayerDialogDetails> {activeItem.title ? ( <VideoPlayerDialogTitle> {activeItem.title} </VideoPlayerDialogTitle> ) : null} {activeItem.description ? ( <VideoPlayerDialogDescription> {activeItem.description} </VideoPlayerDialogDescription> ) : null} </VideoPlayerDialogDetails> ) : null} {items.length > 1 ? ( <VideoPlayerDialogPlaylist aria-label="Video playlist"> <VideoPlayerDialogPlaylistHeader> Playlist · {items.length} videos </VideoPlayerDialogPlaylistHeader> <VideoPlayerDialogPlaylistList> {items.map((item, index) => ( <VideoPlayerDialogPlaylistItem key={item.id} active={index === activeIndex} index={index + 1} poster={item.poster} title={item.title} duration={item.duration} onClick={() => setActiveIndex(index)} /> ))} </VideoPlayerDialogPlaylistList> </VideoPlayerDialogPlaylist> ) : null} </VideoPlayerDialogContent> </VideoPlayerDialog> );}Usage
Copy the recipe into the application, update the import path, and use the local wrapper for the application's standard playback experience.
import { AppVideoPlayerDialog } from "@/components/recipes/app-video-player-dialog";
<AppVideoPlayerDialog
trigger={<Button>Watch video</Button>}
title="A guide to buying your first home"
description="What to prepare before beginning your home-buying journey."
src="/videos/first-home.mp4"
poster="/images/first-home.jpg"
/>Examples
Single Video
Playlist
Set autoPlayNext when a user-started series should advance until its final item.
Props
The recipe's props are application-owned and may be changed after copying.
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactElement | required | Element that opens the dialog. |
size | "sm" | "md" | "lg" | "md" | Maximum dialog width. |
theme | "light" | "dark" | "light" | Dialog surface treatment. |
title, description | ReactNode | - | Metadata for a single video. |
src, poster | string | - | Single-video source and poster. |
videoChildren | ReactNode | - | Native source and track elements. |
playlist | AppVideoItem[] | - | Ordered application video data. |
autoPlayNext | boolean | false | Advances after ended and stops on the final item. |
| dialog root props | Base UI dialog props | - | Controlled or uncontrolled open state. |
Accessibility
- Keep
VideoPlayerDialogTitlein the local composition, or explicitly labelVideoPlayerDialogContent. - Add caption tracks for spoken media.
- Preserve
activeon the current playlist item so it exposesaria-current. - Do not autoplay initial playback with sound.