统一创建入口:模板选择兼容H5、选中模板没有配置文本 不展示输入框

This commit is contained in:
北枳 2025-10-21 15:53:21 +08:00
parent 7ffb8b3297
commit ae807f33e9
3 changed files with 164 additions and 39 deletions

View File

@ -1,7 +1,8 @@
"use client";
import { useEffect, useState } from 'react';
import { X } from 'lucide-react';
import { useEffect, useState, useRef } from 'react';
import { X, ChevronDown, ChevronUp } from 'lucide-react';
import { useDeviceType } from '@/hooks/useDeviceType';
interface TemplatePreviewModalProps {
/** Control visibility */
@ -23,6 +24,7 @@ interface TemplatePreviewModalProps {
/**
* 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,
@ -31,27 +33,62 @@ export function TemplatePreviewModal({
title,
description,
onPrimaryAction,
primaryLabel = 'Try it',
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/80 flex items-center justify-center p-4"
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 w-[70vw] min-h-[40vw] rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl"
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
@ -69,50 +106,121 @@ export function TemplatePreviewModal({
data-alt="template-preview-loading"
className="absolute inset-0 flex items-center justify-center"
>
<div className="h-10 w-10 rounded-full border-2 border-white/30 border-t-white/80 animate-spin" />
<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 || description) && (
{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 px-6 py-8 text-left"
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'}
`}
>
{title && (
<div className="text-bold text-2xl text-white/90 line-clamp-2 mb-2">{title}</div>
)}
<div className={`font-bold text-white/90 line-clamp-2 ${isMobile ? 'text-base' : 'text-2xl'}`}>
{title}
</div>
</div>
)}
{isReady && onPrimaryAction && (
{isReady && (description || onPrimaryAction) && (
<div
data-alt="template-preview-footer"
className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/80 to-transparent px-8 py-6 text-center"
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-base text-white/80 line-clamp-2 text-left">{description}</div>
)}
<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"
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 mt-3 px-6 py-2 text-white hover:border-white hover:bg-white hover:text-slate-900"
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 top-3 right-3 inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 p-2 text-white hover:border-white hover:bg-white hover:text-slate-900"
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="h-4 w-4" />
<X className={isMobile ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
</button>
</div>
</div>
</>
);
}

View File

@ -63,6 +63,21 @@ export default function VideoCreationForm() {
void getTemplateStoryList();
}, [getTemplateStoryList]);
/** Clear inputText when currentTemplate changes */
useEffect(() => {
if (currentTemplate) {
setInputText('');
}
}, [currentTemplate]);
/** Determine if input textarea should be shown based on selected template's freeInput */
const shouldShowInput = useMemo(() => {
if (!isTemplateSelected) {
return true;
}
return isTemplateSelected.freeInput && isTemplateSelected.freeInput.length > 0;
}, [isTemplateSelected]);
const characterInputRef = useRef<HTMLInputElement>(null);
const sceneInputRef = useRef<HTMLInputElement>(null);
const propInputRef = useRef<HTMLInputElement>(null);
@ -350,6 +365,7 @@ export default function VideoCreationForm() {
)}
{/* Text Input Area - Middle */}
{shouldShowInput && (
<div data-alt="text-input-wrapper" className="flex-1 px-4 py-4">
<textarea
data-alt="main-text-input"
@ -359,6 +375,7 @@ export default function VideoCreationForm() {
onChange={(e) => setInputText(e.target.value)}
/>
</div>
)}
{/* Control Panel - Bottom */}
<div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2">

View File

@ -8,7 +8,7 @@ export default function CreateVideo() {
<div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Transform any idea into a compelling video</div>
<div className='mx-auto w-full max-w-[600px] px-3 py-2 text-center text-xs text-gray-400 sm:px-16 sm:text-sm'>Generate professional videos from simple prompts. Browse community creations for inspiration, or start fresh with your own vision</div>
<div className='py-2'>
<div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16'>
<div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16 bg-[radial-gradient(ellipse_at_center,rgba(106,244,249,0.28)_0%,rgba(106,244,249,0.14)_35%,transparent_70%)]'>
<VideoCreationForm />
</div>
</div>