AppVideoPlayerDialog

Copy-and-own application wrapper for video dialog primitives, media controls, playlists, and automatic advancement.

Recipe Source

components/ui/app-video-player-dialog.tsx
"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.

PropTypeDefaultDescription
triggerReactElementrequiredElement that opens the dialog.
size"sm" | "md" | "lg""md"Maximum dialog width.
theme"light" | "dark""light"Dialog surface treatment.
title, descriptionReactNode-Metadata for a single video.
src, posterstring-Single-video source and poster.
videoChildrenReactNode-Native source and track elements.
playlistAppVideoItem[]-Ordered application video data.
autoPlayNextbooleanfalseAdvances after ended and stops on the final item.
dialog root propsBase UI dialog props-Controlled or uncontrolled open state.

Accessibility

  • Keep VideoPlayerDialogTitle in the local composition, or explicitly label VideoPlayerDialogContent.
  • Add caption tracks for spoken media.
  • Preserve active on the current playlist item so it exposes aria-current.
  • Do not autoplay initial playback with sound.

On this page