Files
reui-blocks/blocks/feature313.tsx
T
2026-08-13 20:38:45 +03:30

228 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { motion } from "framer-motion";
import { Play } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
interface Feature313Props {
className?: string;
imageSrc: string;
imageAlt: string;
videoUrl?: string;
videoCaption: string;
videoSecondaryCaption: string;
cards: {
id: string;
title: string;
image: string;
className?: string;
}[];
}
const Feature313 = ({
className,
imageSrc = "/block-media/shadcnblocks/block/lummi/bw6.jpeg",
imageAlt = "گزارش عملکرد",
videoUrl = "/block-media/shadcnblocks/block/man-1.mp4",
videoCaption = "داده پراکنده را به تصمیم تبدیل کنید",
videoSecondaryCaption = "تحلیل داده",
cards = [
{
id: "growth",
title: "گزارش زمان‌بندی‌شده",
image: "/block-media/shadcnblocks/block/placeholder-2.svg",
},
{
id: "design",
title: "تحلیل کوHORT",
image: "/block-media/shadcnblocks/block/placeholder-3.svg",
},
{
id: "support",
title: "مقایسه دوره‌ای",
image: "/block-media/shadcnblocks/block/placeholder-4.svg",
},
{
id: "partnerships",
title: "خروجی گزارش",
image: "/block-media/shadcnblocks/block/placeholder-5.svg",
},
],
}: Feature313Props) => {
const getYouTubeVideoId = (url: string) => {
const regExp =
/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
const match = url.match(regExp);
return match && match[2].length === 11 ? match[2] : null;
};
const youtubeId = videoUrl ? getYouTubeVideoId(videoUrl) : null;
const isYouTube = !!youtubeId;
const isMobile = useIsMobile();
return (
<section
dir="rtl"
lang="fa"
className={cn("relative w-full min-w-0 overflow-hidden bg-muted py-16 md:py-24", className)}
>
<div className="container flex min-w-0 flex-col gap-2 px-4 sm:px-6">
<div className="grid w-full min-w-0 grid-cols-1 gap-1 lg:grid-cols-4">
{cards.map((card, index) => (
<Card
key={card.id}
className="min-w-0 overflow-hidden border-none p-5 text-start shadow-none sm:p-8 lg:p-10"
>
<div className="flex min-w-0 items-center justify-between gap-2">
<div className="flex gap-1">
{cards.map((_, circleIndex) => (
<div
key={circleIndex}
className={`${index >= circleIndex ? "bg-primary" : "bg-muted"} size-2 rounded-full`}
/>
))}
</div>
<div className="text-sm font-medium text-muted-foreground">
{(index + 1).toLocaleString("fa-IR").padStart(2, "۰")}
</div>
</div>
<div className="flex min-w-0 flex-col gap-4 md:flex-row md:justify-between">
<div className="aspect-square size-12 shrink-0">
<img
src={card.image}
alt={card.title}
className="aspect-square h-full w-full rounded-lg object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-md leading-tight font-medium text-balance break-words">
{card.title}
</h3>
</div>
</div>
</Card>
))}
</div>
<Dialog>
<DialogTrigger asChild>
<motion.div
className="group relative aspect-video w-full min-w-0 min-h-48 cursor-pointer overflow-hidden rounded-lg sm:min-h-72"
whileHover="hover"
initial="initial"
>
<motion.div
className="absolute inset-0"
variants={{
initial: { filter: "blur(0px)" },
hover: { filter: "blur(4px)" },
}}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<motion.img
src={imageSrc}
alt={imageAlt}
className="h-full w-full max-w-full rounded-lg object-cover"
variants={{
initial: { scale: 1 },
hover: isMobile ? { scale: 1 } : { scale: 1.1 },
}}
transition={{ duration: 0.3, ease: "easeOut" }}
/>
</motion.div>
<div className="bg-opacity-20 absolute inset-0 flex items-center justify-center">
<motion.div
className="flex flex-col items-center justify-center gap-2 md:flex-row"
variants={{
initial: { gap: "0.5rem" },
hover: { gap: "0rem" },
}}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<motion.div
variants={{
initial: { x: 0, scale: 1 },
hover: {
x: isMobile ? 0 : "-75%",
scale: isMobile ? 1 : 1.2,
},
}}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<Button
size="lg"
className="size-14 rounded-full bg-secondary hover:bg-secondary lg:size-20"
>
<Play className="ms-0.5 size-5 text-primary md:size-7" />
<span className="sr-only">{videoCaption}</span>
</Button>
</motion.div>
<motion.div
className="min-w-0 text-center md:text-start"
variants={{
initial: { opacity: 1 },
hover: isMobile
? { opacity: 1 }
: { opacity: 0, transform: "-translate-y-3" },
}}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<div className="px-2 py-2 sm:px-4">
<p className="text-base font-bold text-balance break-words text-secondary sm:text-lg">
{videoCaption}
</p>
<p className="text-base font-medium text-balance break-words text-muted-foreground sm:text-lg">
{videoSecondaryCaption}
</p>
</div>
</motion.div>
</motion.div>
</div>
</motion.div>
</DialogTrigger>
<DialogContent className="w-[calc(100%-2rem)] max-w-[calc(100%-2rem)] border-none bg-black p-0 sm:w-full md:max-w-6xl md:p-1">
<DialogTitle className="sr-only">{videoCaption}</DialogTitle>
<DialogDescription className="sr-only">
{videoSecondaryCaption} {videoCaption}
</DialogDescription>
<div className="aspect-video w-full">
{isYouTube ? (
<iframe
className="h-full w-full"
src={`https://www.youtube.com/embed/${youtubeId}?autoplay=1`}
title="شاخص‌های کلیدی را یکجا ببینید"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
) : (
<video
className="h-full w-full rounded-lg"
controls
autoPlay
src={videoUrl}
>
ویجت‌های موردنیاز هر تیم را بچینید و فقط معیارهای مرتبط را ببینید.
</video>
)}
</div>
</DialogContent>
</Dialog>
</div>
</section>
);
};
export { Feature313 };