更新 模板选择交互

This commit is contained in:
moux1024 2025-10-22 04:02:58 +08:00
parent a4192ac71e
commit 6d033f5e59
5 changed files with 63 additions and 26 deletions

View File

@ -13,9 +13,12 @@ import { useDeviceType } from "@/hooks/useDeviceType"
/** /**
* A compact template showcase with a header and link to all templates. * 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. * 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 * @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 { templateStoryList, getTemplateStoryList, isLoading } = useTemplateStoryServiceHook()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { isDesktop } = useDeviceType() 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"> <h2 data-alt="famous-template-title" className="text-xl py-4 font-semibold text-white">
Hot Templates Hot Templates
</h2> </h2>
<div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}> {showTabs && (
{(["all", "music", "animation", "fantasy"] as const).map((tab) => ( <div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}>
<button {(["all", "music", "animation", "fantasy"] as const).map((tab) => (
key={tab} <button
type="button" key={tab}
data-alt={`template-tab-${tab}`} type="button"
onClick={() => setActiveTab(tab)} data-alt={`template-tab-${tab}`}
className={`px-3 py-1 rounded-none text-sm transition-colors border ${ onClick={() => setActiveTab(tab)}
activeTab === tab className={`px-3 py-1 rounded-none text-sm transition-colors border ${
? "border-white/60 bg-white/80 text-slate-900" activeTab === tab
: "border-white/20 text-white/80 hover:border-white/40 hover:bg-white/10" ? "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> {tab.toUpperCase()}
))} </button>
</div> ))}
</div>
)}
</div> </div>
{isLoading ? ( {isLoading ? (
@ -82,7 +87,7 @@ const FamousTemplate: React.FC = () => {
> >
<div <div
data-alt="template-thumb" 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 <img
src={t.image_url?.[0] || ""} 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-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 data-alt="template-name" className="text-base font-bold text-white truncate">{t.name}</div>
</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>
</div> </div>
@ -113,7 +122,20 @@ const FamousTemplate: React.FC = () => {
setIsModalOpen(true) 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> </div>
) )
})} })}
@ -136,7 +158,7 @@ const FamousTemplate: React.FC = () => {
dispatch(selectTemplateById(id)) dispatch(selectTemplateById(id))
setActiveTemplateId(null) setActiveTemplateId(null)
}} }}
primaryLabel="Try it Free" primaryLabel="Try this"
/> />
) )
})()} })()}

View File

@ -6,6 +6,7 @@ import { X, ChevronUp, ChevronsDown } from "lucide-react";
import { ChatInputBox } from "@/components/ChatInputBox/ChatInputBox"; import { ChatInputBox } from "@/components/ChatInputBox/ChatInputBox";
import { VideoCreationForm } from '@/components/pages/create-video/CreateInput'; import { VideoCreationForm } from '@/components/pages/create-video/CreateInput';
import { useDeviceType } from '@/hooks/useDeviceType'; import { useDeviceType } from '@/hooks/useDeviceType';
import { useAppSelector } from '@/lib/store/hooks';
export const HOME_BANNER_CODE = "homeBanner"; export const HOME_BANNER_CODE = "homeBanner";
const HOME_BANNER_COLLAPSE_KEY = "homeBannerCollapsedDate"; const HOME_BANNER_COLLAPSE_KEY = "homeBannerCollapsedDate";
@ -44,6 +45,7 @@ export default function HomeBanner() {
const skipAutoCollapseRef = useRef<boolean>(false); const skipAutoCollapseRef = useRef<boolean>(false);
const { isMobile, isDesktop } = useDeviceType(); const { isMobile, isDesktop } = useDeviceType();
const selectedTemplateId = useAppSelector((state) => state.creationTemplate.selectedTemplateId);
/** Returns YYYY-MM-DD for user's local timezone */ /** Returns YYYY-MM-DD for user's local timezone */
const getLocalDateKey = () => { 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(() => { useEffect(() => {
let active = true; let active = true;

View File

@ -33,7 +33,7 @@ export function TemplatePreviewModal({
title, title,
description, description,
onPrimaryAction, onPrimaryAction,
primaryLabel = 'Try it Free', primaryLabel = 'Try this',
}: TemplatePreviewModalProps) { }: TemplatePreviewModalProps) {
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);

View File

@ -394,14 +394,14 @@ export default function VideoCreationForm() {
{/* Template Description */} {/* Template Description */}
{inputPlaceholder && ( {inputPlaceholder && (
<div data-alt="template-description-wrapper" className="px-4"> <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> </div>
)} )}
{/* Text Input Area - Middle */} {/* Text Input Area - Middle */}
{shouldShowInput && ( {shouldShowInput && (
<div data-alt="text-input-wrapper" className="flex-1 flex px-4 py-2"> <div data-alt="text-input-wrapper" className="flex-1 flex px-4 py-2">
{isTemplateSelected?.freeInput[0].input_name && ( {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 <textarea
data-alt="main-text-input" data-alt="main-text-input"
@ -667,7 +667,7 @@ export default function VideoCreationForm() {
applyTemplateSelection(currentTemplate); applyTemplateSelection(currentTemplate);
setCurrentTemplate(null) setCurrentTemplate(null)
}} }}
primaryLabel="Try it Free" primaryLabel="Try this"
/> />
</div> </div>
); );

View File

@ -13,7 +13,7 @@ export default function CreateVideo() {
<VideoCreationForm /> <VideoCreationForm />
</div> </div>
</div> </div>
<FamousTemplate /> <FamousTemplate showTabs={false} />
</div> </div>
); );
} }