forked from 77media/video-flow
更新 模板选择交互
This commit is contained in:
parent
a4192ac71e
commit
6d033f5e59
@ -13,9 +13,12 @@ import { useDeviceType } from "@/hooks/useDeviceType"
|
||||
/**
|
||||
* A compact template showcase with a header and link to all templates.
|
||||
* Shows first 12 templates, in 3 columns, each with thumbnail, name and brief.
|
||||
* @param {object} props - Component props.
|
||||
* @param {boolean} [props.showTabs=true] - Whether to show category tabs.
|
||||
* @returns {JSX.Element} - FamousTemplate component
|
||||
*/
|
||||
const FamousTemplate: React.FC = () => {
|
||||
interface FamousTemplateProps { showTabs?: boolean }
|
||||
const FamousTemplate: React.FC<FamousTemplateProps> = ({ showTabs = true }) => {
|
||||
const { templateStoryList, getTemplateStoryList, isLoading } = useTemplateStoryServiceHook()
|
||||
const dispatch = useAppDispatch()
|
||||
const { isDesktop } = useDeviceType()
|
||||
@ -47,23 +50,25 @@ const FamousTemplate: React.FC = () => {
|
||||
<h2 data-alt="famous-template-title" className="text-xl py-4 font-semibold text-white">
|
||||
Hot Templates
|
||||
</h2>
|
||||
<div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}>
|
||||
{(["all", "music", "animation", "fantasy"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
type="button"
|
||||
data-alt={`template-tab-${tab}`}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`px-3 py-1 rounded-none text-sm transition-colors border ${
|
||||
activeTab === tab
|
||||
? "border-white/60 bg-white/80 text-slate-900"
|
||||
: "border-white/20 text-white/80 hover:border-white/40 hover:bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{showTabs && (
|
||||
<div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}>
|
||||
{(["all", "music", "animation", "fantasy"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
type="button"
|
||||
data-alt={`template-tab-${tab}`}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`px-3 py-1 rounded-none text-sm transition-colors border ${
|
||||
activeTab === tab
|
||||
? "border-white/60 bg-white/80 text-slate-900"
|
||||
: "border-white/20 text-white/80 hover:border-white/40 hover:bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
@ -82,7 +87,7 @@ const FamousTemplate: React.FC = () => {
|
||||
>
|
||||
<div
|
||||
data-alt="template-thumb"
|
||||
className="w-full h-full rounded-md overflow-hidden border border-white/10 flex-shrink-0"
|
||||
className="w-full h-full rounded-md overflow-hidden border border-white/10 flex-shrink-0 relative"
|
||||
>
|
||||
<img
|
||||
src={t.image_url?.[0] || ""}
|
||||
@ -93,6 +98,10 @@ const FamousTemplate: React.FC = () => {
|
||||
<div data-alt="template-meta" className="flex-1 min-w-0 absolute bg-black/50 bottom-0 left-0 right-0 px-3 py-3">
|
||||
<div data-alt="template-name" className="text-base font-bold text-white truncate">{t.name}</div>
|
||||
</div>
|
||||
<div
|
||||
data-alt="template-hover-overlay"
|
||||
className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out pointer-events-none z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -113,7 +122,20 @@ const FamousTemplate: React.FC = () => {
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-alt="try-this-button"
|
||||
className="absolute top-2 left-1/2 -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out bg-white/40 text-white hover:bg-white hover:text-black text-slate-900 text-xs font-medium px-2 py-1 rounded-full border-white/60 shadow-sm"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
const id = t.id || t.template_id
|
||||
dispatch(selectTemplateById(id))
|
||||
}}
|
||||
>
|
||||
Try this
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
@ -136,7 +158,7 @@ const FamousTemplate: React.FC = () => {
|
||||
dispatch(selectTemplateById(id))
|
||||
setActiveTemplateId(null)
|
||||
}}
|
||||
primaryLabel="Try it Free"
|
||||
primaryLabel="Try this"
|
||||
/>
|
||||
)
|
||||
})()}
|
||||
|
||||
@ -6,6 +6,7 @@ import { X, ChevronUp, ChevronsDown } from "lucide-react";
|
||||
import { ChatInputBox } from "@/components/ChatInputBox/ChatInputBox";
|
||||
import { VideoCreationForm } from '@/components/pages/create-video/CreateInput';
|
||||
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||
import { useAppSelector } from '@/lib/store/hooks';
|
||||
|
||||
export const HOME_BANNER_CODE = "homeBanner";
|
||||
const HOME_BANNER_COLLAPSE_KEY = "homeBannerCollapsedDate";
|
||||
@ -44,6 +45,7 @@ export default function HomeBanner() {
|
||||
const skipAutoCollapseRef = useRef<boolean>(false);
|
||||
|
||||
const { isMobile, isDesktop } = useDeviceType();
|
||||
const selectedTemplateId = useAppSelector((state) => state.creationTemplate.selectedTemplateId);
|
||||
|
||||
/** Returns YYYY-MM-DD for user's local timezone */
|
||||
const getLocalDateKey = () => {
|
||||
@ -96,6 +98,19 @@ export default function HomeBanner() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Collapse banner when a template is selected/changed
|
||||
useEffect(() => {
|
||||
if (!selectedTemplateId) return;
|
||||
setIsFlying(true);
|
||||
try {
|
||||
localStorage.setItem(HOME_BANNER_COLLAPSE_KEY, getLocalDateKey());
|
||||
} catch {}
|
||||
if (autoCollapseTimerRef.current) {
|
||||
clearTimeout(autoCollapseTimerRef.current);
|
||||
autoCollapseTimerRef.current = null;
|
||||
}
|
||||
}, [selectedTemplateId]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ export function TemplatePreviewModal({
|
||||
title,
|
||||
description,
|
||||
onPrimaryAction,
|
||||
primaryLabel = 'Try it Free',
|
||||
primaryLabel = 'Try this',
|
||||
}: TemplatePreviewModalProps) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
@ -394,14 +394,14 @@ export default function VideoCreationForm() {
|
||||
{/* Template Description */}
|
||||
{inputPlaceholder && (
|
||||
<div data-alt="template-description-wrapper" className="px-4">
|
||||
<div data-alt="template-description-text" className="italic text-white/50 text-sm">{inputPlaceholder}</div>
|
||||
<div data-alt="template-description-text" className="italic text-[#606775] text-sm">{inputPlaceholder}</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Text Input Area - Middle */}
|
||||
{shouldShowInput && (
|
||||
<div data-alt="text-input-wrapper" className="flex-1 flex px-4 py-2">
|
||||
{isTemplateSelected?.freeInput[0].input_name && (
|
||||
<div data-alt="template-description-text" className="text-white/60 text-sm pr-2">{isTemplateSelected?.freeInput[0].input_name}</div>
|
||||
<div data-alt="template-description-text" className="text-white/60 text-base pr-2 flex-shrink-0 capitalize">{`${isTemplateSelected?.freeInput[0].input_name}:`}</div>
|
||||
)}
|
||||
<textarea
|
||||
data-alt="main-text-input"
|
||||
@ -667,7 +667,7 @@ export default function VideoCreationForm() {
|
||||
applyTemplateSelection(currentTemplate);
|
||||
setCurrentTemplate(null)
|
||||
}}
|
||||
primaryLabel="Try it Free"
|
||||
primaryLabel="Try this"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -13,7 +13,7 @@ export default function CreateVideo() {
|
||||
<VideoCreationForm />
|
||||
</div>
|
||||
</div>
|
||||
<FamousTemplate />
|
||||
<FamousTemplate showTabs={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user