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.
|
* 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"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export default function CreateVideo() {
|
|||||||
<VideoCreationForm />
|
<VideoCreationForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FamousTemplate />
|
<FamousTemplate showTabs={false} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user