Files
mr_decor/components/sections/feature/feature-270.tsx
T

325 lines
10 KiB
TypeScript

"use client";
import { motion } from "motion/react";
import React, { useState } from "react";
import { cn } from "@/lib/utils";
const defaultFeatures = [
{
number: "01",
title: "Instant Implementation",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img11.jpeg",
imageOrder: "order-1",
contentOrder: "order-2",
},
{
number: "02",
title: "One time payment",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img12.jpeg",
imageOrder: "order-2",
contentOrder: "order-1",
},
{
number: "03",
title: "Developer Friendly",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img14.jpeg",
imageOrder: "order-1",
contentOrder: "order-2",
},
];
interface Feature270Item {
number: string;
title: string;
description: string;
image: string;
imageOrder: string;
contentOrder: string;
}
interface Feature270Props {
className?: string;
heading?: string;
features?: Feature270Item[];
headingAlign?: "left" | "right" | "center";
contentAlign?: "left" | "center" | "right";
showNumbers?: boolean;
/** وقتی false باشد، دکمه/لینک «Learn More» روی کارت‌ها نمایش داده نمی‌شود. */
showLearnMore?: boolean;
}
const Feature270 = ({
className,
heading = "Why Choose Shadcn Blocks?",
features = defaultFeatures,
headingAlign = "left",
contentAlign = "left",
showNumbers = true,
showLearnMore = true,
}: Feature270Props) => {
const headingClass = {
left: "text-left",
right: "text-right",
center: "text-center",
}[headingAlign];
const contentClass = {
left: "text-left",
center: "text-center",
right: "text-right",
}[contentAlign];
return (
<section className={cn("relative w-full py-32", className)}>
<div className="container mx-auto px-4 md:px-6">
<div className={cn("w-full space-y-5 lg:w-3/5 mb-12", headingAlign === "right" && "lg:mr-0 lg:ml-auto")}>
<h2 className={cn("text-heading-2 mb-6 w-full max-w-lg font-semibold tracking-tight", headingClass)}>
{heading}
</h2>
</div>
<div className="relative grid grid-cols-1 auto-rows-auto items-stretch gap-6 md:gap-3 md:min-h-[68rem] md:grid-cols-2 lg:min-h-[32rem] lg:grid-cols-3">
{features.map((feature, index) => {
const cardContent = (
<div className="flex flex-col h-full min-h-[26rem]">
<div className={feature.imageOrder}>
<img
src={feature.image}
height="1000"
width="1000"
className="h-70 w-full rounded-3xl object-cover group-hover/card:shadow-xl"
alt="thumbnail"
/>
</div>
<div className={cn("mt-4 w-full p-3 flex-1 flex flex-col min-h-[8rem] gap-0", feature.contentOrder, contentClass)}>
{showNumbers && (
<p className="text-caption font-medium text-muted-foreground tracking-tighter">
{feature.number}
</p>
)}
<h3 className="text-heading-3 mt-3 mb-2 font-semibold leading-tight tracking-tight text-foreground">
{feature.title}
</h3>
<p className="text-caption leading-relaxed text-muted-foreground flex-1">
{feature.description}
</p>
</div>
</div>
);
const cardClassName = "w-full h-full min-h-[28rem] rounded-3xl !bg-muted/70 p-4 flex flex-col";
return (
<PinContainer
key={index}
title={showLearnMore ? "Learn More" : undefined}
href={showLearnMore ? "https://www.shadcnblocks.com/blocks" : undefined}
className={cardClassName}
showPerspective={showLearnMore}
>
{cardContent}
</PinContainer>
);
})}
</div>
</div>
</section>
);
};
export { Feature270 };
// Below is the modified component from Aceternity UI
// Original source: npx shadcn@latest add https://ui.aceternity.com/registry/3d-pin.json
// Modified to follow our coding standards and design system
// We respect copyright and attribution to the original creators
export const PinContainer = ({
children,
title,
href,
className,
containerClassName,
showPerspective = true,
}: {
children: React.ReactNode;
title?: string;
href?: string;
className?: string;
containerClassName?: string;
/** وقتی false باشد، دکمه/برچسب «Learn More» روی هاور نمایش داده نمی‌شود. افکت سه‌بعدی هاور حفظ می‌شود. */
showPerspective?: boolean;
}) => {
const [transform, setTransform] = useState(
"translate(-50%,-50%) rotateX(0deg)",
);
const onMouseEnter = () => {
setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
};
const onMouseLeave = () => {
setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
};
const wrapperClassName = cn(
"group/pin relative z-10",
href ? "cursor-pointer" : "cursor-default",
containerClassName,
);
const outerTransform = showPerspective
? "rotateX(70deg) translateZ(0deg)"
: "rotateX(0deg) translateZ(0deg)";
const content = showPerspective ? (
<>
<div
style={{
perspective: "1000px",
transform: outerTransform,
}}
className="absolute top-1/2 left-1/2 mt-4 ml-[0.09375rem] w-full -translate-x-1/2 -translate-y-1/2"
>
<div
style={{
transform: transform,
}}
className="absolute top-1/2 left-1/2 flex w-full items-start justify-start overflow-hidden rounded-2xl transition duration-700"
>
<div className={cn("relative z-10", className)}>{children}</div>
</div>
</div>
{title != null && <PinPerspective title={title} />}
</>
) : (
<div className={cn("relative z-10", className)}>{children}</div>
);
if (href) {
return (
<a
className={wrapperClassName}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
href={href}
>
{content}
</a>
);
}
return (
<div
className={wrapperClassName}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{content}
</div>
);
};
export const PinPerspective = ({
title,
}: {
title?: string;
href?: string;
}) => {
return (
<motion.div className="pointer-events-none z-20 flex h-80 w-96 items-center justify-center opacity-0 transition duration-500 group-hover/pin:opacity-100">
<div className="inset-0 -mt-7 h-full w-full flex-none">
<div className="absolute inset-x-0 top-0 flex justify-center">
<span className="relative z-10 flex items-center space-x-2 rounded-full bg-foreground px-4 py-0.5 ring-1 ring-border">
<span className="text-caption relative z-20 inline-block py-0.5 font-bold text-background">
{title}
</span>
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-secondary/0 via-secondary to-secondary/0 transition-opacity duration-500 group-hover/btn:opacity-40"></span>
</span>
</div>
<div
style={{
perspective: "1000px",
transform: "rotateX(70deg) translateZ(0)",
}}
className="absolute top-1/2 left-1/2 mt-4 ml-[0.09375rem] -translate-x-1/2 -translate-y-1/2"
>
<>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 0,
}}
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
></motion.div>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 2,
}}
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
></motion.div>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 4,
}}
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
></motion.div>
</>
</div>
<>
<motion.div className="absolute right-1/2 bottom-1/2 h-20 w-px translate-y-[14px] bg-gradient-to-b from-transparent to-secondary blur-[2px] group-hover/pin:h-40" />
<motion.div className="absolute right-1/2 bottom-1/2 h-20 w-px translate-y-[14px] bg-gradient-to-b from-transparent to-secondary group-hover/pin:h-40" />
<motion.div className="absolute right-1/2 bottom-1/2 z-40 h-[4px] w-[4px] translate-x-[1.5px] translate-y-[14px] rounded-full bg-secondary blur-[3px]" />
<motion.div className="absolute right-1/2 bottom-1/2 z-40 h-[2px] w-[2px] translate-x-[0.5px] translate-y-[14px] rounded-full bg-secondary" />
</>
</div>
</motion.div>
);
};