video-flow-b/components/common/TemplatePreviewModal.tsx
2025-10-22 04:02:58 +08:00

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 this',
}: 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;