feat: implement references section with marquee-scrolling testimonial cards and data model
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
export interface CustomTestimonialCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
quote: string;
|
||||
author: string;
|
||||
role?: string;
|
||||
avatar?: string;
|
||||
rating?: number;
|
||||
lineClamp?: boolean;
|
||||
}
|
||||
|
||||
export const CustomTestimonialCard = React.forwardRef<HTMLDivElement, CustomTestimonialCardProps>(
|
||||
({ quote, author, role, avatar, rating, lineClamp, className, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`flex flex-col p-6 bg-white border border-neutral-200 rounded-2xl shadow-sm transition-shadow ${className || ""}`}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex-1">
|
||||
{rating && (
|
||||
<div className="flex gap-1 mb-4 text-yellow-400">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<svg
|
||||
key={i}
|
||||
className={`w-5 h-5 ${i < rating ? "fill-current" : "text-neutral-200 fill-current"}`}
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className={`text-neutral-700 italic leading-relaxed ${lineClamp ? "line-clamp-4" : ""}`}>
|
||||
"{quote}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mt-6">
|
||||
{avatar ? (
|
||||
<Image
|
||||
src={avatar}
|
||||
alt={author}
|
||||
width={48}
|
||||
height={48}
|
||||
className="rounded-full object-cover w-12 h-12"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-full bg-neutral-200 flex items-center justify-center text-neutral-500 font-bold">
|
||||
{author.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-neutral-900">{author}</span>
|
||||
{role && <span className="text-sm text-neutral-500">{role}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
CustomTestimonialCard.displayName = "CustomTestimonialCard";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { TestimonialCard } from "poyraz-ui/molecules";
|
||||
|
||||
import { CustomTestimonialCard } from "@/components/custom-testimonial-card";
|
||||
import { REFERENCES } from "@/data/references";
|
||||
import { useLocale } from "next-intl";
|
||||
import { getLocalizedValue } from "@/lib/locale";
|
||||
@@ -7,40 +7,44 @@ import { getLocalizedValue } from "@/lib/locale";
|
||||
type ReferenceCardsProps = {
|
||||
className?: string;
|
||||
cardClassName?: string;
|
||||
lineClamp?: boolean;
|
||||
};
|
||||
|
||||
export function ReferenceCards({ className, cardClassName }: ReferenceCardsProps) {
|
||||
export function ReferenceCards({ className, cardClassName, lineClamp }: ReferenceCardsProps) {
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{REFERENCES.map((item) => {
|
||||
const card = (
|
||||
<TestimonialCard
|
||||
<CustomTestimonialCard
|
||||
quote={getLocalizedValue(item.quote, locale)}
|
||||
author={item.author}
|
||||
role={getLocalizedValue(item.role, locale)}
|
||||
avatar={item.avatar}
|
||||
rating={item.rating}
|
||||
className={`h-full rounded-sm ${cardClassName || "w-70 shrink-0"}`}
|
||||
lineClamp={lineClamp}
|
||||
className={`h-full ${cardClassName || "w-70 shrink-0"}`}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!item.profileHref) {
|
||||
return <div key={item.id}>{card}</div>;
|
||||
const href = item.documentHref || item.profileHref;
|
||||
|
||||
if (!href) {
|
||||
return <div key={item.id} className="h-full">{card}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
<a
|
||||
key={item.id}
|
||||
href={item.profileHref}
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={`${item.author} LinkedIn profili`}
|
||||
className="block"
|
||||
aria-label={`${item.author} bağlantısı`}
|
||||
className="block h-full transition-transform hover:scale-[1.02]"
|
||||
>
|
||||
{card}
|
||||
</Link>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,9 @@ export function ReferencesDetailContent() {
|
||||
</Link>
|
||||
|
||||
<ReferenceCards
|
||||
className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3"
|
||||
className="columns-1 gap-4 sm:columns-2 lg:columns-3 [&>a]:mb-4 [&>a]:break-inside-avoid [&>div]:mb-4 [&>div]:break-inside-avoid"
|
||||
cardClassName="w-full"
|
||||
lineClamp={false}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,20 @@ import { ReferenceCards } from "@/components/reference-cards";
|
||||
export function ReferencesSection() {
|
||||
return (
|
||||
<section className="space-y-8 pt-0 sm:pt-12">
|
||||
<div className="relative overflow-hidden rounded-sm max-h-48">
|
||||
<ReferenceCards className="flex w-max gap-3" />
|
||||
<div className="relative overflow-hidden rounded-sm py-4">
|
||||
{/* We use two sets of cards for seamless infinite marquee effect */}
|
||||
<div className="flex w-max gap-4 animate-marquee pause-on-hover">
|
||||
<ReferenceCards className="flex gap-4 shrink-0" lineClamp={true} cardClassName="w-80" />
|
||||
<ReferenceCards className="flex gap-4 shrink-0" lineClamp={true} cardClassName="w-80" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none opacity-95 absolute top-0 right-0 h-full w-48 bg-linear-to-l from-white via-white/80 to-transparent"
|
||||
className="pointer-events-none absolute top-0 right-0 h-full w-32 bg-gradient-to-l from-white to-transparent"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute top-0 left-0 h-full w-32 bg-gradient-to-r from-white to-transparent"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user