forked from 77media/video-flow
230 lines
9.5 KiB
TypeScript
230 lines
9.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useRef } from 'react';
|
|
import { X, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { useDeviceType } from '@/hooks/useDeviceType';
|
|
|
|
interface TemplatePreviewModalProps {
|
|
/** Control visibility */
|
|
open: boolean;
|
|
/** Video URL to preview */
|
|
videoUrl: string | null;
|
|
/** Close callback */
|
|
onClose: () => void;
|
|
/** Optional title shown over the video */
|
|
title?: string;
|
|
/** Optional description shown over the video */
|
|
description?: string;
|
|
/** Optional primary action (e.g., Try it) */
|
|
onPrimaryAction?: () => void;
|
|
/** Optional primary action label */
|
|
primaryLabel?: string;
|
|
}
|
|
|
|
/**
|
|
* Fullscreen, Tailwind-based template preview modal with video content.
|
|
* Overlays title/description if provided and supports an optional primary action.
|
|
* Supports expand/collapse for long descriptions and H5 responsive design.
|
|
*/
|
|
export function TemplatePreviewModal({
|
|
open,
|
|
videoUrl,
|
|
onClose,
|
|
title,
|
|
description,
|
|
onPrimaryAction,
|
|
primaryLabel = 'Try it Free',
|
|
}: TemplatePreviewModalProps) {
|
|
const [isReady, setIsReady] = useState(false);
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
const [showExpandButton, setShowExpandButton] = useState(false);
|
|
const descriptionRef = useRef<HTMLDivElement>(null);
|
|
const { isMobile } = useDeviceType();
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
setIsReady(false);
|
|
setIsExpanded(false);
|
|
}
|
|
}, [open]);
|
|
|
|
useEffect(() => {
|
|
if (isReady && description && descriptionRef.current) {
|
|
const element = descriptionRef.current;
|
|
const isOverflowing = element.scrollHeight > element.clientHeight;
|
|
setShowExpandButton(isOverflowing);
|
|
}
|
|
}, [isReady, description]);
|
|
|
|
if (!open || !videoUrl) return null;
|
|
|
|
return (
|
|
<>
|
|
<style jsx>{`
|
|
.description-scroll::-webkit-scrollbar {
|
|
width: 4px;
|
|
}
|
|
.description-scroll::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
.description-scroll::-webkit-scrollbar-thumb {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 2px;
|
|
}
|
|
.description-scroll::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
}
|
|
`}</style>
|
|
<div
|
|
data-alt="template-preview-modal"
|
|
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
|
|
onClick={onClose}
|
|
>
|
|
<div
|
|
data-alt="template-preview-modal-content"
|
|
className={`
|
|
relative rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl
|
|
${isMobile
|
|
? 'w-[95vw] max-h-[90vh] mx-2'
|
|
: 'w-[70vw] min-h-[40vw]'
|
|
}
|
|
`}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<video
|
|
src={videoUrl}
|
|
autoPlay
|
|
loop
|
|
muted
|
|
playsInline
|
|
onCanPlay={() => setIsReady(true)}
|
|
className={`w-full h-auto transition-opacity duration-200 ${isReady ? 'opacity-100' : 'opacity-0'}`}
|
|
/>
|
|
|
|
{!isReady && (
|
|
<div
|
|
data-alt="template-preview-loading"
|
|
className="absolute inset-0 flex items-center justify-center"
|
|
>
|
|
<div className={`rounded-full border-2 border-white/30 border-t-white/80 animate-spin ${isMobile ? 'h-8 w-8' : 'h-10 w-10'}`} />
|
|
</div>
|
|
)}
|
|
|
|
{isReady && title && (
|
|
<div
|
|
data-alt="template-preview-header"
|
|
className={`
|
|
absolute top-0 left-0 right-0
|
|
bg-gradient-to-b from-black/90 via-black/80 to-transparent
|
|
text-left
|
|
${isMobile ? 'px-3 py-2' : 'px-6 py-8'}
|
|
`}
|
|
>
|
|
<div className={`font-bold text-white/90 line-clamp-2 ${isMobile ? 'text-base' : 'text-2xl'}`}>
|
|
{title}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isReady && (description || onPrimaryAction) && (
|
|
<div
|
|
data-alt="template-preview-footer"
|
|
className={`
|
|
absolute bottom-0 left-0 right-0
|
|
bg-gradient-to-t from-black/95 via-black/85 to-transparent
|
|
text-center
|
|
${isMobile ? 'px-3 py-2' : 'px-8 py-6'}
|
|
`}
|
|
>
|
|
{description && (
|
|
<div className={`text-left ${isMobile ? 'mb-2' : 'mb-3'}`}>
|
|
<div
|
|
ref={descriptionRef}
|
|
className={`
|
|
text-white/80 transition-all duration-300
|
|
${isMobile ? 'text-sm' : 'text-base'}
|
|
${isExpanded
|
|
? `${isMobile ? 'max-h-20' : 'max-h-40'} overflow-y-auto pr-2 description-scroll`
|
|
: 'line-clamp-2'
|
|
}
|
|
`}
|
|
style={isExpanded ? {
|
|
scrollbarWidth: 'thin',
|
|
scrollbarColor: 'rgba(255, 255, 255, 0.2) transparent'
|
|
} : undefined}
|
|
>
|
|
{description}
|
|
</div>
|
|
{showExpandButton && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
className={`
|
|
inline-flex items-center gap-1 mt-1
|
|
text-white/60 hover:text-white/90
|
|
transition-colors
|
|
${isMobile ? 'text-xs' : 'text-sm'}
|
|
`}
|
|
data-alt="description-toggle"
|
|
>
|
|
{isExpanded ? (
|
|
<>
|
|
<span>Collapse</span>
|
|
<ChevronUp className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
|
|
</>
|
|
) : (
|
|
<>
|
|
<span>Expand</span>
|
|
<ChevronDown className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
|
|
</>
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
{onPrimaryAction && (
|
|
<button
|
|
type="button"
|
|
className={`
|
|
inline-flex items-center justify-center
|
|
rounded-full border border-white/30 bg-white/10
|
|
text-white hover:border-white hover:bg-white hover:text-slate-900
|
|
transition-all duration-200
|
|
${isMobile ? 'px-5 py-2 text-sm' : 'px-6 py-2.5 text-base'}
|
|
`}
|
|
onClick={onPrimaryAction}
|
|
data-alt="template-preview-primary"
|
|
>
|
|
{primaryLabel}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
data-alt="template-preview-modal-close"
|
|
className={`
|
|
absolute inline-flex items-center justify-center
|
|
rounded-full border border-white/30 bg-white/10
|
|
text-white hover:border-white hover:bg-white hover:text-slate-900
|
|
transition-all duration-200
|
|
${isMobile
|
|
? 'top-2 right-2 p-1.5'
|
|
: 'top-3 right-3 p-2'
|
|
}
|
|
`}
|
|
onClick={onClose}
|
|
>
|
|
<X className={isMobile ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default TemplatePreviewModal;
|
|
|
|
|