272 lines
10 KiB
TypeScript
272 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
import { ArrowRight, ArrowUpRight, Megaphone } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type Hero258Logo = {
|
|
name: string;
|
|
src: string;
|
|
alt: string;
|
|
className?: string;
|
|
};
|
|
|
|
type Hero258Card = {
|
|
title: string;
|
|
description: string;
|
|
image: string;
|
|
imageAlt: string;
|
|
href: string;
|
|
};
|
|
|
|
interface Hero258Props {
|
|
announcement?: {
|
|
text: string;
|
|
href: string;
|
|
};
|
|
heading?: string;
|
|
description?: string;
|
|
cta?: {
|
|
label: string;
|
|
href: string;
|
|
};
|
|
logos?: Hero258Logo[];
|
|
cards?: Hero258Card[];
|
|
className?: string;
|
|
}
|
|
|
|
const Hero258 = ({
|
|
announcement = {
|
|
text: "بهروزرسانی هفتگی کامپوننتها",
|
|
href: "#",
|
|
},
|
|
heading = "رابطهای کاربری حرفهای را سریعتر توسعه دهید",
|
|
description =
|
|
"صدها کامپوننت ظریف ساختهشده با رابط کاربری، استایلدهی مدرن و ری یو آی. مستقیم در پروژه خود کپی کنید و برای تطابق با برندتان سفارشیسازی کنید.",
|
|
cta = {
|
|
label: "مشاهده کامپوننتها",
|
|
href: "#",
|
|
},
|
|
logos = [
|
|
{
|
|
name: "Northwind",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-1.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-6 w-auto",
|
|
},
|
|
{
|
|
name: "Helios",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-4.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-6 w-auto",
|
|
},
|
|
{
|
|
name: "ری یو آی",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-6.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-6 w-auto",
|
|
},
|
|
{
|
|
name: "Silverline",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-8.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-5 w-auto",
|
|
},
|
|
{
|
|
name: "Orion Systems",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-9.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-6 w-auto",
|
|
},
|
|
{
|
|
name: "Crescent",
|
|
src: "/block-media/shadcnblocks/block/logos/company/fictional-company-logo-11.svg",
|
|
alt: "لوگوی شریک",
|
|
className: "h-6 w-auto",
|
|
},
|
|
],
|
|
cards = [
|
|
{
|
|
title: "آماده استفاده در پروژه",
|
|
description:
|
|
"هر بلاک کد آماده تولید است که مستقیم در پروژه خود کپی میکنید. بدون نصب وابستگی یا پیکربندی پیچیده فقط کامپوننتهای زیبا.",
|
|
image:
|
|
"/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
imageAlt: "ویرایشگر کد با بلاکهای ری یو آی",
|
|
href: "#",
|
|
},
|
|
{
|
|
title: "مبتنی بر رابط کاربری، استایلدهی مدرن و ری یو آی",
|
|
description:
|
|
"همه بلاکها از کامپوننتهای ری یو آی و استایلدهی مدرن استفاده میکنند و سازگاری، دسترسپذیری و سفارشیسازی آسان برای تطابق با سیستم طراحی شما را تضمین میکنند.",
|
|
image:
|
|
"/block-media/shadcnblocks/block/placeholder-2.svg",
|
|
imageAlt: "نمایش کامپوننتهای ری یو آی",
|
|
href: "#",
|
|
},
|
|
],
|
|
className,
|
|
}: Hero258Props) => {
|
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
|
|
return (
|
|
<section className={cn("bg-muted/40 py-16 lg:py-32", className)}>
|
|
<div className="container">
|
|
<div className="flex flex-col gap-12">
|
|
<div className="flex flex-col items-center gap-6 text-center">
|
|
<Badge
|
|
asChild
|
|
variant="outline"
|
|
className="group rounded-full border-border/50 bg-background/80 px-4 py-1.5 text-sm transition hover:border-border/90 hover:bg-background/90"
|
|
>
|
|
<a href={announcement.href}>
|
|
<span className="flex size-7 items-center justify-center rounded-full bg-foreground/10 text-foreground/80 transition group-hover:bg-foreground/15">
|
|
<Megaphone aria-hidden="true" className="size-3.5" />
|
|
<span className="sr-only">اطلاعیه</span>
|
|
</span>
|
|
<span className="font-medium text-foreground/90">
|
|
{announcement.text}
|
|
</span>
|
|
<ArrowRight className="size-4 text-foreground/50 transition group-hover:translate-x-0.5 group-hover:text-foreground/80 rtl:rotate-180" />
|
|
</a>
|
|
</Badge>
|
|
<div className="flex max-w-4xl flex-col gap-4">
|
|
<h1 className="text-xl font-medium tracking-tight text-balance md:text-2xll lg:text-2xll">
|
|
{heading}
|
|
</h1>
|
|
<p className="mx-auto max-w-2xl text-base text-muted-foreground md:text-lg">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
<Button asChild size="lg">
|
|
<a href={cta.href}>{cta.label}</a>
|
|
</Button>
|
|
</div>
|
|
|
|
{logos.length > 0 && (
|
|
<div className="mx-auto flex w-full flex-wrap items-center justify-center gap-x-8 gap-y-6 text-muted-foreground/60 md:gap-x-12 md:py-6">
|
|
{logos.map((logo) => (
|
|
<div
|
|
key={logo.name}
|
|
className="transition hover:text-foreground"
|
|
aria-label={logo.name}
|
|
>
|
|
<img
|
|
src={logo.src}
|
|
alt={logo.alt}
|
|
className={cn(
|
|
"mx-auto h-6 w-auto opacity-70 grayscale transition hover:opacity-90 hover:grayscale-0 dark:invert",
|
|
logo.className,
|
|
)}
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className="hide-scrollbar flex gap-4 overflow-x-auto pb-4 md:overflow-visible md:pb-0"
|
|
role="list"
|
|
aria-label="کارتهای ویژه"
|
|
dir="rtl"
|
|
>
|
|
{cards.map((card, index) => {
|
|
const isHovered = hoveredIndex === index;
|
|
const isOtherHovered =
|
|
hoveredIndex !== null && hoveredIndex !== index;
|
|
|
|
return (
|
|
<motion.article
|
|
layout
|
|
key={card.title}
|
|
role="listitem"
|
|
className={cn(
|
|
"group relative flex min-h-96 min-w-72 flex-none overflow-hidden rounded-xl border border-border/60 transition duration-300 md:min-w-0 md:flex-1",
|
|
)}
|
|
onMouseEnter={() => setHoveredIndex(index)}
|
|
onMouseLeave={() => setHoveredIndex(null)}
|
|
onFocus={() => setHoveredIndex(index)}
|
|
onBlur={() => setHoveredIndex(null)}
|
|
tabIndex={0}
|
|
animate={{
|
|
flex: isHovered ? 1.3 : isOtherHovered ? 0.85 : 1,
|
|
}}
|
|
transition={{ duration: 0.45, ease: "easeInOut" }}
|
|
>
|
|
<a href={card.href} key={card.title}>
|
|
<img
|
|
src={card.image}
|
|
alt={card.imageAlt}
|
|
className={cn(
|
|
"absolute inset-0 h-full w-full object-cover transition duration-500",
|
|
isHovered ? "scale-105" : "scale-100",
|
|
)}
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-b from-black/20 via-black/30 to-black/70 transition duration-300 group-hover:via-black/50 group-hover:to-black/80" />
|
|
|
|
<div className="relative z-10 flex h-full w-full flex-col justify-between text-start">
|
|
<div className="p-6">
|
|
<h3 className="text-lg font-semibold tracking-tight text-primary-foreground md:text-xl">
|
|
{card.title}
|
|
</h3>
|
|
</div>
|
|
<motion.div
|
|
layout
|
|
className="flex w-full items-end gap-4 p-6"
|
|
initial={false}
|
|
animate={{
|
|
justifyContent: isHovered
|
|
? "space-between"
|
|
: "flex-start",
|
|
}}
|
|
transition={{ duration: 0.35, ease: "easeInOut" }}
|
|
>
|
|
<AnimatePresence initial={false}>
|
|
{isHovered && (
|
|
<motion.p
|
|
layout
|
|
key="card-description"
|
|
className="flex-1 text-sm text-primary-foreground md:text-base"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{
|
|
duration: 0.4,
|
|
delay: 0.2,
|
|
ease: "easeInOut",
|
|
}}
|
|
>
|
|
<span>{card.description}</span>
|
|
</motion.p>
|
|
)}
|
|
</AnimatePresence>
|
|
<motion.button
|
|
layout
|
|
className="rounded-full border border-secondary bg-secondary p-2 text-sm font-medium text-secondary-foreground"
|
|
initial={false}
|
|
transition={{ duration: 0.4, ease: "easeInOut" }}
|
|
>
|
|
<a href={card.href}>
|
|
<ArrowUpRight className="size-4 rtl:rotate-180" />
|
|
</a>
|
|
</motion.button>
|
|
</motion.div>
|
|
</div>
|
|
</a>
|
|
</motion.article>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Hero258 };
|