更新 模板选择交互

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.
* 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"
/>
)
})()}

View File

@ -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;

View File

@ -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);

View File

@ -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>
);

View File

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